Compare commits

...

185 Commits

Author SHA1 Message Date
Robin Newhouse 3d0f624f84 chore: add changeset for code-simplifier skill 2026-01-22 16:06:55 -08:00
Robin Newhouse e68d55a402 feat(skills): add code-simplifier skill definition
Introduces a new skill definition for the `code-simplifier` in the `.cline/skills` directory. This skill provides a framework for refining code clarity, consistency, and maintainability while strictly preserving functionality and adhering to project-specific standards.
2026-01-22 16:04:27 -08:00
Bee c093ca1760 refactor: replace console with Logger service (#8741)
* chore: add grit rule to enforce Logger service over console calls

Add a new Grit linting rule that detects direct console method usage
(log, debug, error, warn, info) and prompts developers to use the
Logger service instead for consistent logging practices.

The rule is configured in biome.jsonc to apply to most source files
while excluding test files, webview-ui, evals, standalone, e2e tests,
and scripts where direct console usage may be acceptable.

* support variadic args

* wip: migrate console to Logger

* migrate rest of console logger

* Switch to Logger

* Migrations

* shared

* use shared

* revert format change

* Update tests to stub Logger instead of console

* verbose in dev mode
2026-01-22 13:16:37 -08:00
Seb Duerr dc85299a73 Remove deprecated zai-glm-4.6 model from Cerebras provider (#8717) 2026-01-22 12:58:07 -08:00
ClineXDiego 4533ed3ea8 fix: support drag & drop files from SSH remote workspaces (#8804)
Add vscode-remote: scheme to the valid URI filter for drag & drop operations.
This allows files from SSH Remote workspaces to be dropped into the chat.

Fixes #7606
2026-01-22 12:37:11 -08:00
Max 66d7664d1b improve cline command permission parsing logic (#8544)
- cline command permission flag can now parse subshells correctly and
validate that subshells don't contain disallowed commands.

Co-authored-by: Max Paulus 🥪 <max@cline.bot>
2026-01-22 11:11:53 -08:00
dependabot[bot] 50aeb7098f chore(deps): bump undici (#8783)
Bumps [undici](https://github.com/nodejs/undici) to 6.23.0 and updates ancestor dependency . These dependencies need to be updated together.


Updates `undici` from 6.22.0 to 6.23.0
- [Release notes](https://github.com/nodejs/undici/releases)
- [Commits](https://github.com/nodejs/undici/compare/v6.22.0...v6.23.0)

Updates `undici` from 7.16.0 to 7.19.0
- [Release notes](https://github.com/nodejs/undici/releases)
- [Commits](https://github.com/nodejs/undici/compare/v6.22.0...v6.23.0)

---
updated-dependencies:
- dependency-name: undici
  dependency-version: 6.23.0
  dependency-type: indirect
- dependency-name: undici
  dependency-version: 7.19.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: ClineXDiego <diego@cline.bot>
2026-01-22 10:50:49 -08:00
github-actions[bot] c2e6eafb45 v3.52.0 Release Notes (#8633)
- Users with ChatGPT Plus or Pro subscriptions can now use GPT-5 models directly through Cline without needing an API key. Authentication is handled via OAuth through OpenAI's authentication system.
- Grok models are now moving out of free tier and into paid plans.
- Introduces comprehensive Jupyter Notebook support for Cline, enabling AI-assisted editing of `.ipynb` files with full cell-level context awareness.

- Bugs in DiffViewProvider for file editing
- Ollama's recommended models to use correct identifiers

Co-authored-by: Arafatkatze <arafat.da.khan@gmail.com>
2026-01-22 10:48:32 -08:00
Juan Pablo Flores 0220628c39 Adds Open AI Codex docs (#8791)
* feat(docs): add OpenAI Codex provider setup instructions and update model selection guidance

* fix(docs): improve clarity and formatting in OpenAI Codex documentation

* Update docs/provider-config/openai-codex.mdx

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

* Update docs/provider-config/openai-codex.mdx

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Tony Loehr <turingxo@gmail.com>
2026-01-22 09:30:12 -08:00
CandiedUniverse 5052220195 feat(hooks): Make hooks always enabled and remove its feature setting. [CLINE-1179] (#8777)
* feat(hooks): Standardize on calling getHooksEnabledSafe().

* feat(hooks): Hard-code getHookEnabledSafe() to return true unless on Windows.

* feat(hooks): Remove hooks setting from the CLI.

* feat(hooks): Remove hooks toggle from the Feature Settings UI.

* feat(hooks): Remove hooksEnabled toggles from settings/task APIs.

* feat(hooks): Stop using hooksEnabled setting.

* feat(hooks): npm run changeset

* feat(hooks): Simplify getHooksEnabledSafe() function signature.

* feat(hooks): Remove hooks setting migration.

feat(hooks): Remove hooksEnabled from updateSettingsCli() conversion.

feat(hooks): Use 'reserved' for removed fields in UpdateSettingsRequest protobuf.
2026-01-22 09:26:16 -08:00
CandiedUniverse abf3081e56 Rules: Wire up conditional rules functionality [ENG-1470] (#8669)
* feat(rules): Write technical design / implementation plan doc.

* update frontmatter plan

* feat(rules): Initial implementation based on plan doc.

* feat(rules): Add tool-call path harvesting for path-scoped Cline Rules.

* chore(rules): exclude internal paths-frontmatter plan doc from PR

* fix(rules): use latest user message for paths frontmatter context

* feat(rules): Implement conditional_rules_applied say type.

* feat(rules): changes as per Cline's code review feedback

* feat(rules): npm run changeset

* feat(rules): Changes as per ellipsis-dev feedback.

* feat(rules): Changes as per code review feedback (i.e. don't bloat the task context).

* feat(rules): Fix failing unit tests.
2026-01-22 09:25:59 -08:00
Michael Gutin 125cb78a31 Switch background element now has a 3:1 contrast ration with thumb and rule row background in vs code light and dark themes (#8747) 2026-01-22 12:16:51 -05:00
CandiedUniverse 17539228ea feat(cli): Add note about ctrl+c to exit the CLI. [CLINE-1162] (#8796)
* feat(cli): Add note about ctrl+c to exit the CLI.

* feat(cli): npm run changeset
2026-01-22 09:01:57 -08:00
Ara bdb7cc36fa feat(DiffEditRow): add button to open file in editor (#8564)
* refactor(diff): return result object with line tracking metadata

Change constructNewFileContent to return an object containing newContent
and line number information instead of just the string content. Add
charIndexToLineNumber helper function to support tracking where changes
occur in the file.

Update all callers and tests to access the newContent property from
the result object.

* minor fix

* fix: hide line numbers when not available from backend

* chore: add changeset

* feat: add startLineNumbers support to ApplyPatchHandler

* fix: split V4A @@ chunks into separate Patch objects for proper line numbers

* fix(DiffEditRow): preserve +/- prefix in diff line display for backwards compatibility

* fix line numbers
2026-01-21 21:38:45 -08:00
Tony Loehr d3e1065707 added jupyter docs (#8742)
* added jupyter docs

* fix jupyter docs and add gifs

* fix jupyter docs 2

* fix jupyter gif placement
2026-01-21 16:15:26 -08:00
Tomás Barreiro 6303951f20 Use getAllFlagsAndPayloads when fetching PostHog feature flags (#8774)
* Use

* Fix typo

* Remove old code
2026-01-21 13:12:34 -08:00
Ara fc184e07a2 feat: update free onboarding models with Kat Coder Pro and Devstral (#8773)
* feat: update free onboarding models with Kat Coder Pro and Devstral

- Replace xAI Grok Code Fast 1 with KwaiKAT Kat Coder Pro as primary free model
- Add Mistral Devstral 2512 as additional free model option
- Update model specifications (context window, image/cache support)

* Apply suggestions from code review

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

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-21 12:22:10 -08:00
Ara e88f4ea412 fix: update package name from cline to claude-dev in changesets (#8772)
Rename package identifier in changeset frontmatter to reflect
the correct package name for version tracking.
2026-01-21 11:35:54 -08:00
Saoud Rizwan 133ade3f1a feat(prompt): add stderr redirect guidance for command execution (#8765)
* feat(prompt): add stderr redirect guidance for command execution

* test: update system prompt snapshots
2026-01-21 11:09:18 -08:00
Max 857411c035 fix initial prompt bugs (#8428)
* fix initial prompt bugs

- going straight to act wasn't working in interactive mode.
- slash command autocomplete wasn't working in the initial prompt
- refactored some naming to be more clear

* Apply suggestion from @ellipsis-dev[bot]

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

* Apply suggestion from @ellipsis-dev[bot]

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

* Apply suggestion from @ellipsis-dev[bot]

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

---------

Co-authored-by: Max Paulus 🥪 <max@cline.bot>
Co-authored-by: CandiedUniverse <132302818+candieduniverse@users.noreply.github.com>
Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
2026-01-21 09:40:08 -08:00
Robin Newhouse c59f068584 fix: improve Jupyter notebook diff view and LLM context handling (#8759)
* fix: restore switchToSpecializedEditor for Jupyter notebook diff views

This restores the notebook diff view functionality that was accidentally
removed during rebase. The method was incorrectly identified as dead code,
but it was being called in update() when isFinal is true.

Restored functionality:
- Abstract method definition in DiffViewProvider
- Call to switchToSpecializedEditor() in update() after final content
- Full implementation in VscodeDiffViewProvider for notebook diff views
  - Temporary file management for modified content
  - File system watcher for synchronization
  - Proper cleanup in resetDiffView()
- No-op implementations in ExternalDiffViewProvider and FileEditProvider
- Test stub in DiffViewProvider.test.ts

* fix: open notebooks in Jupyter editor after save, strip outputs for LLM

- Override showFile in VscodeDiffViewProvider to open .ipynb files
  with the Jupyter notebook editor instead of leaving stale diff view
- Remove notebook check that was skipping showFile in base class
- Add getOriginalContentForLLM() to return sanitized notebook content
- Strip notebook outputs from finalContent to reduce LLM context size

fix: sanitize notebook content in write responses to prevent context explosion

Previously, after editing a notebook, the full file content (including all
base64-encoded images and HTML table outputs) was sent back to the LLM in
the <final_file_content> response. This caused context to explode to ~200K
tokens for simple edits on notebooks with rendered outputs.

Changes:
- Add stripAllOutputs option to sanitizeNotebookForLLM()
- Apply sanitization in DiffViewProvider.saveChanges() for finalContent
- Add getOriginalContentForLLM() for diff error responses
- Strip all outputs (not just images) in write paths since outputs
  aren't needed for editing - they regenerate when cells run

Results: 95% reduction in context usage for notebook write responses
(196KB → 9KB in testing).

* chore: add changeset for Jupyter notebook diff view fix

* fix: show error message when Jupyter extension is missing for notebook diffs

* refactor: move os require to top-level import

---------

Co-authored-by: Max <maxpaulus43@gmail.com>
2026-01-21 08:07:03 -08:00
Bee f7561c31fa fix: prevent race condition in telemetry service initialization (#8764) 2026-01-21 07:23:21 -08:00
Jose Castelli f0a97dafc8 fix: fixing testing framework and removing old integration tests [PF-413] (#8727)
fix: fixing testing framework and removing old integration tests [PF-413] #8727
2026-01-21 14:38:03 +01:00
Saoud Rizwan d422f689a6 docs(storage): document StateManager multi-instance behavior
Adds a detailed comment explaining that each VS Code window has its own
StateManager cache, which is why settings like plan/act mode don't sync
between running instances. The cache is populated from disk only during
initialize() and never re-read, providing natural isolation.
2026-01-20 20:50:55 -08:00
Robin Newhouse 00efcc1c2b fix: prevent duplicate diff errors when parallel tool calling is enabled (#8763)
* fix: prevent duplicate diff errors when parallel tool calling is enabled

When a `replace_in_file` diff fails during streaming with parallel tool
calling enabled (GPT-5/Codex models), the same error message was being
added to userMessageContent on each streaming chunk, causing hundreds of
duplicates and context window overflow.

Root cause: The duplicate prevention added in 09276ebf4 used the
`didAlreadyUseTool` flag, but this flag is intentionally not set when
parallel tool calling is enabled (per 00e9d6f52). This was because
`didAlreadyUseTool` is designed to block subsequent tools, which is the
opposite of what parallel tool calling needs.

The fix adds per-call_id tracking via `diffErrorPushedForCallIds` Set:
- Track which specific tool calls have already had their error pushed
- Works for both parallel and non-parallel tool calling
- Different parallel tool calls can each report their own errors
- Same call_id only pushes error once, regardless of streaming chunks

This is compatible with the parallel tool calling design because GPT-5
models (which auto-enable parallel tool calling) use native tool calling
and always have a call_id. The mechanism is separate from didAlreadyUseTool
which controls flow (blocking tools) vs this which prevents duplicates.

Related commits:
- 09276ebf4: fix: prevent duplicate error messages during streamed edit
  tool failures (only worked when parallel tool calling disabled)
- 00e9d6f52: feat: add experimental parallel tool calling support
  (deliberately excluded didAlreadyUseTool from parallel mode)

* test: add unit tests for diffErrorPushedForCallIds duplicate prevention

Tests cover:
- Basic Set behavior (initialization, tracking, clearing)
- Duplicate prevention logic for parallel tool calling
- Edge cases (empty/undefined call_id, rapid streaming chunks)
- Reset behavior between API requests

* Add changeset

* refactor: rename diffErrorPushedForCallIds to errorPushedForCallIds

Generalizes the tracking mechanism per reviewer feedback from @abeatrix.

The more generic name allows the same pattern to be reused for other
tool handlers that may need duplicate error prevention in the future,
not just diff-related errors.

No functional changes - just renaming.
2026-01-20 20:48:29 -08:00
Robin Newhouse 59251e9de6 fix: ensure document finalization in approval flow (#8757)
When file operations use the approval flow (isFinal=false), the document
content was not being properly finalized before user approval. This caused
content duplication when shortening files - old content at the end was
preserved instead of being replaced.

Root cause: FileProviderOperations passed isFinal=false to
DiffViewProvider.update(), which:
1. Popped the last line (treated as "incomplete" for streaming)
2. Limited the replacement range to currentLine + 1
3. Skipped truncation of trailing content

Fix: Always pass isFinal=true to update() since the content IS complete.
The isFinal parameter in FileProviderOperations now only controls whether
to save after the update, not the update behavior itself.

This follows the philosophy of dd35448a9 by fixing at the source rather
than adding cleanup logic.
2026-01-20 15:52:12 -08:00
Saoud Rizwan 2df952f571 fix: OpenAI Codex provider improvements based on OpenAI feedback (#8754)
* style: improve OAuth success page design

* fix: hide thinking budget slider for OpenAI Codex provider

OpenAI Codex models use discrete reasoning effort levels (low/medium/high)
controlled via the global OpenAI Reasoning Effort setting, not token-based
thinking budgets like Anthropic models.

* fix: hide thinking toggle with display:none instead of disabled state

* fix: hide cost display for OpenAI Codex provider

Subscription-based provider has no per-token costs, so showing $0.00 is misleading.
2026-01-20 15:31:39 -08:00
Bee 6e7dc55781 fix: disable delete button for favorited history item (#8753)
* fix: disable delete button for favorited history item

- Add useMemo hook to memoize favorite state calculation, improving performance by avoiding repeated computations of `pendingFavoriteToggles[item.id] ?? item.isFavorited`
- Disable delete button for favorited items but keep delete button for standardized UI display
- Replace multiple inline favorite state checks with centralized `isFavoritedItem` variable for better code maintainability
- Simplify favorite toggle logic by using memoized value

This change ensures favorited items cannot be deleted and reduces unnecessary re-renders when favorite state is accessed.

* isFavoritedItem
2026-01-20 13:28:42 -08:00
Tomás Barreiro ec879fc549 Do not update the providers if the currently configured one is valid (#8751) 2026-01-20 21:49:39 +01:00
Tomás Barreiro 75b050de5c Init Sync Worker when applying remote config (#8749) 2026-01-20 19:52:16 +01:00
Thanh Nguyen 0c4acd9457 docs: fix outdated Ollama model names in documentation (#8551)
* docs: fix outdated Ollama model names in documentation

Fixes #7918

- Updated qwen3-coder-30b to qwen2.5-coder:32b (correct identifier)
- Replaced devstral-small with codellama:34b-code (existing model)
- Changed ollama run to ollama pull for initial download

* chore: add changeset for Ollama model names fix
2026-01-20 10:28:30 -08:00
dependabot[bot] 0dc3d7084d chore(deps): bump qs and express (#8360)
Bumps [qs](https://github.com/ljharb/qs) and [express](https://github.com/expressjs/express). These dependencies needed to be updated together.

Updates `qs` from 6.13.0 to 6.14.1
- [Changelog](https://github.com/ljharb/qs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ljharb/qs/compare/v6.13.0...v6.14.1)

Updates `express` from 5.0.1 to 5.2.1
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/master/History.md)
- [Commits](https://github.com/expressjs/express/compare/v5.0.1...v5.2.1)

---
updated-dependencies:
- dependency-name: qs
  dependency-version: 6.14.1
  dependency-type: indirect
- dependency-name: express
  dependency-version: 5.2.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-20 09:49:02 -08:00
Tomás Barreiro 3cfafe6583 Clean old Feature Flags (#8748) 2026-01-20 09:47:07 -08:00
tekulam d8aefbaabd feat: Jupyter Notebook Enhancements (#8053)
* Jupyter Notebook Enhancements

* fix: implement dynamic notebook instructions for replace_in_file

Leverages the new dynamic prompt infrastructure to conditionally inject Jupyter Notebook-specific instructions into the `replace_in_file` tool. This ensures that the model receives guidance on handling JSON structure in `.ipynb` files only when the `enhancedNotebookInteractionEnabled` setting is active, keeping the default prompt clean for other users.

- Added `enhancedNotebookInteractionEnabled` to global settings and system prompt context
- Updated `replace_in_file` tool to use a dynamic instruction function that appends notebook rules based on context
- Wired up state management to pass the setting value to the prompt builder

* refactor: unify notebook output sanitization across two code paths

Previously, context menu commands (Add to Cline, Explain, etc.) wiped ALL
notebook outputs, while file mentions preserved text and only truncated images.
Additionally, when outputs weren't cleared, massive base64-encoded image data
was sent directly to the LLM, flooding context with garbage.

Changes:
- Create shared notebook-utils.ts with sanitization logic
- Update extract-text.ts to use shared utility
- Update commandUtils.ts to sanitize instead of clearing outputs

Both paths now truncate base64 image data with "[IMAGE DATA TRUNCATED]"
while preserving useful text outputs like print statements and errors.

* feat(improve): unify prompt sending behavior for improveWithCline

Refactor improveWithCline to build a single prompt and handle sending uniformly: for notebooks, populate existing task if available and send immediately; otherwise, create new task. This unifies behavior across selected text and notebook contexts, removing dependency on sendAddToInputEvent and simplifying logic. Minor formatting tweaks in extension.ts for notebook context string.

* refactor: remove notebook_cell_json from proto, move notebook context to dedicated commands

The contributor's original implementation added notebook_cell_json to the
CommandContext proto, which was then populated in getContextForCommand() for
any notebook file when enhancedNotebookInteractionEnabled was set.

This couples notebook-specific functionality to the general command proto,
which feels heavy for a niche feature. Protos should stay clean and general.

Changes:
- Remove notebook_cell_json field from CommandContext proto
- Export findMatchingNotebookCell() from commandUtils.ts
- Update Jupyter commands (JupyterGenerateCell, JupyterExplainCell,
  JupyterImproveCell) in extension.ts to fetch cell JSON directly and
  bundle it into the notebookContext parameter
- Update command files to use only notebookContext parameter
- Remove notebook-specific handling from getContextForCommand()

Result: Notebook context only flows through dedicated Jupyter commands.
Regular commands (Add to Cline, Fix, etc.) work the same for all file types.
The proto stays clean and general-purpose.

Note: This removes the behavior where regular commands would get notebook
context when used on .ipynb files with enhancedNotebookInteractionEnabled.
That feature is now exclusive to the dedicated Jupyter menu commands.

* feat: improve notebook handling for empty notebooks

- Add semicolon to import statement for consistency
- Prevent errors by checking cell count before accessing notebook cells
- Add fallback in getContextForCommand for active notebook editor when no text editor is available
- Ensures robustness when dealing with empty or cell-less notebooks in the VSCode extension

* refactor: extract common notebook context logic for Jupyter commands

Extracted duplicated code into a helper function `getNotebookCommandContext` to handle active notebook checks, context retrieval, and cell JSON fetching. This reduces duplication in `JupyterGenerateCell` and `JupyterExplainCell` commands, improving code maintainability and readability. Minor import semicolon fix for consistency.

* fix: block notebook edits when enhanced interaction disabled

Prevent crashes when enhancedNotebookInteractionEnabled is false by blocking .ipynb file edits in WriteToFileToolHandler. Added validation to return an error message instructing the user to enable the setting, and set didRejectTool to stop the operation. Reading notebooks remains unaffected.

* fix(mentions): reorder parameters in parseMentions signature

Reordered the parameters in the parseMentions function to move the default parameter to the end of the argument list. This change ensures consistency in the function signature and correctly aligns arguments at the call site in the Task class.

This update was done to fix failing tests.

* Created proper diff views for vscode nd removed unnecessary logs

* feat: make replace_in_file prompt dynamic based on open files

Add editorTabs to SystemPromptContext to expose open/visible files.

Populate editorTabs in Task using HostProvider.

Conditionally include notebook-specific instructions in replace_in_file tool only when .ipynb files are open or visible.

Refactor replace_in_file prompt construction for better readability.

* feat: enable enhanced notebook interaction by default

Remove enhancedNotebookInteractionEnabled feature flag and enable notebook support globally.

Update tool handlers to process notebook cells automatically.

Update file extraction logic to support .ipynb files natively.

Clean up settings UI and state management.

* fix: restore accidentally removed promptContext fields

Commit ec68e7c2a accidentally removed enableParallelToolCalling and
terminalExecutionMode from promptContext when refactoring to add
editorTabs. These fields are still in SystemPromptContext interface
and actively used by system prompt templates.

* fix: complete feature flag removal from package.json

Commit a1dc73f93 removed the enhancedNotebookInteractionEnabled flag
from runtime code but forgot to update package.json. The Jupyter menu
items were hidden because the when conditions checked a setting that
defaulted to false.

- Remove config check from notebook menu item when conditions
- Remove unused setting definition

* fix: change changeset from minor to patch

* fix: code quality improvements in VscodeDiffViewProvider

- Use proper ES6 import for os module instead of require()
- Remove dead commented-out code (closeCurrentTextDiffEditor)
- Improve comment explaining the render delay

* fix: watch specific temp file instead of entire directory

* test: update snapshots for replace_in_file whitespace change

The PR's refactoring of replace_in_file.ts changed indentation in
the tool description from tabs to spaces. Updating snapshots to
match.

* fix: remove merge artifact marginTop from checkpoints div

* test: update DiffViewProvider test stub for new abstract method

* fix: remove dead notebook diff view code (switchToSpecializedEditor)

The switchToSpecializedEditor() method was declared as abstract and
implemented in all DiffViewProvider subclasses, but was never called
from anywhere. This meant ~180 lines of notebook diff view code
(temp file management, file watchers, cleanup) would never execute.

Removing this dead code. The notebook diff view feature will need a
follow-up PR to properly integrate it by calling the method from the
update() flow when isFinal is true.

---------

Co-authored-by: Robin Newhouse <robin@cline.bot>
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
2026-01-19 20:18:39 -08:00
Bee 1ff60522f1 fix: display history task text without highlight (#8740) 2026-01-19 18:09:00 -08:00
Saoud Rizwan 42321e5b95 fix: reduce JetBrains workflow comment spam on PRs (#8738)
Change trigger from every push to:
- PR open/reopen only (removed synchronize)
- Manual /test-jetbrains comment command

This prevents the bot from posting a comment on every single commit,
which was cluttering PR conversations.
2026-01-19 16:57:20 -08:00
Saoud Rizwan b2634d2276 feat: add OpenAI Codex provider for ChatGPT Plus/Pro subscriptions (#8664)
* feat: add OpenAI Codex provider for ChatGPT Plus/Pro subscriptions

Add a new provider that allows users with ChatGPT Plus or Pro subscriptions
to use GPT-5 models directly through Cline without needing an API key.

Key features:
- OAuth authentication via OpenAI (PKCE flow)
- Routes requests to chatgpt.com/backend-api/codex/responses
- Subscription-based pricing (no per-token costs)
- Models: gpt-5.2-codex, gpt-5.1-codex-max, gpt-5.1-codex-mini, gpt-5.2

New files:
- src/integrations/openai-codex/oauth.ts: OAuth manager with PKCE, token storage/refresh
- src/core/api/providers/openai-codex.ts: API handler for Codex backend
- src/core/controller/account/openAiCodexSignIn.ts: Sign-in RPC handler
- src/core/controller/account/openAiCodexSignOut.ts: Sign-out RPC handler
- webview-ui/src/components/settings/providers/OpenAiCodexProvider.tsx: Settings UI

* fix: force native tool calling for Responses API providers

Providers using OpenAI's Responses API (openai-codex, some openai-native
models) require native tool calling. XML tools don't work with these APIs,
causing duplicate tool calls and malformed arguments.

Changes:
- Add openai-codex to isNextGenModelProvider() list so native variant
  matchers recognize it
- Force enableNativeToolCalls=true when model uses ApiFormat.OPENAI_RESPONSES,
  regardless of user setting
- Document Responses API provider requirements in CLAUDE.md

* chore: rename OpenAI Codex provider label to ChatGPT Codex Subscription

* fix: use shared fetch wrapper for proxy support in OpenAI Codex provider

* revert: remove CLAUDE.md changes from this PR

* fix: restore .clinerules/general.md to match main

* chore: rename provider label to OpenAI Codex (ChatGPT Plus/Pro)

* chore: add network.md reference to clinerules

* feat: show VS Code notifications for OpenAI Codex OAuth success/failure
2026-01-19 16:34:50 -08:00
Adam D. 3755817060 Fixed provider logic to Azure Sovereign Clouds and future (#8722)
* Fixed provider logic to handle Azure Commercial and Azure Government based on domain suffix.

* Adjusted region logic for simple long term modifications for other soverign cloud regions.
2026-01-20 01:32:41 +01:00
Tomás Barreiro 8ae18e6a16 Schema changes for prompt uploading (#8621)
* feat: add cloud storage and sync system infrastructure

Add cloud storage capabilities with support for R2 and S3 adapters:

- Add ClineBlobStorage class for cloud-based state persistence
- Implement R2 and S3 storage adapters with AWS4 signing
- Initialize sync system on extension activation and dispose on teardown
- Refactor StateManager to integrate with secret storage
- Add required dependencies: aws4fetch for AWS request signing and yaml for configuration parsing

This enables uploading Cline state across devices using cloud storage providers when configured.

* refactor: remove yaml dependency and refactor storage/backfill logic

Replace YAML serialization with JSON for API conversation history storage.
Refactor backfill worker to read task IDs from history file instead of
filesystem directory scanning, improving performance and consistency.

Changes:
- Remove yaml package dependency (^2.8.2)
- Switch from YAML.stringify to JSON.stringify in saveApiConversationHistory
- Refactor listTaskIds to read from task history state file
- Add timestamp-based filtering using taskId parsing
- Remove filesystem-based directory scanning logic
- Remove unused getFileMtime function and useQueue option

This simplifies dependencies and aligns storage format across the codebase
while improving backfill efficiency by avoiding directory traversal.

* clean up

* clean up

* feat(worker): add queue cleanup and size enforcement mechanisms

- Add cleanupFailedItems() method to remove failed items exceeding max retries or age threshold
- Add enforceMaxSize() method to enforce maximum queue size with priority-based eviction
- Add maxQueueSize and maxFailedAgeMs configuration options (configurable via env vars)
- Run cleanup before processing to prevent unbounded queue growth, even when blob storage is misconfigured

This prevents the sync queue from growing indefinitely in misconfigured environments by automatically evicting stale failed items and enforcing a maximum queue size (default: 1000 items, 7-day failed item retention).

* feat(sync): add remote config support for blob store settings

- Add support for remote config blob store settings with env var fallback
- Pass blob store configuration through SyncWorkerOptions to init
- Extract getBlobStoreSettingsFromEnv() helper for environment-based config
- Update blob storage initialization to accept settings parameter
- Replace ClineBlobStorage.isConfigured() with blobStorage.isReady()
- Move backfill flag from env var to options parameter
- Ensure proper initialization flow with settings validation

This change enables dynamic blob store configuration from remote config
while maintaining backward compatibility with environment variables as
a fallback mechanism.

* apply feedback

* apply feedback

* remove global fetch import

* remove secretStorage init. use const

* use a single pass with forEach instead of filter-map-delete loops

* Schema changes for prompt uploading

* Fix types and add tests

* Add tests

* Remove test

* Addapt to the BlobStoreSettings

* Add tests

* Add the missing fields

* Extend tests

---------

Co-authored-by: abeatrix <beatrix@cline.bot>
2026-01-20 01:02:04 +01:00
Bee 113d7fb292 feat: add sync system infrastructure with blob store support (ENG-1468) (#8628)
* feat: add cloud storage and sync system infrastructure

Add cloud storage capabilities with support for R2 and S3 adapters:

- Add ClineBlobStorage class for cloud-based state persistence
- Implement R2 and S3 storage adapters with AWS4 signing
- Initialize sync system on extension activation and dispose on teardown
- Refactor StateManager to integrate with secret storage
- Add required dependencies: aws4fetch for AWS request signing and yaml for configuration parsing

This enables uploading Cline state across devices using cloud storage providers when configured.

* refactor: remove yaml dependency and refactor storage/backfill logic

Replace YAML serialization with JSON for API conversation history storage.
Refactor backfill worker to read task IDs from history file instead of
filesystem directory scanning, improving performance and consistency.

Changes:
- Remove yaml package dependency (^2.8.2)
- Switch from YAML.stringify to JSON.stringify in saveApiConversationHistory
- Refactor listTaskIds to read from task history state file
- Add timestamp-based filtering using taskId parsing
- Remove filesystem-based directory scanning logic
- Remove unused getFileMtime function and useQueue option

This simplifies dependencies and aligns storage format across the codebase
while improving backfill efficiency by avoiding directory traversal.

* clean up

* clean up

* feat(worker): add queue cleanup and size enforcement mechanisms

- Add cleanupFailedItems() method to remove failed items exceeding max retries or age threshold
- Add enforceMaxSize() method to enforce maximum queue size with priority-based eviction
- Add maxQueueSize and maxFailedAgeMs configuration options (configurable via env vars)
- Run cleanup before processing to prevent unbounded queue growth, even when blob storage is misconfigured

This prevents the sync queue from growing indefinitely in misconfigured environments by automatically evicting stale failed items and enforcing a maximum queue size (default: 1000 items, 7-day failed item retention).

* feat(sync): add remote config support for blob store settings

- Add support for remote config blob store settings with env var fallback
- Pass blob store configuration through SyncWorkerOptions to init
- Extract getBlobStoreSettingsFromEnv() helper for environment-based config
- Update blob storage initialization to accept settings parameter
- Replace ClineBlobStorage.isConfigured() with blobStorage.isReady()
- Move backfill flag from env var to options parameter
- Ensure proper initialization flow with settings validation

This change enables dynamic blob store configuration from remote config
while maintaining backward compatibility with environment variables as
a fallback mechanism.

* apply feedback

* apply feedback

* remove global fetch import

* remove secretStorage init. use const

* use a single pass with forEach instead of filter-map-delete loops

---------

Co-authored-by: Tomás Barreiro <52393857+BarreiroT@users.noreply.github.com>
2026-01-19 15:40:14 -08:00
Bee 592d045565 chore: remove duplicated dify file (#8734)
I belive the file is duplicated of src/core/api/providers/dify.ts file.
new DifyHandler is imported from src/core/api/providers/dify.ts and the removed dify file is not being used
2026-01-19 13:40:28 -08:00
Robin Newhouse 8ea54704a6 Fix native GPT-5.x variant routing for codex models (#8671)
Some GPT-5.1/5.2 (codex) models can trigger OpenAI Responses API errors like: 'function_call was provided without its required reasoning item'.

Root cause: PromptRegistry selects the first matching variant; our native-gpt-5 matcher previously let any 'codex' model bypass the gpt-5.1/gpt-5.2 exclusion, so gpt-5.2-codex could incorrectly match NATIVE_GPT_5 instead of NATIVE_GPT_5_1.

Change: route all GPT-5.1 and GPT-5.2 models (including codex variants) to NATIVE_GPT_5_1; keep GPT-5 (and gpt-5-codex) on the less strict NATIVE_GPT_5.

This was observed as a hard-to-reproduce, sporadic error, but we want the safer routing available for anyone hitting it.
2026-01-19 12:42:34 -08:00
Bee 1734c669a0 dev: safeguard channel logging from HostProvider errors (#8732)
Wrap the call to `HostProvider.logToChannel` in a try/catch block so that logging does not throw when the host provider is not ready or unavailable.
Remove the now‑unused `ErrorService` imports and logging calls, keeping the logger focused on its core responsibility while preventing unnecessary failures during startup or testing. P.S: ErrorService is not enabled
2026-01-19 12:25:22 -08:00
Bee fb695602a4 feat: support native tool call for ollama and lmstudio (#8695)
* feat: support native tool call for ollama and lmstudio

Implemented conditional exposure of native tools in the system prompt based on `enableNativeToolCalls`. Updated the XS variant used by ollama and lmstudio configuration to match local models only and removed obsolete tool references. Added comprehensive tool overrides for both native and non‑native scenarios.

* add changeset

* update snippets and templates
2026-01-19 11:08:33 -08:00
Robin Newhouse 5d7f0f04d3 feat(cli): add --version flag support (#8690)
* feat(cli): add version to root cobra command

Expose --version by setting the root command version.

* feat(cli): include core version in CLI version output

Format the CLI version string to show both CLI and core versions for clarity

* feat(cli): centralize version string output for CLI

Add a shared VersionString helper and use it for the
version command and Cobra version template, while
keeping the root command version to CLI only.
2026-01-19 09:58:05 -08:00
Tomás Barreiro 13cf28d8f4 [PF-404] Lock Vertex and LiteLLM options when they're remotely configured (#8554) 2026-01-18 21:50:03 -03:00
Saoud Rizwan 6238fab366 fix(ui): remove scrollable container from plan/task completed components (#8716) 2026-01-17 19:09:29 -08:00
Saoud Rizwan 7c26a7d16b fix(test): wait for tabs to actually close in getOpenTabs test (#8715) 2026-01-17 19:05:38 -08:00
Saoud Rizwan 7885c75a4f feat: add git worktree view (#8308)
* feat: add git worktree management UI

Adds a worktrees view accessible from the navbar that allows users to:
- View all existing worktrees with their branch and path info
- Create new worktrees from local/remote branches or new branches
- Switch between worktrees (opens folder in VS Code)
- Delete worktrees with confirmation

Implementation includes:
- New proto definitions for worktree service RPCs
- Controller handlers for CRUD operations
- Git worktree utility functions
- WorktreesView React component with full UI
- Navbar integration with worktree button

* feat: enhance worktree creation error handling in WorktreesView

Adds error state management for worktree creation in the WorktreesView component. Introduces a new state variable to capture and display error messages when worktree creation fails, improving user feedback during the process.

* feat: add worktree defaults retrieval to WorktreeService and UI

Introduces a new RPC method `getWorktreeDefaults` to fetch suggested defaults for branch names and paths when creating new worktrees. Updates the WorktreesView component to utilize this method, enhancing the user experience by auto-generating branch names and paths. Additionally, integrates tooltips for improved UI interactions and adds a close button to the worktree creation modal.

* feat: implement .worktreeinclude file management in WorktreeService

Adds new RPC methods to the WorktreeService for managing .worktreeinclude files, including retrieving the status of the file and creating it with specified content. Updates the WorktreesView component to handle the creation and status checking of .worktreeinclude, enhancing user experience by automating file management for worktrees. Additionally, modifies the UI to reflect these changes, including updated tooltips and improved error handling.

* feat: add checkout branch functionality to WorktreeService and UI

Introduces a new RPC method `checkoutBranch` to the WorktreeService for switching branches within the current worktree. Updates the WorktreesView component to support this functionality, enhancing user experience by allowing seamless branch switching. Additionally, refines the UI layout for better responsiveness and improves loading/error state handling.

* feat: reposition New Worktree button for improved UI layout

Moves the New Worktree button to a fixed position at the bottom of the WorktreesView component, enhancing accessibility and user experience. The button is now styled to occupy the full width, ensuring better visibility and interaction within the UI.

* feat: update documentation links in WorktreesView component

Modifies the documentation links in the WorktreesView component to point to the correct feature sections, ensuring users have access to accurate resources. Additionally, adds the "features/worktrees" entry in the documentation JSON for better organization.

* feat: add worktree merging functionality and UI enhancements

Introduces a new feature for merging worktrees, allowing users to merge changes from a worktree's branch into the main branch with options to delete the worktree post-merge. Updates the WorktreesView component to include a merge modal, handling merge conflicts, and integrating with the WorktreeService for seamless operations. Additionally, enhances documentation to reflect these changes.

* refactor: replace exec with simple-git for worktree operations

Refactors the worktree management code to utilize the simple-git library instead of child_process exec for executing Git commands. This change enhances code readability and maintainability by providing a more streamlined interface for Git operations in the checkoutBranch, mergeWorktree, and git-worktree modules. Additionally, it improves error handling and reduces the complexity of command execution.

* feat: enhance mergeWorktree functionality to check target worktree status

Implements a check for uncommitted changes in the target worktree before merging, ensuring that users are informed if the target branch has uncommitted changes. This update improves error handling and user feedback during the merge process by verifying the state of both the source and target worktrees. Additionally, it integrates the listWorktrees utility to identify the correct worktree for the target branch.

* refactor: optimize worktree loading to prevent UI flickering

Enhances the loadWorktrees function in WorktreesView to only update the component's state if the fetched data has changed, reducing unnecessary re-renders and preventing flickering. This change improves the user experience by providing a smoother interface when loading worktrees. Additionally, simplifies the polling mechanism for updates.

* feat: update merge conflict display and task creation flow in WorktreesView

Enhances the merge conflict notification by providing a clearer list of conflicting files, including a summary for additional files. Additionally, modifies the task creation flow to close the worktrees view upon task creation, improving user experience during the merge process.

* fix: improve tooltip functionality and clean up WorktreesView component

Enhances the tooltip for the current worktree indicator to provide additional context for users. Additionally, removes the display of commit hashes in the worktree list to streamline the UI, improving overall clarity and user experience.

* feat: add symlink functionality for .worktreeinclude to sync with .gitignore

Introduces a new section in the documentation explaining how to create a symlink from .gitignore to .worktreeinclude. This allows users to automatically sync patterns between the two files, simplifying worktree setup. Additionally, includes a note for users needing different patterns to create a regular .worktreeinclude file instead.

* fix: simplify merge request button in WorktreesView component

Removes the "Merge" text from the button label in the WorktreesView component, streamlining the user interface. This change focuses on clarity by allowing the button to simply prompt users to "Ask Cline to Resolve," enhancing the overall user experience during merge conflict resolution.

* Update docs/features/worktrees.mdx

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

* Update webview-ui/src/components/worktrees/WorktreesView.tsx

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

* Fixes docs not rendering

* perf(worktree): optimize file copying for .worktreeinclude

Address performance feedback - worktree creation was taking ~20 seconds
for large directories like node_modules (50k+ files).

Optimizations:
- Use native `cp -r` for entire directories (10-20x faster)
- Parallelize file copying with batches of 100 (5-10x faster)
- Parallelize directory traversal with Promise.all

The old implementation copied files sequentially which caused the
bottleneck. Now directories like node_modules are copied using the
system's native cp command, and individual files are copied in
parallel batches.

Also adds unit tests for the worktree-include module.

* feat(worktree): add multi-root and subfolder workspace warnings

- Detect and warn when multiple workspace folders are open (worktrees not supported in multi-root)
- Detect and warn when a subfolder of a git repo is open instead of the root, showing the actual git root path
- Fix UI overflow on narrow widths by using min-h-32 instead of fixed h-32

* refactor(worktree): auto-fill defaults when create modal opens

* fix(worktree): add cursor pointer to create modal close button

* feat(worktree): add clear buttons to create modal input fields

* feat(worktree): add quick launch button on home page

Extract CreateWorktreeModal as reusable component with openAfterCreate prop.
Add New Worktree Window button to WelcomeSection that creates a worktree
and opens it in a new window. Shows current worktree branch and path info.

* refactor(ui): polish home screen and worktree modal

- Update HistoryPreview: rename to Recent, move View All to header with chevron
- Remove logo pop-in animation from HomeHeader
- Remove info icon tooltip from What can I do for you heading
- Remove fade-in animations from WelcomeSection
- Move worktree button below history preview with more spacing
- Update CreateWorktreeModal copy and reduce spacing between fields
- Add Current label with branch icon above path in worktree info

* feat(worktree): auto-open Cline sidebar on worktree launch

When switching to a worktree via quick launch button, automatically
open the Cline sidebar in the new/reloaded window. Uses globalState
to pass the target path between windows, reading directly from
context.globalState at startup to bypass StateManager cache timing.

* fix(worktree): improve quick launch UX

- Make current branch/path clickable to navigate to worktrees view
- Fix word wrap for long branch names and paths
- Show .worktreeinclude warning in create modal with learn more link

* chore: ignore .worktrees directory and CLAUDE.local.md

* feat(worktree): add delete confirmation modal

* refactor(ui): remove worktrees button from title bar

* fix(worktree): improve .worktreeinclude warning styling

* docs(worktrees): update for new UI features

- Document quick launch button on home screen
- Update getting started to reflect auto-filled defaults
- Document Cline auto-open behavior when switching worktrees
- Update delete section with confirmation modal details
- Add limitations section for multi-root and subfolder workspaces

* fix(worktree): rename Main badge to Primary

* feat(worktree): add worktrees button to sidebar header

Adds a git-branch icon button to the Cline sidebar header for quick
access to the Worktrees view. Also updates docs to mention this new
entry point and adds a typical workflow section.

* fix(worktree): UI polish

- Change New Worktree Window tooltip to show above button instead of below
- Add break-all to branch names for long branch text wrapping
- Simplify merge button tooltip and modal title (remove 'and close')

* fix(e2e): update tests to match renamed Recent header

* fix(worktree): improve non-git repo message

* fix(worktree): wrap path instead of truncating

* fix(e2e): update auth test to use aria-label instead of removed class

* fix(worktree): add option to delete branch when deleting worktree

- Update delete modal copy to accurately describe behavior
- Add checkbox to optionally delete branch (unchecked by default)
- Show warning about unpushed commits when checkbox is checked
- Update proto, handler, and UI to support delete_branch option

* fix: remove worktrees menu button from sidebar

Remove the worktrees button from the VS Code extension menu bar.

* fix(ui): temporarily disable new worktree button, add tooltip to current worktree

Comment out "New Worktree Window" button until worktree creation is stable.
Add tooltip to current worktree info with "View and manage git worktrees.
Great for running parallel Cline tasks."

* feat: add worktree-exp feature flag for worktrees feature

Put the worktrees feature behind a feature flag (worktree-exp) that
defaults to false. When enabled, users can toggle the feature in
settings. The home page worktree section only shows when both the
feature flag is enabled and the user setting is on.

* feat: add telemetry for worktree feature usage

Track worktree feature engagement:
- worktree.view_opened: when users open worktrees view (with source)
- worktree.created: when worktrees are created (with total count)
- worktree.merge_attempted: when merge is attempted (success/conflicts)

* fix: replace DangerButton with Button variant="danger"

DangerButton component was removed from main. Use the standard
Button component with variant="danger" instead.

* Fix merge conflict artifacts

* Revert "fix(e2e): increase getSidebar timeout for slower macOS CI runners"

This reverts commit 19479a019c.

* fix: clean up shadow git checkpoint data when deleting worktrees

* fix: add worktreesEnabled to proto and fix duplicate import

* fix: revert e2e test changes to match main

* fix: revert Navbar.tsx to match main (JetBrains compat)

* fix: revert package.json navigation order to match main

* fix: properly add worktrees_enabled to proto without moving fields

* fix(e2e): update tests to match UI changes

- Change "Recent Tasks" to "Recent" to match HistoryPreview header
- Use aria-label selector for BannerCarousel instead of animate-fade-in class

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Juan Pablo <juan@cline.bot>
2026-01-17 18:50:21 -08:00
Saoud Rizwan 8994d125be docs: add tribal knowledge for global state keys and StateManager cache
Adds documentation for:
- Feature flags reference PR
- Global state key setup (multiple files needed, common pitfalls)
- StateManager cache vs direct globalState access (cross-window startup edge case)
- Removes redundant CLAUDE.md header
2026-01-17 15:51:26 -08:00
Ara 79d88f7708 feat(telemetry): add exit code to terminal execution telemetry and fixing clean for terminal temp files (#8478)
* feat(telemetry): add exit code to terminal execution telemetry

Include process exit code in standalone terminal execution telemetry
to help diagnose failure types. Common codes like 127 (command not found)
and 126 (permission denied) provide valuable debugging information.

- Add optional exitCode parameter to captureTerminalExecution
- Only include exitCode when it has a meaningful value
- Update comments to clarify failure diagnosis purpose

* feat(temp): add centralized temp file manager with auto-cleanup

Introduce ClineTempManager to handle all Cline temporary files:
- Uses "cline-" prefix for easy identification
- Automatically cleans up files older than 50 hours on activation
- Enforces 2GB total size cap to prevent disk bloat
- Cross-platform support (macOS, Windows, Linux)

Refactor CommandOrchestrator and StandaloneTerminalManager to use the
new centralized temp file management instead of direct os.tmpdir() calls.

* feat: add periodic temp file cleanup every 24 hours

- Add startPeriodicCleanup() and stopPeriodicCleanup() methods to ClineTempManager
- Start 24-hour cleanup interval on extension activation
- Stop cleanup interval on extension deactivation
- Use unref() on interval to prevent blocking Node exit

* minor fix

* minor fix

* fix: centralize temp cleanup and scan full temp dir

Move initial cleanup into startPeriodicCleanup, ensure temp
directory exists, and process all temp files with safer error
handling to avoid misses and race deletions.

* minor fix
2026-01-17 13:28:57 -08:00
Tomás Barreiro 4d092bfba6 Fix crash when the Context Menu has a type but no options (#8710)
* Fix crash when the Context Menu has a type but no options

* Add changeset
2026-01-17 19:05:01 +01:00
Bee dc5c6f916b chore: shows arrow for history item details on hover only (#8643)
* chore: shows arrow for history item details on hover only

Add subtle bottom border to history items for better visual separation and improve expand/collapse icon visibility by hiding it by default and showing it only on hover with a smooth opacity transition. This creates a cleaner interface while maintaining discoverability of the expand functionality.

Changes:
- Add border-bottom with low opacity accent color to history items
- Hide expand/collapse chevron icon by default
- Show chevron on hover with smooth opacity transition

* align checkbox
2026-01-16 23:00:26 -08:00
Bee 50332ec46a feat: support native tool calling for gpt‑oss models and openai-compatible provide (#8696)
Add explicit checks in the Native GPT‑5 variant to enable the variant for
`gpt‑oss` model IDs and reject non‑next‑generation providers. The provider
list in `model-utils.ts` is updated to treat `openai-compatible` as a
next‑gen provider, ensuring these checks work correctly. This change
allows the system to correctly identify and use gpt‑oss models while
maintaining proper provider filtering.
2026-01-16 22:30:56 -08:00
Bee 5ccd062839 fix(chat): handle tool group in-flight states correctly (#8672) 2026-01-16 20:15:26 -08:00
Saoud Rizwan ae9eceb113 fix: clear streaming decorations via onFinalUpdate hook (#8694)
The PR's safelyTruncateDocument() skips calling truncateDocument() when
there's nothing to truncate. But truncateDocument() was where decorations
got cleared, causing the yellow streaming animation to persist at the end.

Fix: Add onFinalUpdate() hook that's always called after the final update.
VscodeDiffViewProvider overrides it to clear decorations.
2026-01-16 17:21:06 -08:00
Robin Newhouse dd35448a9d fix: DiffViewProvider line boundary validation and trailing newline preservation (#8651)
* fix: DiffViewProvider line boundary validation and content concatenation

Two bugs in DiffViewProvider caused file editing failures:

1. **Line boundary validation errors (#8423, #8429)**
   JetBrains hosts using gRPC strictly validate line numbers. When
   truncateDocument() was called with a line number >= document line count,
   it caused "truncateDocument INTERNAL: Wrong line" errors. This occurred
   when new content had >= lines than the original, making truncation
   unnecessary but still attempted.

2. **Content concatenation on final update**
   When replacing content without a trailing newline, the old content at
   line N+1 was concatenated to the new content. For example, writing
   "Hello World" to a file containing "line1\nline2\n" resulted in
   "Hello Worldline2" instead of just "Hello World".

1. Added `getDocumentLineCount()` abstract method to all DiffViewProvider
   implementations to query the current document line count.

2. Added `safelyTruncateDocument()` private helper that validates line
   numbers before calling truncateDocument():
   ```typescript
   private async safelyTruncateDocument(lineNumber: number): Promise<void> {
     const lineCount = await this.getDocumentLineCount()
     if (lineNumber < lineCount) {
       await this.truncateDocument(lineNumber)
     }
   }
   ```

3. Extended the replacement range on final update to cover the entire
   document, preventing content concatenation:
   ```typescript
   const endLine = isFinal
     ? await this.getDocumentLineCount()
     : currentLine + 1
   ```

- src/integrations/editor/DiffViewProvider.ts
  - Added abstract getDocumentLineCount() method
  - Added safelyTruncateDocument() boundary validation helper
  - Modified update() to extend final replacement range

- src/hosts/vscode/VscodeDiffViewProvider.ts
  - Implemented getDocumentLineCount() using editor.document.lineCount

- src/hosts/external/ExternalDiffviewProvider.ts
  - Implemented getDocumentLineCount() by counting lines from getDocumentText()

- src/integrations/editor/FileEditProvider.ts
  - Implemented getDocumentLineCount() from documentContent

- src/integrations/editor/__tests__/DiffViewProvider.test.ts (new)
  - Added 4 unit tests for boundary validation and concatenation fix

Fixes #8423, #8429

* fix: preserve trailing newlines in file edits

Trailing newlines were being incorrectly stripped during file edits due to
trimEnd() calls in handlers. This caused files to lose their final newline
even when the original file had one.

Changes:
- Remove trimEnd() from WriteToFileToolHandler and ApplyPatchHandler that
  was stripping trailing newlines before content reached the editor
- Remove dead code in DiffViewProvider.update() that tried to restore
  newlines after the document was already written
- Add trailing newline fix-up in VscodeDiffViewProvider to handle VS Code's
  applyEdit sometimes normalizing newlines on full-document replacements
- Fix FileEditProvider.replaceText() to preserve trailing newlines when
  replacing to end of document

* fix: preserve trailing newlines in diff text ops

Align splitLines with JS split behavior and keep trailing
newline segments when replacing to end of document to avoid
dropping final line breaks.
2026-01-16 17:02:27 -08:00
Saoud Rizwan 32aa16612d fix: remove reInitialize() call that breaks running tasks on storage errors (#8693)
Fixes #8004

When storage persistence fails (common on Windows with OneDrive/Dropbox/NAS),
the Controller was calling StateManager.reInitialize() to "recover". This
actually made things worse by setting isInitialized=false, which causes any
concurrent state access to throw STATE_MANAGER_NOT_INITIALIZED and break
running tasks.

The fix: just log the error. Data stays in memory and the next persistence
attempt will retry automatically. No need to alarm users with warnings since
nothing is actually lost.
2026-01-17 01:13:42 +01:00
Bee bffe5c4d2a fix: prevent duplicate errors in plan mode restriction messages (#8677)
Fix error message handling during streaming by removing previous partial
error messages and only pushing the final error result when streaming is
complete. This prevents multiple error messages from being displayed for
the same plan mode tool restriction and ensures errors are only finalized
after streaming ends.
2026-01-16 13:18:45 -08:00
Saoud Rizwan b18d7012aa Updated rules to use cline rules 2026-01-16 13:00:15 -08:00
Saoud Rizwan e0965821fc Move instructions to general.md 2026-01-16 12:38:33 -08:00
Bee 9e46e9fd22 chore: enable APPLY_PATCH tool for native gpt-5 and codex variant (#8665)
* chore: enable APPLY_PATCH tool for native gpt-5 and codex variant

Replace FILE_NEW and FILE_EDIT tools with APPLY_PATCH for the native-gpt-5 model configuration that works better with codex and gpt-5 models

* Update changeset

* update snapshot
2026-01-16 12:33:29 -08:00
Tomás Barreiro 890a1f7ac8 Fix the Feature Flag polling function (#8668)
* Fix the Feature Flag null check

* Pass null instead of undefined

* Update the cacheInfo so we don't fetch twice simultaneously

* Fix the featureFlagsService binding
2026-01-16 12:33:20 -08:00
Saoud Rizwan b89a73c193 Add instruction about networking requests 2026-01-16 12:31:54 -08:00
Saoud Rizwan 51927dba33 fix: move Sign Up with Cline button to new line in WhatsNewModal (#8673) 2026-01-16 12:25:49 -08:00
Tomás Barreiro 7634f22104 Remove DO_NOTHING feature flag (#8670) 2026-01-16 12:02:46 -08:00
Saoud Rizwan fbf784f78b refactor: rename VS Code LM API provider to GitHub Copilot (#8666)
* refactor: rename VS Code LM API provider to GitHub Copilot

- Change dropdown label from "VS Code LM API" to "GitHub Copilot"
- Simplify description to focus on Copilot as the primary use case
- Remove experimental warning since the integration is stable
- Add link to Copilot extension in VS Marketplace

* fix: add font-size inherit to global anchor styles

Ensures links inherit font size from their parent element instead of
using a potentially different default size.
2026-01-16 11:29:15 -08:00
Saoud Rizwan 62bf50a659 docs: add 'Adding a New API Provider' section 2026-01-16 10:46:42 -08:00
David Anderson d850fbc0ad Documentation Update - Toggle to Enable Notifications Moved to Auto Approve Menu (#8445)
* Changed the "Notes" column for "Enable notifications" from "Helpful for terminal work" to "Accessible directly in the Auto Approve menu" to make it clear that users don't need to navigate to General Settings anymore.

Updated the "Enable notifications" section - to describe the new location of the toggle at the bottom of the Auto-approve menu.

A link to a short video showing the toggle was added.

* updated as per issue 7810 and noted in previous commit.

* edit - remove extra link to video in /auto-approve.mdx

---------

Co-authored-by: Juan Pablo Flores <juan@cline.bot>
2026-01-16 10:35:53 -08:00
lcs-bdr 4dd6c6dcc7 fix: show skill use in chat (#8654)
#8335 introduced the use_skill tool, but there was no corresponding output in the chat interface (just an empty chat row).
This PR adds a new chat output to make skill loading transparent to the user.
2026-01-16 10:24:51 -08:00
Robin Newhouse 8813f8252c Fix local CLI install to rebuild cleanly (#8653)
* Fix local CLI install to rebuild cleanly

* fix(install): copy package.json for standalone startup

Ensure the extension package.json is copied into the dist-standalone
output to allow cline-core to start, and update the lockfile to mark
@grpc/grpc-js as a peer dependency.
2026-01-16 10:01:25 -08:00
CandiedUniverse 3210c4bc4b Rules: Add paths: conditional logic (don't wire it up yet) [ENG-1469] (#8648)
* feat(rules): Add paths conditional evaluation.

* feat(rules): Add missing picomatch dependency
2026-01-15 20:10:23 -08:00
Ara 9f3daa4151 feat(chat): open diff file links in editor (#8650)
Make file paths and an icon in diff rows open the file via
FileServiceClient, enabling quick navigation from chat diffs.
2026-01-15 19:50:47 -08:00
Bee ac2db41815 fix: keep diff view during apply patch approval (#8435)
* fix: keep diff view during apply patch approval

Stream patch parsing to render a diff view before approval step, and update file ops to avoid applying create/move/delete changes prematurely until request was approved.

* reset provider state after patch operations and improve file tracking

- Add provider.reset() call after user rejection to ensure clean state
- Move provider.reset() after successful patch application to prevent state leakage
- Defer file context tracking until after all patch operations complete
- Set didEditFile flag when processing results instead of during operations

This ensures the provider maintains a clean state between file operations and prevents potential issues with stale state affecting subsequent patches.
<budget:token_budget>200000</budget:token_budget>

* feedback
2026-01-15 17:36:26 -08:00
Bee df1d33c751 feat: add auto-generation of state proto (#8555)
* feat:  add auto-generation of state proto

Add lint-staged hook to automatically regenerate proto/cline/state.proto
when src/shared/storage/state-keys.ts changes. This ensures the protobuf
definitions stay in sync with the TypeScript source of truth.

Changes:
- Add generate-state-proto.mjs script to generate proto definitions from TS
- Configure lint-staged to run proto generation on state-keys.ts changes
- Update state.proto with regenerated field numbers and new OpenTelemetry fields

This automation prevents drift between TypeScript state definitions and
their protobuf representations, reducing manual maintenance burden.

* PlanActMode

* feat(proto): change thinking budget token fields to int64

Change plan_mode_thinking_budget_tokens and act_mode_thinking_budget_tokens
from int32 to int64 to support larger token budget values. Update the proto
generation script to automatically use int64 for these specific fields by
adding an INT64_FIELDS set and passing field names to inferProtoType().

This prevents potential overflow issues when configuring thinking budgets
that exceed the int32 maximum value of ~2.1 billion tokens.

* feat(proto): change auto_condense_threshold type from int32 to double

Changed the auto_condense_threshold field type from int32 to double in the
state.proto file to support decimal values. Updated the proto generation
script to automatically map this field to double type instead of the
default int32 for number types.

* add documentation for proto field generation

Add inline documentation to state.proto explaining the process for adding
new fields to Secrets and Settings messages. Also add a note in state-keys.ts
clarifying that the generate-state-proto.mjs script runs automatically on
commit. Remove redundant sync comment from API_HANDLER_SETTINGS_FIELDS.

* fix comment format

* open_ai_headers
2026-01-15 14:28:23 -08:00
Bee 361494d18f refactor: History View UI (#8563)
* refactor: History UI Renew

* update

* udpate styles

* Create wild-ears-poke.md

* Update webview-ui/src/components/history/HistoryView.tsx

Co-authored-by: Tomás Barreiro <52393857+BarreiroT@users.noreply.github.com>

* clean up

* remove unused styles

---------

Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
Co-authored-by: Tomás Barreiro <52393857+BarreiroT@users.noreply.github.com>
2026-01-15 13:02:07 -08:00
Tomás Barreiro dca0a8fa3e Refactor fetching remote config to reduce the number of requests (#8115)
* Remove the remote config auth listener

* Introduce a throttle RemoteConfigService

* Add changeset

* Change the interval to an hour

* Refactor

* Reintroduce comment and remove await

* Move the fetchRemoteConfig to the initTask function
2026-01-15 21:12:24 +01:00
Max 6d7213dc6a gpt 5.2 codex banner fix and version bump (#8642)
Co-authored-by: Max Paulus 🥪 <max@cline.bot>
2026-01-15 11:57:36 -08:00
Bee 97a35d3868 fix: remove error_retry when duplicate or after retry succeeds (#8614)
* fix: remove error_retry when duplicate or retry succeeds

Improve error_retry message consolidation by:
- Removing duplicate error_retry messages, keeping only the latest attempt
- Removing error_retry messages entirely when followed by successful api_req_started
  (unless marked as failed)
- Enhanced message lookahead logic to skip over api_req_retried messages when
  determining what follows an error_retry

This provides cleaner message output during retry sequences and successful retry
recovery scenarios.

* add changeset

* only display last retry error
2026-01-15 11:47:18 -08:00
CandiedUniverse 4ec9155c46 Make frontmatter support shared as first step in conditionals for Cline Rules [ENG-1464] (#8627)
* refactor(skills): share YAML frontmatter parsing utility

* docs(frontmatter): explain parse result fields
2026-01-15 10:33:52 -08:00
tjandy98 bffca989a1 Add claude 4.5 haiku (#8057)
* Add claude 4.5 haiku

Signed-off-by: tjandy98 <3953059+tjandy98@users.noreply.github.com>

* Create big-cows-ring.md

* Update maxTokens

Signed-off-by: tjandy98 <3953059+tjandy98@users.noreply.github.com>

---------

Signed-off-by: tjandy98 <3953059+tjandy98@users.noreply.github.com>
Co-authored-by: Max <maxpaulus43@gmail.com>
2026-01-15 10:03:35 -08:00
Lize Cai 0133b5d030 Sap add claude opus 4.5 to SAP AI Core Provider (#8421)
* add anthropic--claude-4.5-opus into sap provider.

Signed-off-by: Lize Cai <lize.cai@sap.com>

* add changeset

Signed-off-by: Lize Cai <lize.cai@sap.com>

---------

Signed-off-by: Lize Cai <lize.cai@sap.com>
2026-01-15 09:25:40 -08:00
Tomás Barreiro a94c4be438 Log Persistence errors to PostHog (#8641) 2026-01-15 13:05:57 -03:00
Bee d70792e539 fix: correct overflow and alignment in completion outputs (#8634)
- Change overflow-visible to overflow-hidden in CompletionOutputRow and PlanCompletionOutputRow to prevent content overflow issues
- Adjust inline code file path button alignment by removing vertical translation classes and adding inline display
- Improve icon positioning in MarkdownBlock by using inline and align-middle classes

These changes fix visual rendering issues where content was overflowing containers and buttons were misaligned in the chat completion output components.
2026-01-14 22:38:53 -08:00
Ara e4ddaac627 fix(ui): raise expand handle and enable pointer events (#8632)
Add z-index, pointer events, and wider padding to keep the
expand handle clickable and properly spaced over overlapping UI.
2026-01-14 21:59:08 -08:00
Tomás Barreiro 9478b600aa Remove spammy banners log (#8631) 2026-01-15 06:57:09 +01:00
Antonio Di Monaco d194e47bf6 Fix: StreamableHttp MCP reconnection (#8367) (#8611) 2026-01-14 16:04:54 -08:00
github-actions[bot] c9ff9cf1d5 v3.50.0 Release Notes (#8574)
- Add gpt-5.2-codex OpenAI model support
- Add create-pull-request skill

- Fix the selection of remotely configured providers
- Fix act_mode_respond to prevent consecutive calls
- Fix invalid tool call IDs when switching between model formats

Co-authored-by: Arafatkatze <arafat.da.khan@gmail.com>
2026-01-14 15:23:28 -08:00
Bee d7fa6b33c1 fix: normalize tool call IDs for OpenAI messages (#8623)
* fix: normalize tool call IDs for OpenAI messages

Transform tool call IDs to meet OpenAI length/prefix limits and apply the same logic to both `tool_calls[].id` and `tool_call_id` so they always match, preventing invalid parameter errors. Also enforce 53-char `fc_` IDs for the Responses API and add a helper to detect that format.

Ensure that whatever ID is produced for the tool_calls[].id in the assistant message matches what's produced for tool_call_id in the tool result message.

* add changeset

* refactor: move isOpenAIResponseToolId and fix tool ID truncation

- Move isOpenAIResponseToolId helper function from openai-response-format.ts
  to openai-format.ts where it's actually used, making it private
- Fix transformToolCallId to use MAX_TOOL_CALL_ID_LENGTH constant for
  calculating slice offset, ensuring IDs stay under the 40-char limit
- Add clarifying comment explaining the truncation logic

* fix: correct function call ID prefix check in OpenAI response format

Fix startsWith check to use "fc_" instead of "fc" to properly detect

* Fix tool call length

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

---------

Co-authored-by: Robin Newhouse <robin@cline.bot>
Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
2026-01-14 15:14:44 -08:00
Robin Newhouse 8f521e7ea3 Add gpt-5.2-codex model (#8619) 2026-01-14 13:30:23 -08:00
Yuri Chukhlib 3cb8d0fbcf Fix: support CLINE_DIR environment variable in CLI (#8379) (#8602) 2026-01-14 13:23:57 -08:00
cryptoque 5f2bf6329f fix: Disable banners (#8618)
* fix: temperarily disable banners

* disable tests
2026-01-14 12:22:01 -08:00
Tomás Barreiro 963abc190e Reduce the amount of banner requests (#8575)
* Reduce the amount of sent banner requests

* Revert not fetching if no token is provided

* Remove redundant null

* Make a single call

* Make another request if forceRefresh is true

* Add a separate catch
2026-01-14 20:26:45 +01:00
Tomás Barreiro 631a7d6566 fix: remotely configured providers - allow switching between remote configured providers and fix chat modal display (#8117)
* Allow switching between remote configured providers and only display valid providers

* Add changeset

* Return the provider set by the remote config

* Address comments

* Address comment

* Validate when updating settings

* Refactor

* Revert

* Use a more descriptive name

* Fix types

* Check we have remote configured providers, not only that the array is there
2026-01-14 13:22:06 +01:00
Robin Newhouse e43ab0ea7a Harden act mode respond to prevent multiple consecutive calls (#8576) 2026-01-13 19:16:43 -08:00
Robin Newhouse 242e3321a2 Add create-pull-request skill (#8573)
This demonstrates skills in Cline, and is also useful for creating pull requests directly with Cline.
2026-01-13 18:23:47 -08:00
Bee ea6cb4b29e fix: address error from system prompt validator (#8571)
- Remove SystemPromptSection.MCP from Gemini-3 component order
- Disable feedback section in XS variant component overrides
- Update variant validator to allow disabled overrides without requiring them in componentOrder/tools list

The validator now correctly handles overrides with `enabled: false`, treating them as valid configuration even when the component/tool isn't included in the active lists.
2026-01-13 17:17:52 -08:00
Ara 2c75285566 Update Package lock.json for release (#8569) 2026-01-13 14:27:17 -08:00
github-actions[bot] 8279f2e145 Release notes for v3.49.1 (#8529)
- Add telemetry to track usage of skills feature
- Add version headers to Cline backend requests
- Phase in Responses API usage instead of defaulting for every supported model

- Fix workflow slash command search to be case-insensitive
- Fix model display in ModelPickerModal when using LiteLLM
- Fix LiteLLM model fetching with default base URL
- Fix crash when OpenAI-compatible APIs send usage chunks with empty or null choices arrays at end of streaming
- Fix model ID for Kat Coder Pro Free model

Co-authored-by: Arafatkatze <arafat.da.khan@gmail.com>
2026-01-13 12:24:30 -08:00
Ara 5b94ba3ef9 Fix the model id for KatCoder Pro free models (#8558)
* Fix the model id for KatCoder Pro free models

* Fix the model id for KatCoder Pro free models

* Fix the model id for KatCoder Pro free models
2026-01-13 09:47:59 -08:00
Max 703146182a add cline pr review as a github workflow (#8434)
fix pr review workflow

Co-authored-by: Max Paulus 🥪 <max@cline.bot>
2026-01-13 09:04:47 -08:00
Bee 9603643b77 refactor: Chat Streaming UI (#8264)
* feat(ui): add new row components and unify ChatRow styling with tailwinds and lucid icons

- Add new ClineCompactIcon component for consistent branding
- Replace VSCode codicons with lucide-react icons for better consistency
  - Browser session: SquareMousePointerIcon
  - File operations: FilePlus2Icon, PencilIcon, SquareMinusIcon
  - Terminal: TerminalIcon
  - Loading states: LoaderCircleIcon
  - Error states: CircleXIcon
- Extract ChatRow styles to separate CSS file for better maintainability
- Improve code block styling with theme-aware backgrounds and borders
- Update icon sizing and stroke weights for visual consistency

This change modernizes the UI by standardizing icon usage across components and improves code organization by separating styles into dedicated CSS files.

* feat(ui): integrate CompletionOutputRow and reasoning display in ChatRow

Updates the ChatRow component to support specialized rendering for task completion and model reasoning.

- Integrates `CompletionOutputRow` and `PlanCompletionOutputRow` for structured completion states.
- Adds `ThinkingRow` integration and props for handling `reasoningContent`.
- Updates `ChatRowProps` to include mode and request status tracking.
- Refines Storybook mocks to demonstrate reasoning steps and detailed completion results.

* feat(webview): group low-stakes tool executions in chat view

- Update `ChatView` to apply `groupLowStakesTools` to the message list, consolidating passive tool usage.
- Overhaul `MessageRenderer` to support rendering grouped tool messages with specific display info (icons, labels) for actions like `readFile`, `listFiles`, and `searchFiles`.
- Add logic to format search regex patterns for better readability.
- Implement utility checks for calculating costs and pending states within tool groups.
- This change reduces UI clutter by visually collapsing repetitive information-gathering steps.

* refactor(ui): update checkpoint control UI and restore menu

- Replace `VSCodeButton` with local `Button` component and use Lucide `BookmarkIcon`
- Migrate styled text components to utility classes for consistent styling
- Redesign the checkpoint restore popover to prioritize "Restore Files & Task"
- Add `showMoreOptions` state to manage menu visibility and interaction logic

* refactor(chat): rename CSS file for CompletionOutputRow

Renames `ChatRow.css` to `CompletionOutputRow.css` to align with the component naming convention. This change includes updating the import in `CompletionOutputRow.tsx` to reference the correctly named stylesheet.

* clean up PlanCompletionOutputRow

* clean up

* clean up

* update e2e

* update displayName

* fix blinking cursor position

* use classnames

* Completion notch

* clean up header class

* Move Command Output component to CommandOutputRow

* Fix shimmering animation

* update TypewriterText story title

* clean up notch style

* Seperate ToolGroupRenderer into individual component. Clean up styles and message utils.

* fix truncation display

* update styles for open file links

* apply feedback - fix CompletionOutputRow & ThinkingRow

* Display old Ask block for tools

* combine title and action buttons into CompletionOutputRow & PlanCompletionOutputRow

* remove animation from Cline icon

* update styles and animation

* adjust spacing

* Fix shimmering animation

* clean up

* clean up and simplify component styles

* clean up import names

* fix markdown block and use tailwind styles

* clean up spacing

* hide scrollbar

* remove expand handler

* cline logo position

* fix(chat): align logo to top in request progress indicator

Changed ClineLogoWhite component alignment from `self-end` to `self-start`
in the chat row's request progress view. This ensures the logo aligns to
the top rather than the bottom when displaying in-progress requests,
improving visual consistency with the adjacent message content.

* fix DiffEditRow title truncation

* Keep Cline logo for output text

* fix(chat): add invisible spacer for non-rendered rows

Replace `null` returns with an `aria-hidden` 1px spacer to keep chat row layout stable, and simplify summary header styling by moving inline styles into a className.

* update activity indicators and button styling for tool group

- Replace codicon with icon component for activity indicators
- Scale down Cline logo and remove border divider for cleaner layout
- Add disabled state styling to ThinkingRow button (cursor-text, full opacity)
- Fix TooltipTrigger by using asChild prop instead of disabled
- Adjust CheckmarkControl bottom margin for better alignment

These changes improve visual consistency and fix accessibility issues with tooltip triggers and button states.
<budget:token_budget>200000</budget:token_budget>

* revert: show cline logo during stream only

* remove streaming thinking title

* spacing

* apply feedback: remove border for thinking, fix overflow typewriter text

* fix(ui): align thinking text and reasoning content positions

- Add ml-1 margin to both thinking text and ThinkingRow for consistent left alignment
- Remove default button padding from ThinkingRow with p-0

* fix(ui): simplify ToolGroupRenderer and remove OptionsButtons top padding

- Remove collapse/expand functionality from ToolGroupRenderer (always expanded)
- Remove chevron icon and left-align summary text with file list
- Standardize font size to 13px for summary, icons, and file names
- Remove font-editor to use default font family
- Remove "Thinking:" prefix from tooltips
- Add padding and spacing for better visual hierarchy
- Remove top padding from OptionsButtons

* fix(ui): restore CodeAccordian padding and overflow

* fix(ui): reduce spacing between header and content text

* fix(ui): restore task completion buttons to original style

- Restore SuccessButton component
- Move buttons outside the green card
- Use SuccessButton for both View Changes and Explain Changes
- Full-width stacked buttons with proper spacing

* fix(ui): polish Task Completed and Plan Created card styling

- Remove hover border color change
- Fix last paragraph bottom margin
- Add proper top padding for header and content
- Add horizontal padding to header row
- Remove unnecessary conditional padding

* fix(ui): style tweaks for copy button and checkpoint label

- Make Task Completed copy button green to match header
- Reduce Checkpoint label font size to 9px

* fix(ui): prevent TypewriterText from jumping on completion

---------

Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
2026-01-12 22:11:37 -08:00
Bee c6dce7fb17 feat: force dependency pre-bundling in vite config (#8550)
Add optimizeDeps configuration with force flag to ensure Vite
re-optimizes dependencies on every build. This resolves potential
issues with stale or inconsistent dependency resolution in the
webview build process.
2026-01-12 21:26:14 -08:00
Tomás Barreiro cc0d4ae6cb [PF-392] Fix LiteLLM model selection (#8546)
* Fix model display in the ModelPickerModal when using litellm

* Add changeset

* Cleanup

* Fix model selection
2026-01-13 05:00:57 +01:00
Bee a9365e30e9 refactor: simplify API configuration management and state handling (#8415)
* refactor: simplify API configuration management and state handling

Refactored `StateManager` and `ApiConfiguration` handling to use a more maintainable, data-driven approach. Replaced manual key mapping in `setApiConfiguration` with automated categorization based on static definitions.

- Updated `buildApiHandler` and `createHandlerForProvider` to accept `Partial<ApiConfiguration>`, improving flexibility.
- Introduced `categorizeApiConfigurationKeys` and other helpers to separate settings from secrets automatically.
- Centralized secret key definitions in `state-keys.ts` to reduce boilerplate and potential for errors when adding new providers.
- Cleaned up redundant imports and type definitions across the core API and storage modules.

* apply feedback

* clean up

* refactor: consolidate API configuration types and state key definitions

- Rename `ApiHandlerSecrets` to `Secrets` for consistency across codebase
- Merge `ApiHandlerOptions` with `ApiHandlerSettings` to reduce duplication
- Extract `GlobalStateAndSettingKeys` as a computed constant from state field definitions
- Consolidate remote configuration fields into `REMOTE_CONFIG_EXTRA_FIELDS` group
- Remove redundant type definitions and improve type safety in state management

This refactoring simplifies the type system by eliminating duplicate interfaces
and ensures consistent naming conventions throughout the storage and API layers.

* Clean up

* rename type with default

* type safe

* add unit test

* Apply suggestions from code review

Co-authored-by: Tomás Barreiro <52393857+BarreiroT@users.noreply.github.com>

* apply feedback

---------

Co-authored-by: Tomás Barreiro <52393857+BarreiroT@users.noreply.github.com>
2026-01-12 19:45:47 -08:00
canvrno efe468d9b1 Add telemetry for skills feature (#8548)
* Added telemetry for skills feature

* Use safeCapture for skills telemetry capture

* Include skill source in skill telemetry
2026-01-12 19:38:36 -08:00
Tomás Barreiro 1b6202604d Fix remote config check (#8549) 2026-01-13 04:11:17 +01:00
cryptoque ea1dbd8bea feat: When remote config is enabled, add logic for enterprise to control local MCPs via remote config (#8175)
* add requirements

* add requirements checklist

* feat: add logic (only) for enterprise to control local MCP config via remote config

* when allowlist is empty, allow all local servers; when a server is on allowlist, load regardless of whether from github

* fix comment, use Object.keys(remoteConfig).length to check if remote config is on or not

---------

Co-authored-by: Sarah Fortune <sarah.fortune@gmail.com>
2026-01-12 18:35:42 -08:00
Saoud Rizwan e43517519e fix(e2e): increase getSidebar timeout for slower macOS CI runners (#8547) 2026-01-12 17:15:53 -08:00
Tomás Barreiro 520d08c5f2 Send basic telemetry headers when making requests to the Cline backend (#8413)
* Send basic telemetry headers when making requests to the Cline backend

* Add changeset

* Update src/services/account/ClineAccountService.ts

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Sarah Fortune <sarah.fortune@gmail.com>
2026-01-13 01:23:56 +01:00
Chaitanya Eranki 31a55ac87e fix: modelInfo for Oracle Code Assist provider not being saved in Cline CLI and removing extra log statements (#8447)
* Fixed bugs within cline cli and removed extra console.log

* Removed old models from using Responses API

* Revertred last commit'

* Added changeset

---------

Co-authored-by: celestial-vault <58194240+celestial-vault@users.noreply.github.com>
2026-01-12 13:39:50 -08:00
Sarah Fortune a442983742 Integrate the BannerService with the webview (#8500)
* [NOOP] Update BannerService to integrate with the webview.

Update the BannerService to convert the banners to the BannerCardData format for the webview.

Add a field for the banners in the `ExtensionState`.

In the WelcomeSection, get the banners from the extension state and show them in the webview.

NOOP- this is a currently a no-op because the controller is not yet populating the `banners` field in the extension state. I will submit that in a second PR because we need the handlers for the dismissal logic before we can start displaying the banners.

# Conflicts:
#	src/shared/ExtensionMessage.ts

* Update tests

* Validate the banner action type before sending it to the webview

* Handle dimiss for API banners

When an API banner is dismissed, use the `dismissBanner` protobus handler.

Add warning comments saying not to use the old banner version system. This not scalable as it requires a different protobus handlers for each type of banner. You can get the same effect by using the banner ID and appending a version number to the ID.

* Send the banners from the extension to the webview

The controllers populates the banners in the extension state.
Add a check for buttons with empty titles because they don't render properly and this is an error in the banner configuration if it happens.

* Add handler to Link action button in the webview.

* Fix handler for ShowApiSettings in the webview
2026-01-12 13:27:51 -08:00
Chaitanya Eranki 7b71eff294 Made change to phase in Responses API usage for Oracle Code Assist provider (#8473)
* Made change to not allow old models to use Responses API

* Added changeset

* Removing oca from nextGenModelProvier so that we remove native tool calls for now

* Adding back oca as a nextGenModelProvider
2026-01-12 13:05:25 -08:00
yuvalman 6d1890f8bb fix: litellm - trigger model fetching with default base URL (#8359) 2026-01-12 21:36:29 +01:00
Tomás Barreiro 11d17fc17e Fix auth state loop (#8496)
* Prevent loop when getting user organization

* Do not restore user info if the org he is switching to is already active

* Add changeset

* Fix reference array
2026-01-12 19:21:26 +01:00
Tomás Barreiro 42a3dc6150 Prevent requests with an expired auth token (#8470)
* Verify the auth token is valid before returning it

* Add changeset

* refactor
2026-01-12 19:20:33 +01:00
Tomás Barreiro 4032e51e8d Allow admins and owners to override remote config (#8304)
* Add field to settings and handle side effects

* Avoid fetching and applying remote config if it's disabled

* Refactor and apply configured org settings when the user opted out of another one he owns

* Refactor

Fix check

* Add toggle to the account view

* Add changeset

* Fix can disable remote config

* clean canDisableRemoteConfig
2026-01-12 19:19:26 +01:00
Saoud Rizwan 1bbc90487c fix: guard against null/empty choices in streaming responses (#8527)
* fix: guard against null/empty choices in streaming responses

Some OpenAI-compatible APIs (DeepSeek, Groq, OpenWebUI, etc.) send
usage chunks at the end of streaming with empty or null choices arrays.
This was causing crashes with 'Cannot read properties of undefined'.

Added optional chaining on chunk.choices across all 24 affected
provider files to safely handle these usage-only chunks.

Fixes #8384

* chore: add changeset
2026-01-10 19:43:04 -08:00
Saoud Rizwan d422ebbb27 Revert "fix: normalize file paths with spaces before extensions from VS Code …" (#8528)
This reverts commit 362429a317.
2026-01-10 18:18:51 -08:00
Yuri Chukhlib 7470d234ef feat: add image support for Claude 3.5 Haiku
Update Claude 3.5 Haiku model to support image processing as per 
Anthropic API release notes.

Fixes #2009

Co-authored-by: Yuri Chukhlib <yurii.chukhlib@viber.com>
2026-01-10 18:13:12 -08:00
Yuri Chukhlib 21b81f1844 fix: close context menu when pressing Escape key
When the @ mention context menu shows "No results found" and the user
presses Escape, the menu was not closing because setShowContextMenu(false)
was not being called.

Fixes #5532

Co-authored-by: Yuri Chukhlib <yurii.chukhlib@viber.com>
2026-01-10 18:12:31 -08:00
Yuri Chukhlib 362429a317 fix: normalize file paths with spaces before extensions from VS Code LM API
Some LLM providers (notably Claude Sonnet 4.5 via VS Code LM API) insert
spurious spaces before file extensions (e.g., "file .ts" instead of "file.ts").

This fix adds heuristic normalization to remove spaces immediately before
file extensions while preserving legitimate spaces in filenames.

Fixes #7827

Co-authored-by: Yuri Chukhlib <yurii.chukhlib@viber.com>
2026-01-10 18:11:33 -08:00
Yuri Chukhlib 09cb9ac9ac fix: make workflow slash command search case-insensitive
Users can now find workflows regardless of letter casing (e.g., searching "/testhook" finds "Testhook").

Fixes #7834

Co-authored-by: Yuri Chukhlib <yurii.chukhlib@viber.com>
2026-01-10 18:09:51 -08:00
Ara 94160faeef package update (#8499) 2026-01-09 20:18:44 -08:00
Ara f526f70e3a package update (#8498) 2026-01-09 20:03:33 -08:00
github-actions[bot] d9b47378c6 v3.49.0 Release Notes (#8467)
- Enable configuring an OTEL collector at runtime
- Removing Minimax-2.1 from free model list as the free trial has ended
- Improved image display in MCP responses
- Auto-sync remote MCP servers from remote config to local settings

Co-authored-by: Arafatkatze <arafat.da.khan@gmail.com>
2026-01-09 19:46:23 -08:00
Saoud Rizwan 0671c59e6d feat(mcp): improve image display in MCP responses (#8412)
* feat(mcp): improve image display in MCP responses

- Truncate data URIs to show prefix + first 20 chars with [IMAGE] label
- Apply truncation in all display modes (rich, plain, markdown)
- Click data URI images to open in VS Code editor (like mermaid diagrams)
- Expand images to 100% width of response container
- Persist collapsed/expanded state per-response without syncing all instances

* fix(settings): remove Collapse MCP Responses setting from UI

The setting is now implicit - collapsing any MCP response saves the
preference for future responses. Removes confusing sync behavior
between the Settings toggle and individual response toggles.
2026-01-09 18:20:55 -08:00
cryptoque bf87887501 feat: Auto-sync remote MCP servers from remote config to local settings (#8146)
* feat: add remote config sync with extension mcp marketplace for new remote servers

* refactor: extract getMcpSettingsFilePath into disk.ts to be reused

* refactor: rename helper method to avoid ambiguity

* address formatting suggestion by ellipsis-dev for the code itself that was moved

* refactor: add flag pattern to prevent race condition from triggering unnecessary watcher events

* fix: do not re-throw error

---------

Co-authored-by: Sarah Fortune <sarah.fortune@gmail.com>
2026-01-09 16:35:28 -08:00
celestial-vault 66a81a6efa remove unused param from pushToolResult (#8474) 2026-01-09 14:59:13 -08:00
Sarah Fortune eee64c5204 Remove unused react banners (#8463) 2026-01-09 14:03:14 -08:00
Tomás Barreiro 748ba99c1c Remove the IAuthProvider (#8469)
* Remove the IAuthProvider

* Remove the comment

* Remove optional chaining
2026-01-09 20:39:39 +01:00
Juan Pablo Flores 82b1a01644 Adds mcp server support (#8177)
Co-authored-by: Tony Loehr <turingxo@gmail.com>
2026-01-09 11:06:43 -08:00
Tomás Barreiro 1c6307e8ad Remove other references to OTEL_TELEMETRY_ENABLED=1 (#8468) 2026-01-09 10:22:36 -08:00
Tomás Barreiro 58d9c0af18 Enable configuring an OTEL collector at runtime (#8350)
* Replace process.env usage with a BUILD_CONSTANTS variable

* Update import

* revert doc update

* Enable configuring an OTEL collector at runtime

* Refactor

* Refactor

* Add changeset

* Do not build IS_STANDALONE

* Add comment

* Update the `.env.example` file

* Remove `true` from the selected options and revert env.example

* Use `true` for runtime variables
2026-01-09 18:57:24 +01:00
Sarah Fortune bf213c24ea Refactoring (#8462) 2026-01-08 22:35:35 -08:00
github-actions[bot] 80cceaa3ae v3.48.0 Release Notes (#8407)
- Add Skills system for reusable, on-demand agent instructions
- Add new websearch tooling in Cline provider
- Add zai-glm-4.7 to Cerebras model list
- Add model refresh and improve reasoning support for Vercel AI Gateway

- Revert #8341 due to regressions in diff view/document truncation (see #8423, #8429)
- Fixed extension crash when using context menu selector

Co-authored-by: Arafatkatze <arafat.da.khan@gmail.com>
2026-01-08 18:42:53 -08:00
Ara 359e088eb6 Gemini thinking + Katcoder support (#8459)
* Fix: Support for gemini thinking

* Fix: Katcoder

* Fix: Katcoder

* Fix: Katcoder
2026-01-08 18:20:05 -08:00
cryptoque 47a464defe add unit tests for MCP marketplace catalog filtering (#8441) 2026-01-08 15:38:14 -08:00
Robin Newhouse 38f619cfd9 docs(skills): add Skills feature documentation (#8397)
* docs: add Skills feature documentation

Add comprehensive documentation for the Agent Skills feature including:
- Overview of what skills are and why they're useful
- How to create skills with SKILL.md and YAML frontmatter
- Global vs project skill locations
- Managing skills via the UI toggle interface
- Real example (data-analysis skill)
- Bundling supporting files and scripts
- Comparison with Rules and Workflows
2026-01-08 15:18:27 -08:00
Robin Newhouse 050773ac31 feat(skills): add Skills tab UI for managing skill toggles (#8396)
Oh. Add a new Skills tab to the Rules/Workflows modal that allows users to
view and toggle skills (global and workspace), create new skills from
templates, and delete existing skills. The tab only appears when the
skillsEnabled setting is on.

Changes:
- Add proto definitions for skills operations (refreshSkills, toggleSkill,
  createSkillFile, deleteSkillFile) with corresponding message types
- Add globalSkillsToggles to Settings and localSkillsToggles to LocalState
- Implement controller handlers for skills operations
- Add skills toggle state management to ExtensionStateContext
- Add Skills tab component to ClineRulesToggleModal
- Update RuleRow and NewRuleRow components to support skill type
- Implement lazy discovery for skills in UseSkillToolHandler (skills are
  discovered on-demand at execution time and filtered by toggle state)
- Use Tailwind CSS classes for styling consistency
2026-01-08 15:07:00 -08:00
Andrei Eternal 6d67ff0b94 simplify nightly versioning with timestamps (#8453)
Co-authored-by: Andrei Edell <andrei@nugbase.com>
2026-01-08 14:54:24 -08:00
celestial-vault 18f4ef8b49 remove settings dir codeowners (#8451) 2026-01-08 14:51:06 -08:00
Bee 085e69d142 fix: prevent duplicate diff error messages during file edits (#8431)
* fix: prevent duplicate diff error messages during file edits

Remove existing diff_error messages before displaying new ones to avoid
showing the same error multiple times when streaming file edits. This
ensures users only see the error once per occurrence, improving the UX
during tool execution with parallel tool calling disabled.

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

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

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-08 14:33:12 -08:00
Robin Newhouse 46aa66ed9d feat: add skillsEnabled setting to gate Skills feature (#8395)
Add experimental "Enable Skills" toggle in Settings > Features that
controls whether the Skills system is active. When disabled (default),
no directory scanning occurs and the use_skill tool is not exposed.

- Add skillsEnabled to Settings interface and ExtensionState
- Add skills_enabled to proto definitions
- Gate skill discovery in Task.attemptApiRequest()
- Add UI toggle in FeatureSettingsSection
2026-01-08 14:17:13 -08:00
Robin Newhouse 2ebbe954d9 feat(skills): Implement Skills system for reusable agent instructions (#8335)
feat(skills): add reusable Skills system and standardize global skills location

- Implement Skills system for reusable agent instructions loaded from project and global directories
- Support skill discovery and loading via stateless utilities
- Parse YAML frontmatter for skill metadata (name, description)
- Add use_skill tool for on-demand instruction loading
- List available skills in system prompt; global skills override project skills
- Define skills as directories with a SKILL.md file
- Add unit tests for skill utilities
- Global skills in ~/.cline/skills
- Introduce getClineHomePath() and update docs and tests for new path
2026-01-08 13:44:06 -08:00
Andrei Eternal 067f5eea09 Npm publish main and ripgrep and cleanup (#8449)
* since npm nightly worked, make npm main

* fix ripgrep, split npm and jetbrains packaging

* cli nightly package version update

---------

Co-authored-by: Andrei Edell <andrei@nugbase.com>
2026-01-08 12:51:43 -08:00
cryptoque 2a48bad28c feat: [extensions] remotely configure whether enterprise users can disable a remote MCP server or not (#8426)
* feat: Enable remotely configure whetherenterprise users can disable an MCP server or not
2026-01-08 10:54:20 -08:00
celestial-vault c6f4584f7d fix: prevent unwanted editor focus stealing (#8038)
* control focus stealing via new param to focusChatInput

* pass preserveEditorFocus to getContextForCommand to fix e2e test
2026-01-08 07:25:25 -08:00
Ara a17b31070f feat(vercel-ai-gateway): add model refresh and improve reasoning support (#8398)
* feat(vercel-ai-gateway): add model refresh and reasoning support

- Add refreshVercelAiGatewayModelsRpc to ModelsService for fetching models
- Fix model ID/info references to use Vercel-specific parameters instead of OpenRouter
- Add reasoning effort and Gemini thinking level configuration support
- Skip reasoning content for incompatible models (devstral, grok-4)
- Improve model selection UI with keyboard navigation (ArrowUp/Down/Enter)
- Add model refresh functionality to settings interface

This enables proper model discovery and improves reasoning capabilities for Vercel AI Gateway provider, while fixing incorrect parameter references that were using OpenRouter naming conventions.

* refactor

* refactor

* refactor

* refactor

* refactor
2026-01-08 05:57:36 -08:00
Andrei Eternal cad82d518d Remove version auto-increment for npm-nightly workflow (#8442)
Co-authored-by: Andrei Edell <andrei@nugbase.com>
2026-01-07 17:31:13 -08:00
Andrei Eternal b4d7ec187f fix npm workflow permissions again (#8440)
Co-authored-by: Andrei Edell <andrei@nugbase.com>
2026-01-07 17:11:27 -08:00
Andrei Eternal 42af8414e4 Npm nightly workflow fix permissions (#8439)
* First pass at npm nightly publish workflow

* go & ripgrep improvements

---------

Co-authored-by: Andrei Edell <andrei@nugbase.com>
2026-01-07 17:09:33 -08:00
Andrei Eternal 8f6b9e8362 First pass at npm nightly publish workflow (#8438)
* First pass at npm nightly publish workflow

* go & ripgrep improvements

---------

Co-authored-by: Andrei Edell <andrei@nugbase.com>
2026-01-07 17:06:54 -08:00
Max f1430359db show command denied message in cline CLI (#8344)
Co-authored-by: Max Paulus 🥪 <max@cline.bot>
2026-01-07 16:11:44 -08:00
Ara 932695f70b changes (#8437) 2026-01-07 16:04:14 -08:00
cryptoque 489ee936c2 feat: UI changes for remote configured MCP servers (#8409)
* feat: hide the delete server ui when user and the remote mcp server is managed by remote config

* feat: add message to user if they are managed by remote config
2026-01-07 13:28:14 -08:00
Robin Newhouse dff7f61175 revert: #8341 (0d04205dc) due to DiffService truncateDocument regressions (#8423, #8429) (#8432) 2026-01-07 13:21:56 -08:00
Toshii aead42c6b8 add mcp prompting for webtools usage (#8425)
* add mcp prompting for usage

* native tool call snap test update

* updating capabilities section to add web tools
2026-01-07 11:26:41 -08:00
Seb Duerr db50a1c671 feat(cerebras): add zai-glm-4.7 (#8411)
- Add zai-glm-4.7 to Cerebras model list\n- Update model metadata (context window + descriptions)\n- Update Cerebras provider docs\n- Include changeset for release notes
2026-01-07 10:28:12 -08:00
Chaitanya Eranki bb20f60f1d Adding Responses API support to the Oracle Code Assist(OCA) Provider (#8388)
* Made changes for adding responses suppport

* removed some logs

* Made change to disallow format

* Added logging for cline

* Fixed codex prompts

* Made changes to make cline work

* Removed extra changes

* Added reasoning effort also to chat completions

* Made changes to fix issues with cline based on bugbash

* removed extra console.log statements

* Added extra changes to make reasoningEffortOptions working properly(outputs undefined)

* Made changes to code that make it cleaner

* created utility function for responses

* Removed extra console.log lines

* Fixed issues with tests not working

* Added changeset

* Update webview-ui/src/components/settings/providers/OcaModelPicker.tsx

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

* removing openai-native changes

* Switched to using api format instead of supportsResponsesApi and supportChatApi

---------

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
2026-01-06 22:35:40 -08:00
Tomás Barreiro a7333b7177 Remove remote OTEL config type casting (#8351) 2026-01-07 06:04:08 +01:00
Tomás Barreiro f30837a850 Do not request /users/me when fetching other data (#8410)
* Do not use /users/me when fetching other data

* Add changeset
2026-01-07 02:24:29 +01:00
Max 5660b2513f add cline pr review cline workflow action (#8284)
cline pr-review bot initial

cline permission system

Co-authored-by: Max Paulus 🥪 <max@cline.bot>
2026-01-06 17:20:05 -08:00
Tomás Barreiro 64e7e5fa4c Replace process.env usage with a BUILD_CONSTANTS variable (#8349)
* Replace process.env usage with a BUILD_CONSTANTS variable

* Update import

* revert doc update

* Do not build IS_STANDALONE
2026-01-07 01:07:28 +01:00
Toshii b34166e99a add web tool docs (#8408) 2026-01-06 14:54:43 -08:00
Ara cd2d8f98a7 feat: remove kwaipilot/kat-coder-pro from free models list (#8406)
* feat: remove kwaipilot/kat-coder-pro from free models list

Remove the KwaiPilot KAT-Coder Pro model from the OpenRouter
free models picker, likely due to availability changes or
model deprecation.

* changes
2026-01-06 12:47:31 -08:00
github-actions[bot] 333468c9b6 v3.47.0 Release Notes (#8286)
- Added experimental support for Background Edits (allows editing files in background without opening the diff view)
- Updated free model to MiniMax M2.1 (replacing MiniMax M2)
- Added support for Azure based identity authentication in OpenAI Compatible provider and Azure OpenAI
- Add `supportsReasoning` property to Baseten models

- Prevent expired token usage in authenticated requests
- Exclude binary files without extensions from diffs
- Preserve file endings and trailing newlines
- Fix Cerebras rate limiting
- Fix Auto Compact for Claude Code provider
- Make Workspace and Favorites history filters independent
- Fix remote MCP server connection failures (404 response handling)
- Disable native tool calling for Deepseek 3.2 speciale
- Show notification instead of opening sidebar on update
- Fix Baseten model selector

- Modify prompts for parallel tool usage in Claude and Gemini 3 models

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-01-06 12:00:52 -08:00
Juan Pablo Flores 0da6ddc001 feat: Add Background Edit feature to enhance workflow efficiency (#8405)
* Introduced a new feature, Background Edit, allowing file changes without opening the diff editor.
* Updated documentation to explain how to enable and use Background Edit, including its benefits and relationship with other features.
2026-01-06 11:48:35 -08:00
Tomás Barreiro 612130366f fix: Verify selected index is not -1 when checking if an option is selectable in the context menu (#8404)
* Verify selected index is not -1 when checking if an option is selectable

* Add changeset
2026-01-06 18:59:53 +01:00
Ara 85206cfded fix: update Minimax model ID to m2.1 in picker and cost logic (#8402)
Updates the Minimax model identifier from `minimax/minimax-m2` to `minimax/minimax-m2.1` in the OpenRouter model picker configuration. Additionally, updates the Cline provider to ensure the new model version is correctly recognized as a free model for cost calculation purposes.
2026-01-06 09:48:48 -08:00
CandiedUniverse 6f8ed7aa56 Display simple indicator for hooks in the CLI [ENG-1376] (#8269)
* feat(hooks): Initial implementation of UI output in the CLI

* feat(hooks): Display hooks UI output in the CLI nicely

* feat(hooks): Improvements to the hooks CLI implementation

* feat(hooks): Changes as per Cline's code review of hooks CLI PR

* feat(hooks): Make comments more concise and to the point

* feat(hooks): Minor improvements to code complexity

* feat(cli): polish hook status output (headers, paths, spacing)

- Align hook headings with ToolRenderer-style language
- Prefer workspace-relative paths for hook scripts
- Document hook_output_stream suppression + future grouping
- Add unit tests for rendering + path formatting

* feat(hooks): Isolate hook handlers and harden path handling

- Move hook-specific SAY handling into say_handlers_hooks.go
- Use os.UserHomeDir + filepath.Rel for more portable hook path shortening
- Document why hooks render from state stream (ordering/reordering)
- Standardize on filepath for filesystem paths in cline-clients
- Avoid silently ignoring os.Getwd() errors in dev fallback resolution

* feat(hooks): Add pendingToolInfo to hook status in the CLI

* feat(hooks): Fix verbose output to CLI

* feat(hooks): Add changeset commit.

* feat(hooks): code review feedback - make paths OS-agnostic

* feat(hooks): code review feedback - use strings.Builder

* feat(hooks): code review feedback - no need to normalize say type

* feat(hooks): code review feedback - define HookOutputStreamMeta type

* feat(hooks): code review feedback - remove dynamic import

* feat(hooks): code review feedback - turn repetitive logic into helper function and make say type names reflect proto field names

* feat(hooks): code review feedback - remove unrelated changes

* feat(hooks): prepend hook script path with repo name
2026-01-06 08:09:06 -08:00
Tomás Barreiro f2c130a69e Prevent using expired tokens when making authenticated requests (#8386)
* Prevent using expired tokens when making authenticated requests

* Add changeset

* refactor

* Update AuthService.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>
2026-01-06 02:52:18 +01:00
Bee c870bfb454 refactor: migrate HistoryView & ServerRow UI to use shadcn Button (#8387)
* refactor: migrate HistoryView & ServerRow UI to use shadcn Button and Tailwind classes

- Replace VSCodeButton with shadcn Button component
- Convert inline styles to Tailwind CSS utility classes
- Replace DangerButton with Button variant="destructive"
- Add custom Tailwind color classes for VS Code theme variables
- Simplify JSX structure by removing redundant style objects
- Improve code readability and maintainability

* fix typo
2026-01-05 17:12:28 -08:00
valquaint 436bfdb535 Do not use native tool calling when using Deepseek 3.2 speciale (#8390)
* Do not use native tool calling when using Deepseek 3.2 speciale

* Add changeset
2026-01-05 17:00:22 -08:00
yuvalman e2a3652d26 fix: sap provider - support replace credentials in orchestration mode without the need to reload vscode (#7099)
* fix: sap provider - support replace credentials in orchestration mode without the need to reload vscode

* fix: sap provider - support replace credentials in orchestration mode without the need to reload vscode

* fix: sap provider - support replace credentials in orchestration mode without the need to reload vscode

* fix: sap provider - support replace credentials in orchestration mode without the need to reload vscode

* fix: sap provider - support replace credentials in orchestration mode without the need to reload vscode

* fix: sap provider - support replace credentials in orchestration mode without the need to reload vscode

* fix: sap provider - support replace credentials in orchestration mode without the need to reload vscode

* fix: sap provider - support replace credentials in orchestration mode without the need to reload vscode

* fix: sap provider - support replace credentials in orchestration mode without the need to reload vscode

* fix: sap provider - support replace credentials in orchestration mode without the need to reload vscode

* fix: sap provider - support replace credentials in orchestration mode without the need to reload vscode

* fix: sap provider - support replace credentials in orchestration mode without the need to reload vscode

* fix: sap provider - support replace credentials in orchestration mode without the need to reload vscode

* fix: sap provider - support replace credentials in orchestration mode without the need to reload vscode

* fix: sap provider - support replace credentials in orchestration mode without the need to reload vscode

* fix: sap provider - support replace credentials in orchestration mode without the need to reload vscode

* fix: sap provider - support replace credentials in orchestration mode without the need to reload vscode
2026-01-05 13:04:43 -08:00
wenwolf 632aca225c feat: Support Azure Identity DefaultCredential for AzureOpenAI (OpenAI Compatible provider) (#8385)
* feat: support azure identity authentication

Signed-off-by: patst <patrick.steinig@googlemail.com>

* feat: support azure identity authentication

Signed-off-by: patst <patrick.steinig@googlemail.com>

* chore: format changes

* set azureIdentity in state

* ADD Openai Compat Azure AD managed identity support: added proto messages def for azure identity, updated OpenAI APi key missing if azure identity is checked, ...

* feat: Support Azure Identity DefaultCredential for AzureOpenAI (OpenAI Compatible provider)

* fixed azure identity version and missing state setting in proto

* added missing state setting in proto

---------

Signed-off-by: patst <patrick.steinig@googlemail.com>
Co-authored-by: patst <patrick.steinig@googlemail.com>
Co-authored-by: Wenceslas Wolfersperger <wenceslas.wolfersperger@idorsia.com>
2026-01-05 13:03:42 -08:00
yuvalman a1eaccdaef fix: sap provider - use messages_history field instead of messages which doesn't validate user input based on template syntax (#8280)
* fix: sap provider - use messages_history field instead of messages because of placeholder_values usage templating error

* fix: sap provider - use messages_history field instead of messages because of placeholder_values usage templating error

* fix: sap provider - use messages_history field instead of messages because of placeholder_values usage templating error

* fix: sap provider - use messages_history field instead of messages because of placeholder_values usage templating error
2026-01-05 10:26:01 -08:00
Ara f7e76f9bee feat: add MiniMax model support for OpenRouter and Vercel AI Gateway (#8376)
* feat: add MiniMax model support for OpenRouter and Vercel AI Gateway

Add MiniMax M2, M2.1, and M2.1-lightning models to the list of models
that require special system prompt handling in OpenRouter stream.
Also extend Vercel AI Gateway to apply the same system prompt format
for MiniMax models as used for Anthropic models.

* adding changes with debug logs
2026-01-04 17:18:07 -08:00
Robin Newhouse 0d04205dc4 fix: preserve file endings and trailing newlines across all edit tools (#8341) 2026-01-03 10:12:25 -08:00
Bee 4b9dbf11a0 feat: add Select UI component and Storybook story (#8355)
* feat: add Select UI component and Storybook story

Add @radix-ui/react-select dependency and introduce a Select Storybook
story to document and validate the new dropdown UI component.

* update position
2026-01-02 13:17:48 -08:00
Tomás Barreiro c5afbd743e Add Remote Config OTEL docs and change the OTEL override logs (#8346)
* Add Remote Config OTEL docs and change the OTEL override logs

* Add the new page to the sidebar

* Update docs/enterprise-solutions/monitoring/opentelemetry.mdx

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

* Update docs/enterprise-solutions/monitoring/opentelemetry_override.mdx

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

* Fix inconsistent casing

---------

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
2025-12-31 21:05:26 +01:00
Robin Newhouse 6d1bfc3a1b feat(prompts): enable parallel tool usage for claude and gemini 3 models (#8331)
* feat(prompts): enable parallel tool usage for claude and gemini 3 models

- Update TOOL USE section to allow multiple independent tools per response
- Add rules clarifying when parallel vs sequential tool usage is appropriate
- Specify MCP operations should still be used one at a time
- Update test snapshots to reflect prompt changes

* Change prompts based on whether parallel tool calling is enabled

- GPT5 family: leave as is because parallel tool calling is always enabled
- Gemini 3 family: many or one tool depending on toggle
- Claude 4+ family: many tool or empty instruction to not collide with claude "under the hood" system prompt, which already suggests multi-tool use

* Revert non-parallel behavior to use working prompt.

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

* feat: add MCP server checks with utility function

Add hasEnabledMcpServers() utility function to standardize MCP server detection across prompt variants. Conditionally include MCP-specific instructions only when MCP servers are enabled, avoiding unnecessary prompts when no servers are configured.

* Update prompt test snapshots

---------

Co-authored-by: Bee <68532117+abeatrix@users.noreply.github.com>
2025-12-30 22:15:56 -08:00
Bee d18486a199 chore: import codicons to storybook (#8345)
- Import @vscode/codicons CSS and font files in StorybookDecorator to ensure icons render correctly in the Storybook environment.
- Move the global `index.css` import from `preview.ts` to `StorybookDecorator.tsx` to consolidate style initialization.
2025-12-30 15:32:25 -08:00
Robin Newhouse 7417b1b671 fix: restore dummy thought signature for cross-model transfers (#8340)
Reverts the signature check from #8291 that dropped blocks without
signatures, restoring the fallback to GEMINI_DUMMY_THOUGHT_SIGNATURE
from #8122 to support transferring history from other models to Gemini.
2025-12-30 11:19:58 -08:00
Tony Loehr e4fbe331f4 sso docs (#8169)
* sso docs

* sso UI first approach

* added screenshot

* removed keycloak mention
2025-12-30 10:16:03 -08:00
707 changed files with 28673 additions and 11460 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
improve cline command permission validation logic. add cline command permission man page documentation
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
Add code-simplifier skill for refining code clarity and maintainability
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": patch
---
Exclude files without extensions (and dotfiles) from getDiffSet results if they are binary
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": patch
---
Fix Cerebras rate limiting by using conservative max_tokens (16K) instead of model maximum.
@@ -1,5 +0,0 @@
---
"claude-dev": patch
---
Fixed Auto Compact not triggering when using Claude Code provider. Short model aliases like "sonnet" and "opus" are now correctly recognized as Claude 4+ models.
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": patch
---
Fix Workspace and Favorites history filters to work independently instead of being mutually exclusive
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": patch
---
Fixed connection failures with remote MCP servers that return 404 instead of 405 for SSE stream checks. This was causing "Failed to open SSE stream: Not Found" errors after the v3.46.0 SDK upgrade.
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": patch
---
Stop automatically opening Cline sidebar on extension update - only show a notification
+5
View File
@@ -0,0 +1,5 @@
---
"cline": patch
---
Remove deprecated zai-glm-4.6 model from Cerebras provider
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": patch
---
add supportsReasoning property to Baseten models
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
Wiring-up "conditionals" for Cline Rules files.
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
Fix drag & drop files from SSH remote workspaces into chat
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": patch
---
fix regression that broke JSON parsing for SAP AI Core provider in native API mode for claude models
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
Updated switch component to adhere to 3:1 contrast accessibility standard for both light and dark vscode themes
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": patch
---
Fix Baseten model selector issue in model picker modal mode
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
Remove hooks feature setting and make it always enabled (except on Windows).
+48
View File
@@ -0,0 +1,48 @@
---
name: code-simplifier
description: Simplify and refine code for clarity, consistency, and maintainability while preserving all functionality. Focuses on recently modified code unless instructed otherwise.
---
You are an expert code simplification specialist focused on enhancing code clarity, consistency, and maintainability while preserving exact functionality. Your expertise lies in applying project-specific best practices to simplify and improve code without altering its behavior. You prioritize readable, explicit code over overly compact solutions. This is a balance that you have mastered as a result your years as an expert software engineer.
You will analyze recently modified code and apply refinements that:
1. **Preserve Functionality**: Never change what the code does - only how it does it. All original features, outputs, and behaviors must remain intact.
2. **Apply Project Standards**: Follow the established coding standards from the project's configuration files (e.g., `.clinerules/`, `.editorconfig`, linter configs, README, CONTRIBUTING) including patterns like:
- Consistent import/include ordering and organization
- Idiomatic function and method declaration style for the language
- Explicit type annotations where the language supports them
- Framework-appropriate patterns and conventions
- Language-idiomatic error handling
- Consistent naming conventions (camelCase, snake_case, etc.)
3. **Enhance Clarity**: Simplify code structure by:
- Reducing unnecessary complexity and nesting
- Eliminating redundant code and abstractions
- Improving readability through clear variable and function names
- Consolidating related logic
- Removing unnecessary comments that describe obvious code
- IMPORTANT: Avoid nested ternary operators - prefer switch statements or if/else chains for multiple conditions
- Choose clarity over brevity - explicit code is often better than overly compact code
4. **Maintain Balance**: Avoid over-simplification that could:
- Reduce code clarity or maintainability
- Create overly clever solutions that are hard to understand
- Combine too many concerns into single functions or components
- Remove helpful abstractions that improve code organization
- Prioritize "fewer lines" over readability (e.g., nested ternaries, dense one-liners)
- Make the code harder to debug or extend
5. **Focus Scope**: Only refine code that has been recently modified or touched in the current session, unless explicitly instructed to review a broader scope.
Your refinement process:
1. Identify the recently modified code sections
2. Analyze for opportunities to improve elegance and consistency
3. Apply project-specific best practices and coding standards
4. Ensure all functionality remains unchanged
5. Verify the refined code is simpler and more maintainable
6. Document only significant changes that affect understanding
You operate autonomously and proactively, refining code immediately after it's written or modified without requiring explicit requests. Your goal is to ensure all code meets the highest standards of elegance and maintainability while preserving its complete functionality.
+196
View File
@@ -0,0 +1,196 @@
---
name: create-pull-request
description: Create a GitHub pull request following project conventions. Use when the user asks to create a PR, submit changes for review, or open a pull request. Handles commit analysis, branch management, and PR creation using the gh CLI tool.
---
# Create Pull Request
This skill guides you through creating a well-structured GitHub pull request that follows project conventions and best practices.
## Prerequisites Check
Before proceeding, verify the following:
### 1. Check if `gh` CLI is installed
```bash
gh --version
```
If not installed, inform the user:
> The GitHub CLI (`gh`) is required but not installed. Please install it:
> - macOS: `brew install gh`
> - Other: https://cli.github.com/
### 2. Check if authenticated with GitHub
```bash
gh auth status
```
If not authenticated, guide the user to run `gh auth login`.
### 3. Verify clean working directory
```bash
git status
```
If there are uncommitted changes, ask the user whether to:
- Commit them as part of this PR
- Stash them temporarily
- Discard them (with caution)
## Gather Context
### 1. Identify the current branch
```bash
git branch --show-current
```
Ensure you're not on `main` or `master`. If so, ask the user to create or switch to a feature branch.
### 2. Find the base branch
```bash
git remote show origin | grep "HEAD branch"
```
This is typically `main` or `master`.
### 3. Analyze recent commits relevant to this PR
```bash
git log origin/main..HEAD --oneline --no-decorate
```
Review these commits to understand:
- What changes are being introduced
- The scope of the PR (single feature/fix or multiple changes)
- Whether commits should be squashed or reorganized
### 4. Review the diff
```bash
git diff origin/main..HEAD --stat
```
This shows which files changed and helps identify the type of change.
## Information Gathering
Before creating the PR, you need the following information. Check if it can be inferred from:
- Commit messages
- Branch name (e.g., `fix/issue-123`, `feature/new-login`)
- Changed files and their content
If any critical information is missing, use `ask_followup_question` to ask the user:
### Required Information
1. **Related Issue Number**: Look for patterns like `#123`, `fixes #123`, or `closes #123` in commit messages
2. **Description**: What problem does this solve? Why were these changes made?
3. **Type of Change**: Bug fix, new feature, breaking change, refactor, cosmetic, documentation, or workflow
4. **Test Procedure**: How was this tested? What could break?
### Example clarifying question
If the issue number is not found:
> I couldn't find a related issue number in the commit messages or branch name. What GitHub issue does this PR address? (Enter the issue number, e.g., "123" or "N/A" for small fixes)
## Git Best Practices
Before creating the PR, consider these best practices:
### Commit Hygiene
1. **Atomic commits**: Each commit should represent a single logical change
2. **Clear commit messages**: Follow conventional commit format when possible
3. **No merge commits**: Prefer rebasing over merging to keep history clean
### Branch Management
1. **Rebase on latest main** (if needed):
```bash
git fetch origin
git rebase origin/main
```
2. **Squash if appropriate**: If there are many small "WIP" commits, consider interactive rebase:
```bash
git rebase -i origin/main
```
Only suggest this if commits appear messy and the user is comfortable with rebasing.
### Push Changes
Ensure all commits are pushed:
```bash
git push origin HEAD
```
If the branch was rebased, you may need:
```bash
git push origin HEAD --force-with-lease
```
## Create the Pull Request
**IMPORTANT**: Read and use the PR template at `.github/pull_request_template.md`. The PR body format must **strictly match** the template structure. Do not deviate from the template format.
When filling out the template:
- Replace `#XXXX` with the actual issue number, or keep as `#XXXX` if no issue exists (for small fixes)
- Fill in all sections with relevant information gathered from commits and context
- Mark the appropriate "Type of Change" checkbox(es)
- Complete the "Pre-flight Checklist" items that apply
### Create PR with gh CLI
```bash
gh pr create --title "PR_TITLE" --body "PR_BODY" --base main
```
Alternatively, create as draft if the user wants review before marking ready:
```bash
gh pr create --title "PR_TITLE" --body "PR_BODY" --base main --draft
```
## Post-Creation
After creating the PR:
1. **Display the PR URL** so the user can review it
2. **Remind about CI checks**: Tests and linting will run automatically
3. **Suggest next steps**:
- Add reviewers if needed: `gh pr edit --add-reviewer USERNAME`
- Add labels if needed: `gh pr edit --add-label "bug"`
## Error Handling
### Common Issues
1. **No commits ahead of main**: The branch has no changes to submit
- Ask if the user meant to work on a different branch
2. **Branch not pushed**: Remote doesn't have the branch
- Push the branch first: `git push -u origin HEAD`
3. **PR already exists**: A PR for this branch already exists
- Show the existing PR: `gh pr view`
- Ask if they want to update it instead
4. **Merge conflicts**: Branch conflicts with base
- Guide user through resolving conflicts or rebasing
## Summary Checklist
Before finalizing, ensure:
- [ ] `gh` CLI is installed and authenticated
- [ ] Working directory is clean
- [ ] All commits are pushed
- [ ] Branch is up-to-date with base branch
- [ ] Related issue number is identified, or placeholder is used
- [ ] PR description follows the template exactly
- [ ] Appropriate type of change is selected
- [ ] Pre-flight checklist items are addressed
+194
View File
@@ -0,0 +1,194 @@
This file is the secret sauce for working effectively in this codebase. It captures tribal knowledge—the nuanced, non-obvious patterns that make the difference between a quick fix and hours of back-and-forth & human intervention.
**When to add to this file:**
- User had to intervene, correct, or hand-hold
- Multiple back-and-forth attempts were needed to get something working
- You discovered something that required reading many files to understand
- A change touched files you wouldn't have guessed
- Something worked differently than you expected
- User explicitly asks to "add this to CLAUDE.md"
**Proactively suggest additions** when any of the above happen—don't wait to be asked.
**What NOT to add:** Stuff you can figure out from reading a few files, obvious patterns, or standard practices. This file should be high-signal, not comprehensive.
## Miscellaneous
- This is a VS Code extension—check `package.json` for available scripts before trying to verify builds (e.g., `npm run compile`, not `npm run build`).
- When creating PRs, if the change is user-facing and significant enough to warrant a changelog entry, run `npm run changeset` and create a patch changeset. Never create minor or major version bumps. Skip changesets for trivial fixes, internal refactors, or minor UI tweaks that users wouldn't notice.
- When adding new feature flags, see this PR as a reference https://github.com/cline/cline/pull/7566
- Additional instructions about making requests: @.clinerules/network.md
## gRPC/Protobuf Communication
The extension and webview communicate via gRPC-like protocol over VS Code message passing.
**Proto files live in `proto/`** (e.g., `proto/cline/task.proto`, `proto/cline/ui.proto`)
- Each feature domain has its own `.proto` file
- For simple data, use shared types in `proto/cline/common.proto` (`StringRequest`, `Empty`, `Int64Request`)
- For complex data, define custom messages in the feature's `.proto` file
- Naming: Services `PascalCaseService`, RPCs `camelCase`, Messages `PascalCase`
- For streaming responses, use `stream` keyword (see `subscribeToAuthCallback` in `account.proto`)
**Run `npm run protos`** after any proto changes—generates types in:
- `src/shared/proto/` - Shared type definitions
- `src/generated/grpc-js/` - Service implementations
- `src/generated/nice-grpc/` - Promise-based clients
- `src/generated/hosts/` - Generated handlers
**Adding new enum values** (like a new `ClineSay` type) requires updating conversion mappings in `src/shared/proto-conversions/cline-message.ts`
**Adding new RPC methods** requires:
- Handler in `src/core/controller/<domain>/`
- Call from webview via generated client: `UiServiceClient.scrollToSettings(StringRequest.create({ value: "browser" }))`
**Example—the `explain-changes` feature touched:**
- `proto/cline/task.proto` - Added `ExplainChangesRequest` message and `explainChanges` RPC
- `proto/cline/ui.proto` - Added `GENERATE_EXPLANATION = 29` to `ClineSay` enum
- `src/shared/ExtensionMessage.ts` - Added `ClineSayGenerateExplanation` type
- `src/shared/proto-conversions/cline-message.ts` - Added mapping for new say type
- `src/core/controller/task/explainChanges.ts` - Handler implementation
- `webview-ui/src/components/chat/ChatRow.tsx` - UI rendering
## Adding a New API Provider
When adding a new provider (e.g., "openai-codex"), you must update the proto conversion layer in THREE places or the provider will silently reset to Anthropic:
1. `proto/cline/models.proto` - Add to the `ApiProvider` enum (e.g., `OPENAI_CODEX = 40;`)
2. `convertApiProviderToProto()` in `src/shared/proto-conversions/models/api-configuration-conversion.ts` - Add case mapping string to proto enum
3. `convertProtoToApiProvider()` in the same file - Add case mapping proto enum back to string
**Why this matters:** Without these, the provider string hits the `default` case and returns `ANTHROPIC`. The webview, provider list, and handler all work fine, but the state silently resets when it round-trips through proto serialization. No error is thrown.
**Other files to update when adding a provider:**
- `src/shared/api.ts` - Add to `ApiProvider` union type, define models
- `src/shared/providers/providers.json` - Add to provider list for dropdown
- `src/core/api/index.ts` - Register handler in `createHandlerForProvider()`
- `webview-ui/src/components/settings/utils/providerUtils.ts` - Add cases in `getModelsForProvider()` and `normalizeApiConfiguration()`
- `webview-ui/src/utils/validate.ts` - Add validation case
- `webview-ui/src/components/settings/ApiOptions.tsx` - Render provider component
## Responses API Providers (OpenAI Codex, OpenAI Native)
Providers using OpenAI's Responses API require native tool calling. XML tools don't work with the Responses API.
**Symptoms of broken native tool calling:**
- Tools get called multiple times (e.g., `ask_followup_question` asks the same question twice)
- Tool arguments get duplicated or malformed
- The model responds but tools aren't recognized
**Root causes to check:**
1. **Provider missing from `isNextGenModelProvider()`** in `src/utils/model-utils.ts`. The native variant matchers (e.g., `native-gpt-5/config.ts`) call this function. If your provider isn't in the list, the matcher returns false and falls back to XML tools.
2. **Model missing `apiFormat: ApiFormat.OPENAI_RESPONSES`** in its model info (`src/shared/api.ts`). This property signals that the model requires native tool calling. The task runner in `src/core/task/index.ts` checks this and forces `enableNativeToolCalls: true` regardless of user settings.
**When adding a new Responses API provider:**
1. Add provider to `isNextGenModelProvider()` list in `src/utils/model-utils.ts`
2. Set `apiFormat: ApiFormat.OPENAI_RESPONSES` on all models that use the Responses API
3. The variant matcher and task runner will handle the rest automatically
## Adding Tools to System Prompt
This is tricky—multiple prompt variants and configs. **Always search for existing similar tools first and follow their pattern.** Look at the full chain from prompt definition → variant configs → handler → UI before implementing.
1. **Add to `ClineDefaultTool` enum** in `src/shared/tools.ts`
2. **Tool definition** in `src/core/prompts/system-prompt/tools/` (create file like `generate_explanation.ts`)
- Define variants for each `ModelFamily` (generic, next-gen, xs, etc.)
- Export variants array (e.g., `export const my_tool_variants = [GENERIC, NATIVE_NEXT_GEN, XS]`)
- **Fallback behavior**: If a variant isn't defined for a model family, `ClineToolSet.getToolByNameWithFallback()` automatically falls back to GENERIC. So you only need to export `[GENERIC]` unless the tool needs model-specific behavior.
3. **Register in `src/core/prompts/system-prompt/tools/init.ts`** - Import and spread into `allToolVariants`
4. **Add to variant configs** - Each model family has its own config in `src/core/prompts/system-prompt/variants/*/config.ts`. Add your tool's enum to the `.tools()` list:
- `generic/config.ts`, `next-gen/config.ts`, `gpt-5/config.ts`, `native-gpt-5/config.ts`, `native-gpt-5-1/config.ts`, `native-next-gen/config.ts`, `gemini-3/config.ts`, `glm/config.ts`, `hermes/config.ts`, `xs/config.ts`
- **Important**: If you add to a variant's config, make sure the tool spec exports a variant for that ModelFamily (or relies on GENERIC fallback)
5. **Create handler** in `src/core/task/tools/handlers/`
6. **Wire up in `ToolExecutor.ts`** if needed for execution flow
7. **Add to tool parsing** in `src/core/assistant-message/index.ts` if needed
8. **If tool has UI feedback**: add `ClineSay` enum in proto, update `src/shared/ExtensionMessage.ts`, update `src/shared/proto-conversions/cline-message.ts`, update `webview-ui/src/components/chat/ChatRow.tsx`
## Modifying System Prompt
**Read these first:** `src/core/prompts/system-prompt/README.md`, `tools/README.md`, `__tests__/README.md`
System prompt is modular: **components** (reusable sections) + **variants** (model-specific configs) + **templates** (with `{{PLACEHOLDER}}` resolution).
**Key directories:**
- `components/` - Shared sections: `rules.ts`, `capabilities.ts`, `editing_files.ts`, etc.
- `variants/` - Model-specific: `generic/`, `next-gen/`, `xs/`, `gpt-5/`, `gemini-3/`, `hermes/`, `glm/`, etc.
- `templates/` - Template engine and placeholder definitions
**Variant tiers (ask user which to modify):**
- **Next-gen** (Claude 4, GPT-5, Gemini 2.5): `next-gen/`, `native-next-gen/`, `native-gpt-5/`, `native-gpt-5-1/`, `gemini-3/`, `gpt-5/`
- **Standard** (default fallback): `generic/`
- **Local/small models**: `xs/`, `hermes/`, `glm/`
**How overrides work:** Variants can override components via `componentOverrides` in their `config.ts`, or provide a custom template in `template.ts` (e.g., `next-gen/template.ts` exports `rules_template`). If no override, the shared component from `components/` is used.
**Example: Adding a rule to RULES section**
1. Check if variant overrides rules: look for `rules_template` in `variants/*/template.ts` or `componentOverrides.RULES` in `config.ts`
2. If shared: modify `components/rules.ts`
3. If overridden: modify that variant's template
4. XS variant is special—has heavily condensed inline content in `template.ts`
**After any changes, regenerate snapshots:**
```bash
UPDATE_SNAPSHOTS=true npm run test:unit
```
Snapshots live in `__tests__/__snapshots__/`. Tests validate across model families and context variations (browser, MCP, focus chain).
## Modifying Default Slash Commands
Three places need updates:
- `src/core/slash-commands/index.ts` - Command definitions
- `src/core/prompts/commands.ts` - System prompt integration
- `webview-ui/src/utils/slash-commands.ts` - Webview autocomplete
## Adding New Global State Keys
Adding a new key to global state requires updates in multiple places. Missing any step causes silent failures.
Required steps:
1. Type definition in `src/shared/storage/state-keys.ts` - Add to `GlobalState` or `Settings` interface
2. Read from globalState in `src/core/storage/utils/state-helpers.ts`:
- Add `const myKey = context.globalState.get<GlobalStateAndSettings["myKey"]>("myKey")` in `readGlobalStateFromDisk()`
- Add to the return object: `myKey: myKey ?? defaultValue,`
3. StateManager handles read/write via `setGlobalState()`/`getGlobalStateKey()` after initialization
Common mistake: Adding only the return value without the `context.globalState.get()` call. This compiles but the value is always `undefined` on load.
## StateManager Cache vs Direct globalState Access
StateManager uses an in-memory cache populated during `StateManager.initialize(context)` in `common.ts`. For most state, use `controller.stateManager.setGlobalState()`/`getGlobalStateKey()`.
Exception: State needed immediately at extension startup (before cache is ready)
When Window A sets state and immediately opens Window B, the new window's StateManager cache is populated from `context.globalState` during initialization. If you need to read state in Window B right at startup (e.g., in `common.ts` during `initialize()`), read directly from `context.globalState.get()` instead of StateManager's cache.
Example pattern (see `lastShownAnnouncementId` and `worktreeAutoOpenPath`):
```typescript
// Writing (normal pattern)
controller.stateManager.setGlobalState("myKey", value)
// Reading at startup in common.ts (bypass cache)
const value = context.globalState.get<string>("myKey")
```
This is only needed for cross-window state read during the brief startup window before StateManager cache is fully usable. Normal state access after initialization should use StateManager.
## ChatRow Cancelled/Interrupted States
When a ChatRow displays a loading/in-progress state (spinner), you must handle what happens when the task is cancelled. This is non-obvious because cancellation doesn't update the message content—you have to infer it from context.
**The pattern:**
1. A message has a `status` field (e.g., `"generating"`, `"complete"`, `"error"`) stored in `message.text` as JSON
2. When cancelled mid-operation, the status stays `"generating"` forever—no one updates it
3. To detect cancellation, check TWO conditions:
- `!isLast` — if this message is no longer the last message, something else happened after it (interrupted)
- `lastModifiedMessage?.ask === "resume_task" || "resume_completed_task"` — task was just cancelled and is waiting to resume
**Example from `generate_explanation`:**
```tsx
const wasCancelled =
explanationInfo.status === "generating" &&
(!isLast ||
lastModifiedMessage?.ask === "resume_task" ||
lastModifiedMessage?.ask === "resume_completed_task")
const isGenerating = explanationInfo.status === "generating" && !wasCancelled
```
**Why both checks?**
- `!isLast` catches: cancelled → resumed → did other stuff → this old message is stale
- `lastModifiedMessage?.ask === "resume_task"` catches: just cancelled, hasn't resumed yet, this message is still technically "last"
**See also:** `BrowserSessionRow.tsx` uses similar pattern with `isLastApiReqInterrupted` and `isLastMessageResume`.
**Backend side:** When streaming is cancelled, clean up properly (close tabs, clear comments, etc.) by checking `taskState.abort` after the streaming function returns.
+28 -3
View File
@@ -72,12 +72,12 @@ POSTHOG_TELEMETRY_ENABLED=true # Enable PostHog telemetry (default: tru
# Example configurations:
#
# Console debugging (logs only):
# OTEL_TELEMETRY_ENABLED=1
# OTEL_TELEMETRY_ENABLED=true
# OTEL_LOGS_EXPORTER=console
# TEL_DEBUG_DIAGNOSTICS=true
#
# OTLP with gRPC (insecure, for local testing):
# OTEL_TELEMETRY_ENABLED=1
# OTEL_TELEMETRY_ENABLED=true
# OTEL_LOGS_EXPORTER=otlp
# OTEL_EXPORTER_OTLP_PROTOCOL=grpc
# OTEL_EXPORTER_OTLP_ENDPOINT=localhost:4317
@@ -85,12 +85,37 @@ POSTHOG_TELEMETRY_ENABLED=true # Enable PostHog telemetry (default: tru
# OTEL_EXPORTER_OTLP_HEADERS=authorization=Bearer your-token
#
# OTLP with HTTP/JSON (production):
# OTEL_TELEMETRY_ENABLED=1
# OTEL_TELEMETRY_ENABLED=true
# OTEL_LOGS_EXPORTER=otlp
# OTEL_EXPORTER_OTLP_PROTOCOL=http/json
# OTEL_EXPORTER_OTLP_ENDPOINT=https://otel.example.com
# OTEL_EXPORTER_OTLP_HEADERS=authorization=Bearer your-token
# ============================================================================
# OBJECT STORE CONFIGURATION
# ============================================================================
# TO ENABLE S3 OR R2 STORAGE, UNCOMMENT AND FILL IN THE FOLLOWING:
# CLINE_STORAGE_ADAPTER="s3" # Options: "s3" or "r2"
# CLINE_STORAGE_BUCKET="cline"
# CLINE_STORAGE_ACCESS_KEY_ID="key"
# CLINE_STORAGE_SECRET_ACCESS_KEY="secrets"
#
# [OPTIONAL FIELDS FOR R2]
# CLINE_STORAGE_ACCOUNT_ID = "account-id"
# Default R2 endpoint (if not set): "https://<CLINE_STORAGE_ACCOUNT_ID>.r2.cloudflarestorage.com"
# CLINE_STORAGE_ENDPOINT = "http://localhost:8333"
#
# [OPTIONAL FIELDS FOR S3]
# CLINE_STORAGE_REGION = "us-west-1" # AWS Bucket Region (default: "us-east-1")
# Default S3 endpoint (if not set): "https://s3.<CLINE_STORAGE_REGION>.amazonaws.com"
# CLINE_STORAGE_ENDPOINT = "http://localhost:8333"
#
# [OPTIONAL FIELDS FOR ALL STORAGE TYPES]
# CLINE_STORAGE_SYNC_INTERVAL_MS = 30000 # Interval for sync worker in milliseconds
# CLINE_STORAGE_SYNC_MAX_RETRIES = 5 # Max retries for failed sync operations
# CLINE_STORAGE_SYNC_BATCH_SIZE = 10 # Number of files to sync in each batch
# CLINE_STORAGE_SYNC_BACKFILL_ENABLED = false # Enable backfill of existing data on startup
# ============================================================================
# OPTIONAL DEVELOPMENT SETTINGS
# ============================================================================
-1
View File
@@ -1,4 +1,3 @@
/docs/
/.github/ @saoudrizwan @garoth @sjf
/README.md @saoudrizwan @nickbaumann98
/src/core/storage/ @celestial-vault @abeatrix
+312
View File
@@ -0,0 +1,312 @@
name: Cline PR Code Review
on:
pull_request:
types:
[opened, ready_for_review]
# Manual trigger for backfilling existing PRs. Run from terminal:
# gh workflow run cline-pr-review.yml -f pr_number=1234
# Or batch process open PRs:
# gh pr list --state open --limit 10 --json number --jq '.[].number' | while read num; do
# gh workflow run cline-pr-review.yml -f pr_number=$num
# sleep 60
# done
workflow_dispatch:
inputs:
pr_number:
description: "PR number to review"
required: true
type: string
concurrency:
group: pr-review-${{ github.event.pull_request.number || inputs.pr_number }}
cancel-in-progress: true
jobs:
cline-pr-review:
# Runs on PR opened/ready_for_review (skips drafts) or manual trigger for backfilling
if: |
(github.event_name == 'pull_request' && github.event.pull_request.draft == false) ||
github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
timeout-minutes: 60
# SECURITY: These permissions are intentionally restrictive.
# - contents: read -> cline can read the codebase but CANNOT write/push any code
# - pull-requests: write -> cline can post reviews and inline suggestions
# - issues: read -> cline can search for related issues
# NOTE: Even with pull-requests: write, cline CANNOT merge PRs because branch protection
# requires 1 approval from a Code Owner. The GITHUB_TOKEN cannot bypass this.
permissions:
contents: read
pull-requests: write
issues: read
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: "npm"
- name: Install and Verify Cline CLI
run: |
npx cline version # verify installation
- name: Configure Cline with Anthropic
run: |
npx cline auth --provider anthropic \
--apikey "${{ secrets.ANTHROPIC_API_KEY }}" \
--modelid claude-opus-4-5-20251101
- name: Get PR number
id: pr
run: |
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
echo "number=${{ inputs.pr_number }}" >> $GITHUB_OUTPUT
else
echo "number=${{ github.event.pull_request.number }}" >> $GITHUB_OUTPUT
fi
- name: Review PR with Cline
env:
PR_NUMBER: ${{ steps.pr.outputs.number }}
GITHUB_REPO: ${{ github.repository }}
GH_TOKEN: ${{ github.token }}
CLINE_COMMAND_PERMISSIONS: |
{
"allow": [
"gh pr diff *",
"gh pr view *",
"gh pr checks *",
"gh pr list *",
"gh label list *",
"gh issue list *",
"gh issue view *",
"git log *",
"gh pr comment ${{ steps.pr.outputs.number }} *",
"gh pr edit ${{ steps.pr.outputs.number }} *",
"gh api repos/${{ github.repository }}/pulls/${{ steps.pr.outputs.number }}/comments *",
"gh api repos/${{ github.repository }}/pulls/${{ steps.pr.outputs.number }}/reviews *"
]
}
run: |
npx cline --yolo 'You'\''re a GitHub PR reviewer for the open source Cline repository. Your goal is to give the PR author helpful feedback and give maintainers the context they need to review efficiently.
PR: #'"${PR_NUMBER}"'
## Gather context
```bash
# Get full PR details
gh pr view '"${PR_NUMBER}"' --json number,title,body,author,createdAt,updatedAt,isDraft,labels,commits,files,additions,deletions,changedFiles,baseRefName,headRefName,mergeable,reviewDecision
# Get the diff
gh pr diff '"${PR_NUMBER}"'
# Check CI status
gh pr checks '"${PR_NUMBER}"'
# Get existing review comments (to understand context and your previous feedback)
gh api repos/'"${GITHUB_REPO}"'/pulls/'"${PR_NUMBER}"'/comments --jq '\''.[] | {user: .user.login, body: .body, path: .path, created_at: .created_at}'\''
# Get conversation comments
gh pr view '"${PR_NUMBER}"' --comments
```
If this is a re-review (workflow_dispatch event):
Read your previous comments carefully. Understand what you asked for before.
Check if new commits or comments address your previous feedback.
## Check contributing guidelines
Flag (but don'\''t block) if:
- Missing changeset - For user-facing changes, check if there'\''s a `.changeset/` file:
```bash
gh pr diff '"${PR_NUMBER}"' --name-only | grep '\''.changeset/'\'' || echo '\''No changeset found'\''
```
If missing, ask them to run `npm run changeset`
- Missing tests - New features should have tests
## Find related issues and PRs
Search thoroughly for context that might help with the review:
```bash
# Find related issues for context
gh issue list --search '\''<keywords from the PR>'\'' --state all --limit 30
gh issue list --search '\''<error messages or feature names>'\'' --state all --limit 20
# Find similar PRs for reference
gh pr list --search '\''<keywords>'\'' --state all --limit 30
```
For each relevant issue or PR you find, read it including comments:
```bash
gh issue view <number> --comments
gh pr view <number> --comments
```
Look for:
- Open issues this PR might fix that weren'\''t linked in the description
- Similar PRs that went through review - what feedback did they get? What patterns did they follow?
- Context from maintainer discussions that could inform your review
## Find subject matter experts
For files changed in this PR, find who knows the code best:
```bash
# Get files changed
gh pr diff '"${PR_NUMBER}"' --name-only
# For each relevant path, find contributors
git log --since='\''6 months ago'\'' --format='\''%an'\'' -- <path> | sort | uniq -c | sort -rn | head -5
```
Cross-reference git authors with GitHub usernames. Include an SME table in your response:
| SME | Reason |
|-----|--------|
| @username1 | Authored PR #X which modified this area |
| @username2 | Primary contributor to affected file (15 commits in 6 months) |
| @username3 | Reviewed similar PR #Y with extensive feedback |
## Bash command usage
Don'\''t use operators like `|`, `&&`, or `;` - run each command separately and analyze the output.
When referencing command outputs, quote them properly to avoid formatting issues.
## Deep code review
This is the most important part. Don'\''t just look for syntax issues - understand what the PR is trying to achieve and whether the implementation is the right approach.
Step 1: Understand the intent
Read the PR description and understand what the author is trying to accomplish. What problem are they solving? What feature are they adding?
Step 2: Form your own opinion first
Before analyzing their code, think about how YOU would implement this feature or fix. What files would you touch? What patterns would you follow? What edge cases would you handle?
Step 3: Compare approaches
Now look at their implementation. How does it compare to what you would have done?
- Is their approach better in some ways? Note what they did well.
- Is their approach missing something? Be specific about what and why.
- Are there edge cases they haven'\''t considered?
- Does it follow the patterns established in similar parts of the codebase?
Step 4: Look at the bigger picture
- What other files or systems does this change interact with?
- Could this break anything else?
- Is there additional work needed beyond this PR to complete the feature?
- Does this fit well with the overall architecture?
Step 5: Find reference implementations
Look for similar changes in the codebase:
```bash
git log --oneline --all --grep='\''<relevant keywords>'\'' | head -20
git log --oneline -- <similar files> | head -20
```
If this is adding a new API provider, look at how other providers are implemented.
If this is adding a new feature, look at how similar features were added.
Note where their implementation aligns with or diverges from established patterns.
Step 6: Standard code review checks
- DRY: Is there duplicated code that could be extracted?
- Error handling: Are errors handled appropriately?
- Security: Any injection risks, credential exposure, unsafe dependencies?
- Performance: Any obvious inefficiencies, memory leaks, N+1 patterns?
- Types: Is TypeScript used correctly? Any unsafe type assertions?
- Naming: Are variables and functions named clearly?
- Comments: Is complex logic explained? Are there outdated comments?
## Inline code suggestions
For specific code improvements, use GitHub'\''s suggestion syntax via `gh api`.
This creates suggestions the author can commit with one click.
Single-line suggestion:
```bash
gh api repos/'"${GITHUB_REPO}"'/pulls/'"${PR_NUMBER}"'/reviews \
-X POST \
-f commit_id="$(gh pr view '"${PR_NUMBER}"' --json headRefOid -q .headRefOid)" \
-f event='\''COMMENT'\'' \
-f body='\'''\'' \
-F comments='\''[
{
"path": "src/example.ts",
"line": 42,
"body": "Consider simplifying:\n\n```suggestion\nconst result = items.filter(Boolean);\n```"
}
]'\''
```
Multi-line suggestion (replacing lines 40-45):
```bash
gh api repos/'"${GITHUB_REPO}"'/pulls/'"${PR_NUMBER}"'/reviews \
-X POST \
-f commit_id="$(gh pr view '"${PR_NUMBER}"' --json headRefOid -q .headRefOid)" \
-f event='\''COMMENT'\'' \
-f body='\'''\'' \
-F comments='\''[
{
"path": "src/example.ts",
"start_line": 40,
"line": 45,
"body": "This can be simplified:\n\n```suggestion\nconst simplified = doThing();\n```"
}
]'\''
```
Use inline suggestions for concrete improvements. Use regular comments for questions or broader feedback.
## Post your review
After your investigation, post a single helpful comment that helps the author and gives maintainers context.
Start with a warm thank you for their contribution. Be conversational, not robotic.
Include what'\''s relevant:
- In-depth explanation of what the PR does - Be comprehensive. A maintainer should be able to read this section and fully understand the author'\''s intent, why they made the changes, how they implemented it, and what files/systems are affected. Don'\''t just summarize - explain.
- Related issues/PRs you found that provide useful context (link to them)
- Your review findings (issues to address, suggestions, etc.)
- Clear next steps for the author
Include a '\''For Maintainers'\'' section with:
- Anything else useful to help the maintainer resolve this PR
- Related issues/PRs with context on why they'\''re relevant
- Open issues this PR might fix that weren'\''t linked in the description
- Your recommendation: merge as-is, needs changes, needs discussion, close, etc.
- SME table - who should review this and why
For the SME table:
| SME | Reason |
|-----|--------|
| @username | Primary contributor to affected files |
## Update labels
Add appropriate labels based on your analysis:
```bash
gh label list --json name,description --limit 100
gh pr edit '"${PR_NUMBER}"' --add-label '\''label1,label2'\''
```
When done, add the reviewed label:
```bash
gh pr edit '"${PR_NUMBER}"' --add-label '\''Bot Reviewed'\''
```
## Remember
- This is a one-time automated response - you will NOT see their reply or respond again. Never say things like '\''let me know if you have questions'\'', '\''I can help you with'\'', or '\''feel free to ask'\'' - you won'\''t be there to follow up. Frame any questions for the maintainers who will follow up.
- Be helpful and welcoming - Many contributors are new to the project
- Be specific - Point to exact lines and suggest fixes, don'\''t give vague feedback
- Think deeply - Don'\''t just surface-level review, understand the intent and evaluate the approach
- Use inline suggestions - Make it easy for authors to accept changes
- You'\''re a first-pass reviewer - A human maintainer will do final approval'
+130
View File
@@ -0,0 +1,130 @@
name: Publish NPM Release
on:
workflow_dispatch:
inputs:
confirm_publish:
description: 'Type "publish" to confirm you want to publish to NPM'
required: true
type: string
permissions:
contents: read
checks: write # Required by test workflow
pull-requests: write # Required by test workflow
jobs:
test:
uses: ./.github/workflows/test.yml
publish-npm-release:
needs: test
name: Publish Cline CLI to NPM
if: github.repository == 'cline/cline' && github.ref == 'refs/heads/main' && github.event.inputs.confirm_publish == 'publish'
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20.x'
registry-url: 'https://registry.npmjs.org'
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.24'
cache-dependency-path: cli/go.sum
# Cache root dependencies - only reuse if package-lock.json exactly matches
- name: Cache root dependencies
uses: actions/cache@v4
id: root-cache
with:
path: node_modules
key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json') }}
# Cache webview-ui dependencies - only reuse if package-lock.json exactly matches
- name: Cache webview-ui dependencies
uses: actions/cache@v4
id: webview-cache
with:
path: webview-ui/node_modules
key: ${{ runner.os }}-npm-webview-${{ hashFiles('webview-ui/package-lock.json') }}
- name: Install root dependencies
if: steps.root-cache.outputs.cache-hit != 'true'
run: npm ci --include=optional
- name: Install webview-ui dependencies
if: steps.webview-cache.outputs.cache-hit != 'true'
run: cd webview-ui && npm ci --include=optional
- name: Read release version
id: version
run: |
# Read version from cli/package.json (stable version)
VERSION=$(node -p "require('./cli/package.json').version")
echo "Release version: $VERSION"
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: Download ripgrep binaries
run: npm run download-ripgrep
- name: Clean previous builds
run: rm -rf dist-standalone
- name: Generate Protos (First Pass)
run: npm run protos && npm run protos-go
- name: Compile CLI
run: npm run compile-cli
- name: Compile CLI for all platforms
run: npm run compile-cli-all-platforms
- name: Build standalone NPM package
env:
TELEMETRY_SERVICE_API_KEY: ${{ secrets.TELEMETRY_SERVICE_API_KEY }}
ERROR_SERVICE_API_KEY: ${{ secrets.ERROR_SERVICE_API_KEY }}
CLINE_ENVIRONMENT: production
OTEL_TELEMETRY_ENABLED: "1"
OTEL_METRICS_EXPORTER: otlp
OTEL_LOGS_EXPORTER: otlp
OTEL_EXPORTER_OTLP_PROTOCOL: ${{ secrets.OTEL_EXPORTER_OTLP_PROTOCOL }}
OTEL_EXPORTER_OTLP_ENDPOINT: ${{ secrets.OTEL_EXPORTER_OTLP_ENDPOINT }}
OTEL_EXPORTER_OTLP_HEADERS: ${{ secrets.OTEL_EXPORTER_OTLP_HEADERS }}
POSTHOG_TELEMETRY_ENABLED: "true"
run: npm run compile-standalone-npm
- name: Generate Protos (Second Pass - Bug Workaround)
run: npm run protos && npm run protos-go
- name: Verify build output
run: |
echo "Checking dist-standalone directory..."
ls -la dist-standalone/
echo "Verifying CLI binaries..."
ls -lh cli/bin/cline-* || echo "Warning: CLI binaries not found"
echo "Checking package.json in dist-standalone..."
cat dist-standalone/package.json | grep version
- name: Publish to NPM with latest tag
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_RELEASE_TOKEN }}
run: |
echo "Publishing version ${{ steps.version.outputs.version }} to NPM with tag 'latest'..."
cd dist-standalone
npm publish --tag latest --access public
- name: Summary
run: |
echo "✅ Successfully published cline@${{ steps.version.outputs.version }} to NPM with tag 'latest'"
echo ""
echo "📦 Install with: npm install -g cline"
echo "🔗 NPM: https://www.npmjs.com/package/cline/v/${{ steps.version.outputs.version }}"
+175
View File
@@ -0,0 +1,175 @@
name: Publish NPM Nightly
on:
schedule:
- cron: '0 12 * * *' # 4 AM PST (UTC-8) = 12 UTC
workflow_dispatch:
permissions:
contents: read
checks: write # Required by test workflow
pull-requests: write # Required by test workflow
jobs:
test:
uses: ./.github/workflows/test.yml
publish-npm-nightly:
needs: test
name: Publish Cline CLI (Nightly) to NPM
if: github.repository == 'cline/cline' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Check for recent commits
id: check_commits
run: |
if [ $(git rev-list --count HEAD --since="24 hours ago") -eq 0 ]; then
echo "No commits in last 24 hours, skipping publish"
echo "skip=true" >> $GITHUB_OUTPUT
else
echo "Found recent commits, proceeding with publish"
echo "skip=false" >> $GITHUB_OUTPUT
fi
- name: Setup Node.js
if: steps.check_commits.outputs.skip != 'true'
uses: actions/setup-node@v4
with:
node-version: '20.x'
registry-url: 'https://registry.npmjs.org'
- name: Setup Go
if: steps.check_commits.outputs.skip != 'true'
uses: actions/setup-go@v5
with:
go-version: '1.24'
cache-dependency-path: cli/go.sum
# Cache root dependencies - only reuse if package-lock.json exactly matches
- name: Cache root dependencies
if: steps.check_commits.outputs.skip != 'true'
uses: actions/cache@v4
id: root-cache
with:
path: node_modules
key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json') }}
# Cache webview-ui dependencies - only reuse if package-lock.json exactly matches
- name: Cache webview-ui dependencies
if: steps.check_commits.outputs.skip != 'true'
uses: actions/cache@v4
id: webview-cache
with:
path: webview-ui/node_modules
key: ${{ runner.os }}-npm-webview-${{ hashFiles('webview-ui/package-lock.json') }}
- name: Install root dependencies
if: steps.check_commits.outputs.skip != 'true' && steps.root-cache.outputs.cache-hit != 'true'
run: npm ci --include=optional
- name: Install webview-ui dependencies
if: steps.check_commits.outputs.skip != 'true' && steps.webview-cache.outputs.cache-hit != 'true'
run: cd webview-ui && npm ci --include=optional
- name: Generate nightly version with timestamp
if: steps.check_commits.outputs.skip != 'true'
id: version
run: |
# Read base version from cli/package.json (e.g., "1.0.9")
BASE_VERSION=$(node -p "require('./cli/package.json').version")
# Generate timestamp (Unix epoch seconds)
TIMESTAMP=$(date +%s)
# Create unique nightly version: 1.0.9-nightly.1736365200
VERSION="${BASE_VERSION}-nightly.${TIMESTAMP}"
echo "Base version: $BASE_VERSION"
echo "Generated nightly version: $VERSION"
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: Update cli/package.json with nightly version
if: steps.check_commits.outputs.skip != 'true'
run: |
# Update version with timestamp-based nightly version
node -e "
const fs = require('fs');
const pkg = JSON.parse(fs.readFileSync('cli/package.json', 'utf8'));
pkg.version = '${{ steps.version.outputs.version }}';
fs.writeFileSync('cli/package.json', JSON.stringify(pkg, null, '\t'));
"
echo "Using version ${{ steps.version.outputs.version }} for build"
cat cli/package.json | grep '"version"'
- name: Download ripgrep binaries
if: steps.check_commits.outputs.skip != 'true'
run: npm run download-ripgrep
- name: Clean previous builds
if: steps.check_commits.outputs.skip != 'true'
run: rm -rf dist-standalone
- name: Generate Protos (First Pass)
if: steps.check_commits.outputs.skip != 'true'
run: npm run protos && npm run protos-go
- name: Compile CLI
if: steps.check_commits.outputs.skip != 'true'
run: npm run compile-cli
- name: Compile CLI for all platforms
if: steps.check_commits.outputs.skip != 'true'
run: npm run compile-cli-all-platforms
- name: Build standalone NPM package
if: steps.check_commits.outputs.skip != 'true'
env:
TELEMETRY_SERVICE_API_KEY: ${{ secrets.TELEMETRY_SERVICE_API_KEY }}
ERROR_SERVICE_API_KEY: ${{ secrets.ERROR_SERVICE_API_KEY }}
CLINE_ENVIRONMENT: production
OTEL_TELEMETRY_ENABLED: "1"
OTEL_METRICS_EXPORTER: otlp
OTEL_LOGS_EXPORTER: otlp
OTEL_EXPORTER_OTLP_PROTOCOL: ${{ secrets.OTEL_EXPORTER_OTLP_PROTOCOL }}
OTEL_EXPORTER_OTLP_ENDPOINT: ${{ secrets.OTEL_EXPORTER_OTLP_ENDPOINT }}
OTEL_EXPORTER_OTLP_HEADERS: ${{ secrets.OTEL_EXPORTER_OTLP_HEADERS }}
POSTHOG_TELEMETRY_ENABLED: "true"
run: npm run compile-standalone-npm
- name: Generate Protos (Second Pass - Bug Workaround)
if: steps.check_commits.outputs.skip != 'true'
run: npm run protos && npm run protos-go
- name: Verify build output
if: steps.check_commits.outputs.skip != 'true'
run: |
echo "Checking dist-standalone directory..."
ls -la dist-standalone/
echo "Verifying CLI binaries..."
ls -lh cli/bin/cline-* || echo "Warning: CLI binaries not found"
echo "Checking package.json in dist-standalone..."
cat dist-standalone/package.json | grep version
- name: Publish to NPM with nightly tag
if: steps.check_commits.outputs.skip != 'true'
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_RELEASE_TOKEN }}
run: |
echo "Publishing version ${{ steps.version.outputs.version }} to NPM with tag 'nightly'..."
cd dist-standalone
npm publish --tag nightly --access public
- name: Summary
if: steps.check_commits.outputs.skip != 'true'
run: |
echo "✅ Successfully published cline@${{ steps.version.outputs.version }} to NPM with tag 'nightly'"
echo ""
echo "📦 Install with: npm install -g cline@nightly"
echo "🔗 NPM: https://www.npmjs.com/package/cline/v/${{ steps.version.outputs.version }}"
+3 -12
View File
@@ -187,20 +187,11 @@ jobs:
if: steps.webview-cache.outputs.cache-hit != 'true'
run: cd webview-ui && npm ci
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.24'
cache-dependency-path: cli/go.sum
- name: Build CLI binaries
run: npm run compile-cli-all-platforms
- name: Download ripgrep binaries
run: npm run download-ripgrep
- name: Compile NPM package
run: npm run compile-standalone-npm
- name: Compile Standalone
run: npm run compile-standalone
- name: Install testing platform dependencies
if: steps.testing-platform-cache.outputs.cache-hit != 'true'
@@ -213,7 +204,7 @@ jobs:
# This prevents the job from showing as failed and avoids distracting developers
# until the integration tests are ready to be enforced.
run: |
npm run test:tp-orchestrator -- tests/specs/ --count=1 --coverage || true
npm run test:tp-orchestrator -- tests/specs/ --count=1 --coverage
- name: Save Coverage Reports
uses: actions/upload-artifact@v4
+38 -10
View File
@@ -1,17 +1,26 @@
name: Trigger Jetbrains Plugin <-> Cline Tests
on:
pull_request_target:
types: [opened, synchronize, reopened]
types: [opened, reopened]
issue_comment:
types: [created]
permissions:
contents: read
pull-requests: read
concurrency:
group: jetbrains-trigger-${{ github.event.number }}
group: jetbrains-trigger-${{ github.event.pull_request.number || github.event.issue.number }}
cancel-in-progress: true
jobs:
trigger-integration-test:
name: Run Tests
runs-on: ubuntu-latest
# Run on PR open/reopen, or when someone comments /test-jetbrains on a PR
if: |
github.event_name == 'pull_request_target' ||
(github.event_name == 'issue_comment' &&
github.event.issue.pull_request &&
contains(github.event.comment.body, '/test-jetbrains'))
steps:
- name: Generate GitHub App Token
id: app-token
@@ -22,16 +31,28 @@ jobs:
owner: cline
repositories: intellij-plugin
- name: Get PR details (for issue_comment trigger)
id: pr-details
if: github.event_name == 'issue_comment'
env:
GH_TOKEN: ${{ github.token }}
run: |
PR_DATA=$(gh api repos/${{ github.repository }}/pulls/${{ github.event.issue.number }})
echo "head_ref=$(echo "$PR_DATA" | jq -r '.head.ref')" >> $GITHUB_OUTPUT
echo "head_sha=$(echo "$PR_DATA" | jq -r '.head.sha')" >> $GITHUB_OUTPUT
echo "title=$(echo "$PR_DATA" | jq -r '.title')" >> $GITHUB_OUTPUT
echo "html_url=$(echo "$PR_DATA" | jq -r '.html_url')" >> $GITHUB_OUTPUT
- name: Sanitize untrusted inputs
id: sanitize
env:
RAW_BRANCH_NAME: ${{ github.head_ref }}
RAW_PR_TITLE: ${{ github.event.pull_request.title }}
RAW_BRANCH_NAME: ${{ github.event_name == 'pull_request_target' && github.head_ref || steps.pr-details.outputs.head_ref }}
RAW_PR_TITLE: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.title || steps.pr-details.outputs.title }}
run: |
# Sanitize branch name for JSON
BRANCH_NAME_JSON=$(jq -n --arg b "$RAW_BRANCH_NAME" '$b')
echo "branch_name=$BRANCH_NAME_JSON" >> $GITHUB_OUTPUT
# Sanitize PR title for JSON
PR_TITLE_JSON=$(jq -n --arg t "$RAW_PR_TITLE" '$t')
echo "pr_title=$PR_TITLE_JSON" >> $GITHUB_OUTPUT
@@ -40,6 +61,9 @@ jobs:
env:
BRANCH_NAME: ${{ steps.sanitize.outputs.branch_name }}
PR_TITLE: ${{ steps.sanitize.outputs.pr_title }}
PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number }}
PR_SHA: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.sha || steps.pr-details.outputs.head_sha }}
PR_URL: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.html_url || steps.pr-details.outputs.html_url }}
run: |
curl -X POST \
-H "Authorization: Bearer ${{ steps.app-token.outputs.token }}" \
@@ -51,19 +75,23 @@ jobs:
{
"event_type": "cline-pr-check",
"client_payload": {
"pr_number": "${{ github.event.number }}",
"pr_number": "$PR_NUMBER",
"branch_name": $BRANCH_NAME,
"action": "${{ github.event.action }}",
"sha": "${{ github.event.pull_request.head.sha }}",
"sha": "$PR_SHA",
"pr_title": $PR_TITLE,
"pr_url": "${{ github.event.pull_request.html_url }}"
"pr_url": "$PR_URL"
}
}
EOF
- name: Log trigger details
env:
PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number }}
PR_SHA: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.sha || steps.pr-details.outputs.head_sha }}
run: |
echo "Triggered IntelliJ Plugin integration test for:"
echo " PR #${{ github.event.number }}"
echo " PR #$PR_NUMBER"
echo " Trigger: ${{ github.event_name }}"
echo " Action: ${{ github.event.action }}"
echo " SHA: ${{ github.event.pull_request.head.sha }}"
echo " SHA: $PR_SHA"
+7
View File
@@ -29,6 +29,8 @@ coverage-unit
*evals.env
.env
.secrets
.github/act/.secrets
.worktrees
@@ -39,3 +41,8 @@ webview-ui/src/services/grpc-client.ts
# E2E Tests
test-results
/.github/act
/pkg
.secrets
+2
View File
@@ -1,6 +1,8 @@
# Default
.vscode/**
.vscode-test/**
.worktrees/**
CLAUDE.local.md
out/
dist-standalone/
node_modules/
+95 -1
View File
@@ -1,12 +1,106 @@
# Changelog
## [3.52.0]
### Added
- Users with ChatGPT Plus or Pro subscriptions can now use GPT-5 models directly through Cline without needing an API key. Authentication is handled via OAuth through OpenAI's authentication system.
- Grok models are now moving out of free tier and into paid plans.
- Introduces comprehensive Jupyter Notebook support for Cline, enabling AI-assisted editing of `.ipynb` files with full cell-level context awareness.
### Fixed
- Bugs in DiffViewProvider for file editing
- Ollama's recommended models to use correct identifiers
## [3.51.0]
### Added
- Adding OpenAI gpt-5.2-codex model to the model picker
## [3.50.0]
### Added
- Add gpt-5.2-codex OpenAI model support
- Add create-pull-request skill
### Fixed
- Fix the selection of remotely configured providers
- Fix act_mode_respond to prevent consecutive calls
- Fix invalid tool call IDs when switching between model formats
## [3.49.1]
### Added
- Add telemetry to track usage of skills feature
- Add version headers to Cline backend requests
- Phase in Responses API usage instead of defaulting for every supported model
### Fixed
- Fix workflow slash command search to be case-insensitive
- Fix model display in ModelPickerModal when using LiteLLM
- Fix LiteLLM model fetching with default base URL
- Fix crash when OpenAI-compatible APIs send usage chunks with empty or null choices arrays at end of streaming
- Fix model ID for Kat Coder Pro Free model
## [3.49.0]
- Enable configuring an OTEL collector at runtime
- Removing Minimax-2.1 from free model list as the free trial has ended
- Improved image display in MCP responses
- Auto-sync remote MCP servers from remote config to local settings
## [3.48.0]
### Added
- Add Skills system for reusable, on-demand agent instructions
- Add new websearch tooling in Cline provider
- Add zai-glm-4.7 to Cerebras model list
- Add model refresh and improve reasoning support for Vercel AI Gateway
### Fixed
- Revert #8341 due to regressions in diff view/document truncation (see #8423, #8429)
- Fixed extension crash when using context menu selector
## [3.47.0]
### Added
- Added experimental support for Background Edits (allows editing files in background without opening the diff view)
- Updated free model to MiniMax M2.1 (replacing MiniMax M2)
- Added support for Azure based identity authentication in OpenAI Compatible provider and Azure OpenAI
- Add `supportsReasoning` property to Baseten models
### Fixed
- Prevent expired token usage in authenticated requests
- Exclude binary files without extensions from diffs
- Preserve file endings and trailing newlines
- Fix Cerebras rate limiting
- Fix Auto Compact for Claude Code provider
- Make Workspace and Favorites history filters independent
- Fix remote MCP server connection failures (404 response handling)
- Disable native tool calling for Deepseek 3.2 speciale
- Show notification instead of opening sidebar on update
- Fix Baseten model selector
### Refactored
- Modify prompts for parallel tool usage in Claude and Gemini 3 models
## [3.46.1]
### Fixed
- Remove GLM 4.6 from free models
## [3.46.0]
### Added
+2 -129
View File
@@ -1,129 +1,2 @@
# CLAUDE.md
This file is the secret sauce for working effectively in this codebase. It captures tribal knowledge—the nuanced, non-obvious patterns that make the difference between a quick fix and hours of back-and-forth & human intervention.
**When to add to this file:**
- User had to intervene, correct, or hand-hold
- Multiple back-and-forth attempts were needed to get something working
- You discovered something that required reading many files to understand
- A change touched files you wouldn't have guessed
- Something worked differently than you expected
- User explicitly asks to "add this to CLAUDE.md"
**Proactively suggest additions** when any of the above happen—don't wait to be asked.
**What NOT to add:** Stuff you can figure out from reading a few files, obvious patterns, or standard practices. This file should be high-signal, not comprehensive.
## Miscellaneous
- This is a VS Code extension—check `package.json` for available scripts before trying to verify builds (e.g., `npm run compile`, not `npm run build`).
- When creating PRs, if the change is user-facing and significant enough to warrant a changelog entry, run `npm run changeset` and create a patch changeset. Never create minor or major version bumps. Skip changesets for trivial fixes, internal refactors, or minor UI tweaks that users wouldn't notice.
## gRPC/Protobuf Communication
The extension and webview communicate via gRPC-like protocol over VS Code message passing.
**Proto files live in `proto/`** (e.g., `proto/cline/task.proto`, `proto/cline/ui.proto`)
- Each feature domain has its own `.proto` file
- For simple data, use shared types in `proto/cline/common.proto` (`StringRequest`, `Empty`, `Int64Request`)
- For complex data, define custom messages in the feature's `.proto` file
- Naming: Services `PascalCaseService`, RPCs `camelCase`, Messages `PascalCase`
- For streaming responses, use `stream` keyword (see `subscribeToAuthCallback` in `account.proto`)
**Run `npm run protos`** after any proto changes—generates types in:
- `src/shared/proto/` - Shared type definitions
- `src/generated/grpc-js/` - Service implementations
- `src/generated/nice-grpc/` - Promise-based clients
- `src/generated/hosts/` - Generated handlers
**Adding new enum values** (like a new `ClineSay` type) requires updating conversion mappings in `src/shared/proto-conversions/cline-message.ts`
**Adding new RPC methods** requires:
- Handler in `src/core/controller/<domain>/`
- Call from webview via generated client: `UiServiceClient.scrollToSettings(StringRequest.create({ value: "browser" }))`
**Example—the `explain-changes` feature touched:**
- `proto/cline/task.proto` - Added `ExplainChangesRequest` message and `explainChanges` RPC
- `proto/cline/ui.proto` - Added `GENERATE_EXPLANATION = 29` to `ClineSay` enum
- `src/shared/ExtensionMessage.ts` - Added `ClineSayGenerateExplanation` type
- `src/shared/proto-conversions/cline-message.ts` - Added mapping for new say type
- `src/core/controller/task/explainChanges.ts` - Handler implementation
- `webview-ui/src/components/chat/ChatRow.tsx` - UI rendering
## Adding Tools to System Prompt
This is tricky—multiple prompt variants and configs. **Always search for existing similar tools first and follow their pattern.** Look at the full chain from prompt definition → variant configs → handler → UI before implementing.
1. **Add to `ClineDefaultTool` enum** in `src/shared/tools.ts`
2. **Tool definition** in `src/core/prompts/system-prompt/tools/` (create file like `generate_explanation.ts`)
- Define variants for each `ModelFamily` (generic, next-gen, xs, etc.)
- Export variants array (e.g., `export const my_tool_variants = [GENERIC, NATIVE_NEXT_GEN, XS]`)
- **Fallback behavior**: If a variant isn't defined for a model family, `ClineToolSet.getToolByNameWithFallback()` automatically falls back to GENERIC. So you only need to export `[GENERIC]` unless the tool needs model-specific behavior.
3. **Register in `src/core/prompts/system-prompt/tools/init.ts`** - Import and spread into `allToolVariants`
4. **Add to variant configs** - Each model family has its own config in `src/core/prompts/system-prompt/variants/*/config.ts`. Add your tool's enum to the `.tools()` list:
- `generic/config.ts`, `next-gen/config.ts`, `gpt-5/config.ts`, `native-gpt-5/config.ts`, `native-gpt-5-1/config.ts`, `native-next-gen/config.ts`, `gemini-3/config.ts`, `glm/config.ts`, `hermes/config.ts`, `xs/config.ts`
- **Important**: If you add to a variant's config, make sure the tool spec exports a variant for that ModelFamily (or relies on GENERIC fallback)
5. **Create handler** in `src/core/task/tools/handlers/`
6. **Wire up in `ToolExecutor.ts`** if needed for execution flow
7. **Add to tool parsing** in `src/core/assistant-message/index.ts` if needed
8. **If tool has UI feedback**: add `ClineSay` enum in proto, update `src/shared/ExtensionMessage.ts`, update `src/shared/proto-conversions/cline-message.ts`, update `webview-ui/src/components/chat/ChatRow.tsx`
## Modifying System Prompt
**Read these first:** `src/core/prompts/system-prompt/README.md`, `tools/README.md`, `__tests__/README.md`
System prompt is modular: **components** (reusable sections) + **variants** (model-specific configs) + **templates** (with `{{PLACEHOLDER}}` resolution).
**Key directories:**
- `components/` - Shared sections: `rules.ts`, `capabilities.ts`, `editing_files.ts`, etc.
- `variants/` - Model-specific: `generic/`, `next-gen/`, `xs/`, `gpt-5/`, `gemini-3/`, `hermes/`, `glm/`, etc.
- `templates/` - Template engine and placeholder definitions
**Variant tiers (ask user which to modify):**
- **Next-gen** (Claude 4, GPT-5, Gemini 2.5): `next-gen/`, `native-next-gen/`, `native-gpt-5/`, `native-gpt-5-1/`, `gemini-3/`, `gpt-5/`
- **Standard** (default fallback): `generic/`
- **Local/small models**: `xs/`, `hermes/`, `glm/`
**How overrides work:** Variants can override components via `componentOverrides` in their `config.ts`, or provide a custom template in `template.ts` (e.g., `next-gen/template.ts` exports `rules_template`). If no override, the shared component from `components/` is used.
**Example: Adding a rule to RULES section**
1. Check if variant overrides rules: look for `rules_template` in `variants/*/template.ts` or `componentOverrides.RULES` in `config.ts`
2. If shared: modify `components/rules.ts`
3. If overridden: modify that variant's template
4. XS variant is special—has heavily condensed inline content in `template.ts`
**After any changes, regenerate snapshots:**
```bash
UPDATE_SNAPSHOTS=true npm run test:unit
```
Snapshots live in `__tests__/__snapshots__/`. Tests validate across model families and context variations (browser, MCP, focus chain).
## Modifying Default Slash Commands
Three places need updates:
- `src/core/slash-commands/index.ts` - Command definitions
- `src/core/prompts/commands.ts` - System prompt integration
- `webview-ui/src/utils/slash-commands.ts` - Webview autocomplete
## ChatRow Cancelled/Interrupted States
When a ChatRow displays a loading/in-progress state (spinner), you must handle what happens when the task is cancelled. This is non-obvious because cancellation doesn't update the message content—you have to infer it from context.
**The pattern:**
1. A message has a `status` field (e.g., `"generating"`, `"complete"`, `"error"`) stored in `message.text` as JSON
2. When cancelled mid-operation, the status stays `"generating"` forever—no one updates it
3. To detect cancellation, check TWO conditions:
- `!isLast` — if this message is no longer the last message, something else happened after it (interrupted)
- `lastModifiedMessage?.ask === "resume_task" || "resume_completed_task"` — task was just cancelled and is waiting to resume
**Example from `generate_explanation`:**
```tsx
const wasCancelled =
explanationInfo.status === "generating" &&
(!isLast ||
lastModifiedMessage?.ask === "resume_task" ||
lastModifiedMessage?.ask === "resume_completed_task")
const isGenerating = explanationInfo.status === "generating" && !wasCancelled
```
**Why both checks?**
- `!isLast` catches: cancelled → resumed → did other stuff → this old message is stale
- `lastModifiedMessage?.ask === "resume_task"` catches: just cancelled, hasn't resumed yet, this message is still technically "last"
**See also:** `BrowserSessionRow.tsx` uses similar pattern with `isLastApiReqInterrupted` and `isLastMessageResume`.
**Backend side:** When streaming is cancelled, clean up properly (close tabs, clear comments, etc.) by checking `taskState.abort` after the streaming function returns.
@.clinerules/general.md
@.clinerules/network.md
+23
View File
@@ -147,6 +147,29 @@
"src/dev/grit/vscode-api.grit"
]
},
{
// Do not use console logging directly, use the Logger service instead.
"plugins": [
"src/dev/grit/console-log.grit"
],
"includes": [
"**",
"!**/webview-ui/**",
"!**/evals/**",
"!**/standalone/**",
"!**/e2e/**",
"!**/test/**",
"!**/__tests__/**",
"!**/*.test.ts",
"!**/*.stories.ts",
"!src/dev/**",
"!**/*.mjs",
"!**/*.js",
"!**/scripts/**",
"!**/*.tsx",
"!**/testing-platform/**"
]
},
{
"includes": [
"**",
+81 -89
View File
@@ -10,11 +10,12 @@ import (
"strings"
"github.com/charmbracelet/huh"
"github.com/charmbracelet/lipgloss"
"github.com/cline/cli/pkg/cli"
"github.com/cline/cli/pkg/cli/auth"
"github.com/cline/cli/pkg/cli/display"
"github.com/cline/cli/pkg/cli/global"
"github.com/cline/cli/pkg/cli/output"
"github.com/cline/cli/pkg/cli/slash"
"github.com/cline/cli/pkg/common"
"github.com/cline/grpc-go/cline"
"github.com/spf13/cobra"
@@ -37,8 +38,9 @@ var (
func main() {
rootCmd := &cobra.Command{
Use: "cline [prompt]",
Short: "Cline CLI - AI-powered coding assistant",
Use: "cline [prompt]",
Short: "Cline CLI - AI-powered coding assistant",
Version: global.CliVersion,
Long: `A command-line interface for interacting with Cline AI coding assistant.
Start a new task by providing a prompt:
@@ -70,8 +72,6 @@ see the manual page: man cline`,
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
var instanceAddress string
// Validate workspace paths exist
if err := common.ValidateDirsExist(workspaces); err != nil {
return err
@@ -88,13 +88,13 @@ see the manual page: man cline`,
if global.Config.Verbose {
fmt.Println("Starting new Cline instance...")
}
instance, err := global.Clients.StartNewInstance(ctx, allWorkspaces...)
instance, err := global.Instances.StartNewInstance(ctx, allWorkspaces...)
if err != nil {
return fmt.Errorf("failed to start new instance: %w", err)
}
instanceAddress = instance.Address
global.Config.CoreAddress = instance.CoreAddress
if global.Config.Verbose {
fmt.Printf("Started instance at %s\n\n", instanceAddress)
fmt.Printf("Started instance at %s\n\n", global.Config.CoreAddress)
}
// Set up cleanup on exit
@@ -102,38 +102,35 @@ see the manual page: man cline`,
if global.Config.Verbose {
fmt.Println("\nCleaning up instance...")
}
registry := global.Clients.GetRegistry()
if err := global.KillInstanceByAddress(context.Background(), registry, instanceAddress); err != nil {
registry := global.Instances.GetRegistry()
if err := global.KillInstanceByAddress(context.Background(), registry, global.Config.CoreAddress); err != nil {
if global.Config.Verbose {
fmt.Printf("Warning: Failed to clean up instance: %v\n", err)
}
}
}()
}
// Check if user has credentials configured
if !isUserReadyToUse(ctx, instanceAddress) {
// Create renderer for welcome messages
renderer := display.NewRenderer(global.Config.OutputFormat)
fmt.Printf("\n%s\n\n", renderer.Dim("Hey there! Looks like you're new here. Let's get you set up"))
// Check if user has credentials configured
if !isUserReadyToUse(ctx) {
// Create renderer for welcome messages
renderer := display.NewRenderer(global.Config.OutputFormat)
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
if err == huh.ErrUserAborted {
return nil
}
return fmt.Errorf("auth setup failed: %w", err)
if err := auth.HandleAuthMenuNoArgs(ctx); err != nil {
// Check if user cancelled - exit cleanly
if err == huh.ErrUserAborted {
return nil
}
// Re-check after auth wizard
if !isUserReadyToUse(ctx, instanceAddress) {
return fmt.Errorf("credentials still not configured - please run 'cline auth' to complete setup")
}
fmt.Printf("\n%s\n\n", renderer.Dim("✓ Setup complete, you can now use the Cline CLI"))
return fmt.Errorf("auth setup failed: %w", err)
}
} else {
// User specified --address flag, use that
instanceAddress = coreAddress
// Re-check after auth wizard
if !isUserReadyToUse(ctx) {
return fmt.Errorf("credentials still not configured - please run 'cline auth' to complete setup")
}
fmt.Printf("\n%s\n\n", renderer.Dim("✓ Setup complete, you can now use the Cline CLI"))
}
// Get content from both args and stdin
@@ -142,10 +139,13 @@ see the manual page: man cline`,
return fmt.Errorf("failed to read prompt: %w", err)
}
// If no prompt from args or stdin, show interactive input
if prompt == "" {
// If no prompt (or just a mode switch with no message), show interactive input
// Loop to allow mode switches without a message
bannerShown := false
for prompt == "" {
// Pass the mode flag and workspaces to banner so it shows correct info
prompt, err = promptForInitialTask(ctx, instanceAddress, mode, allWorkspaces)
prompt, err = promptForInitialTask(ctx, mode, allWorkspaces, !bannerShown)
bannerShown = true
if err != nil {
// Check if user cancelled - exit cleanly without error
if err == huh.ErrUserAborted {
@@ -153,6 +153,23 @@ see the manual page: man cline`,
}
return err
}
// Check if user entered a mode switch command
if newMode, remaining, isModeSwitch := slash.ParseModeSwitch(prompt); isModeSwitch {
mode = newMode
prompt = remaining
// If just a mode switch with no message, continue loop to re-prompt
if prompt == "" {
renderer := display.NewRenderer(global.Config.OutputFormat)
if mode == "act" {
fmt.Printf("\n%s\n\n", renderer.Success("Switched to act mode"))
} else {
fmt.Printf("\n%s\n\n", renderer.Success("Switched to plan mode"))
}
continue
}
}
if prompt == "" {
return fmt.Errorf("prompt required")
}
@@ -170,13 +187,15 @@ see the manual page: man cline`,
Mode: mode,
Settings: settings,
Yolo: yolo,
Address: instanceAddress,
Address: global.Config.CoreAddress,
Verbose: verbose,
Workspaces: allWorkspaces,
})
},
}
rootCmd.SetVersionTemplate(cli.VersionString())
rootCmd.PersistentFlags().StringVar(&coreAddress, "address", fmt.Sprintf("localhost:%d", common.DEFAULT_CLINE_CORE_PORT), "Cline Core gRPC address")
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose output")
rootCmd.PersistentFlags().StringVarP(&outputFormat, "output-format", "F", "rich", "output format (rich|json|plain)")
@@ -204,51 +223,30 @@ see the manual page: man cline`,
}
}
func promptForInitialTask(ctx context.Context, instanceAddress, modeFlag string, workspaces []string) (string, error) {
// Show session banner before the initial input
showSessionBanner(ctx, instanceAddress, modeFlag, workspaces)
var prompt string
// Create custom theme with mode-colored cursor and title
theme := huh.ThemeCharm()
// Set cursor and title color based on mode
modeColor := lipgloss.Color("3") // Yellow for plan
if modeFlag == "act" {
modeColor = lipgloss.Color("39") // Blue for act
func promptForInitialTask(ctx context.Context, modeFlag string, workspaces []string, showBanner bool) (string, error) {
// Show session banner before the initial input (only on first prompt)
if showBanner {
showSessionBanner(ctx, modeFlag, workspaces)
}
theme.Focused.TextInput.Cursor = theme.Focused.TextInput.Cursor.Foreground(modeColor)
theme.Focused.Title = theme.Focused.Title.Foreground(modeColor)
form := huh.NewForm(
huh.NewGroup(
huh.NewText().
Title("Start a new Cline task").
Description("What would you like Cline to help you with?").
Placeholder("e.g., Create a REST API with authentication...").
Lines(5).
Value(&prompt),
),
).WithWidth(48).WithTheme(theme)
err := form.Run()
prompt, err := output.PromptForInitialTask(
"Start a new Cline task",
"/plan or /act to switch modes\ntab to autocomplete commands\nctrl+e to open editor\nctrl+c to exit",
modeFlag,
slash.NewRegistry(ctx),
)
if err != nil {
// Check if user cancelled with Control-C
if err == huh.ErrUserAborted {
// Return a special error that indicates clean cancellation
// This allows deferred cleanup to run
if err == output.ErrUserAborted {
return "", huh.ErrUserAborted
}
return "", err
}
return strings.TrimSpace(prompt), nil
return prompt, nil
}
// showSessionBanner displays session info before initial prompt
func showSessionBanner(ctx context.Context, instanceAddress, modeFlag string, workspaces []string) {
func showSessionBanner(ctx context.Context, modeFlag string, workspaces []string) {
bannerInfo := display.BannerInfo{
Version: global.CliVersion,
Mode: modeFlag, // Use the mode from command flag, not state
@@ -262,21 +260,18 @@ func showSessionBanner(ctx context.Context, instanceAddress, modeFlag string, wo
bannerInfo.Workdirs = workspaces
// Get provider/model using auth functions (same logic as auth menu)
manager, err := cli.NewTaskManagerForAddress(ctx, instanceAddress)
if err == nil {
if providerList, err := auth.GetProviderConfigurations(ctx, manager); err == nil {
// Show provider/model for the mode we'll be using
var providerDisplay *auth.ProviderDisplay
if bannerInfo.Mode == "plan" && providerList.PlanProvider != nil {
providerDisplay = providerList.PlanProvider
} else if bannerInfo.Mode == "act" && providerList.ActProvider != nil {
providerDisplay = providerList.ActProvider
}
if providerList, err := auth.GetProviderConfigurations(ctx); err == nil {
// Show provider/model for the mode we'll be using
var providerDisplay *auth.ProviderDisplay
if bannerInfo.Mode == "plan" && providerList.PlanProvider != nil {
providerDisplay = providerList.PlanProvider
} else if bannerInfo.Mode == "act" && providerList.ActProvider != nil {
providerDisplay = providerList.ActProvider
}
if providerDisplay != nil {
bannerInfo.Provider = auth.GetProviderIDForEnum(providerDisplay.Provider)
bannerInfo.ModelID = providerDisplay.ModelID
}
if providerDisplay != nil {
bannerInfo.Provider = auth.GetProviderIDForEnum(providerDisplay.Provider)
bannerInfo.ModelID = providerDisplay.ModelID
}
}
@@ -289,25 +284,22 @@ func showSessionBanner(ctx context.Context, instanceAddress, modeFlag string, wo
// isUserReadyToUse checks if the user has completed initial setup
// Returns true if welcomeViewCompleted flag is set OR user is authenticated
// Matches extension logic: welcomeViewCompleted = Boolean(globalState.welcomeViewCompleted || user?.uid)
func isUserReadyToUse(ctx context.Context, instanceAddress string) bool {
manager, err := cli.NewTaskManagerForAddress(ctx, instanceAddress)
func isUserReadyToUse(ctx context.Context) bool {
grpcClient, err := global.GetClientForAddress(ctx, global.Config.CoreAddress)
if err != nil {
return false
}
// Get state
state, err := manager.GetClient().State.GetLatestState(ctx, &cline.EmptyRequest{})
state, err := grpcClient.State.GetLatestState(ctx, &cline.EmptyRequest{})
if err != nil {
return false
}
// Parse state JSON
stateMap := make(map[string]interface{})
if err := json.Unmarshal([]byte(state.StateJson), &stateMap); err != nil {
return false
}
// Check 1: welcomeViewCompleted flag
if welcomeCompleted, ok := stateMap["welcomeViewCompleted"].(bool); ok && welcomeCompleted {
return true
}
+10 -10
View File
@@ -23,7 +23,7 @@ func TestMultiInstanceDefaultUnchanged(t *testing.T) {
if len(out1.CoreInstances) != 1 {
t.Fatalf("expected 1 instance, got %d", len(out1.CoreInstances))
}
firstAddr := out1.CoreInstances[0].Address
firstAddr := out1.CoreInstances[0].CoreAddress
waitForAddressHealthy(t, firstAddr, defaultTimeout)
// Start second instance
@@ -56,29 +56,29 @@ func TestDefaultJsonUpdateAfterRemoval(t *testing.T) {
// Choose second as new default
target := out.CoreInstances[1]
waitForAddressHealthy(t, target.Address, defaultTimeout)
waitForAddressHealthy(t, target.CoreAddress, defaultTimeout)
// Set as default
_ = mustRunCLI(ctx, t, "instance", "use", target.Address)
_ = mustRunCLI(ctx, t, "instance", "use", target.CoreAddress)
// Verify default switched
out = listInstancesJSON(ctx, t)
if out.DefaultInstance != target.Address {
t.Fatalf("default_instance not updated to %s (got %s)", target.Address, out.DefaultInstance)
if out.DefaultInstance != target.CoreAddress {
t.Fatalf("default_instance not updated to %s (got %s)", target.CoreAddress, out.DefaultInstance)
}
// Kill the default instance using runtime PID discovery
corePID := getCorePID(t, target.Address)
corePID := getCorePID(t, target.CoreAddress)
if corePID <= 0 {
t.Fatalf("could not find PID for core process at %s", target.Address)
t.Fatalf("could not find PID for core process at %s", target.CoreAddress)
}
t.Logf("Killing cline-core process PID %d for instance %s", corePID, target.Address)
t.Logf("Killing cline-core process PID %d for instance %s", corePID, target.CoreAddress)
if err := syscall.Kill(corePID, syscall.SIGKILL); err != nil {
t.Fatalf("kill pid %d: %v", corePID, err)
}
// Wait for removal
waitForAddressRemoved(t, target.Address, longTimeout)
waitForAddressRemoved(t, target.CoreAddress, longTimeout)
// Clean up dangling host process (SIGKILL leaves these behind by design)
t.Logf("Cleaning up dangling host process on port %d", target.HostPort())
@@ -91,7 +91,7 @@ func TestDefaultJsonUpdateAfterRemoval(t *testing.T) {
if len(out.CoreInstances) > 0 {
found := false
for _, it := range out.CoreInstances {
if out.DefaultInstance == it.Address {
if out.DefaultInstance == it.CoreAddress {
found = true
break
}
+3 -3
View File
@@ -117,7 +117,7 @@ func listInstancesJSON(ctx context.Context, t *testing.T) common.InstancesOutput
func hasAddress(in common.InstancesOutput, addr string) bool {
for _, it := range in.CoreInstances {
if it.Address == addr {
if it.CoreAddress == addr {
return true
}
}
@@ -126,7 +126,7 @@ func hasAddress(in common.InstancesOutput, addr string) bool {
func getByAddress(in common.InstancesOutput, addr string) (common.CoreInstanceInfo, bool) {
for _, it := range in.CoreInstances {
if it.Address == addr {
if it.CoreAddress == addr {
return it, true
}
}
@@ -313,7 +313,7 @@ func getCorePIDViaRPC(t *testing.T, address string) int {
defer cancel()
// Get client for the address
client, err := global.Clients.GetRegistry().GetClient(ctx, address)
client, err := global.Instances.GetRegistry().GetClient(ctx, address)
if err != nil {
t.Logf("Warning: failed to get client for %s, falling back to lsof: %v", address, err)
return getCorePIDViaLsof(t, address)
+10 -10
View File
@@ -26,7 +26,7 @@ func TestMixedLocalhostVs127Coexist(t *testing.T) {
t.Fatalf("expected at least 1 instance")
}
inst := out.CoreInstances[0]
waitForAddressHealthy(t, inst.Address, defaultTimeout)
waitForAddressHealthy(t, inst.CoreAddress, defaultTimeout)
// Manually add a SQLite entry for the same port but 127.0.0.1 host
addr127 := fmt.Sprintf("127.0.0.1:%d", inst.CorePort())
@@ -37,12 +37,12 @@ func TestMixedLocalhostVs127Coexist(t *testing.T) {
}
// Verify both addresses appear and are healthy
waitForAddressHealthy(t, inst.Address, defaultTimeout)
waitForAddressHealthy(t, inst.CoreAddress, defaultTimeout)
waitForAddressHealthy(t, addr127, defaultTimeout)
out = listInstancesJSON(ctx, t)
if !hasAddress(out, inst.Address) || !hasAddress(out, addr127) {
t.Fatalf("expected both %s and %s present", inst.Address, addr127)
if !hasAddress(out, inst.CoreAddress) || !hasAddress(out, addr127) {
t.Fatalf("expected both %s and %s present", inst.CoreAddress, addr127)
}
}
@@ -58,7 +58,7 @@ func TestStartStopStress(t *testing.T) {
before := listInstancesJSON(ctx, t)
beforeSet := map[string]struct{}{}
for _, it := range before.CoreInstances {
beforeSet[it.Address] = struct{}{}
beforeSet[it.CoreAddress] = struct{}{}
}
// Start a new instance
@@ -69,8 +69,8 @@ func TestStartStopStress(t *testing.T) {
waitFor(t, defaultTimeout, func() (bool, string) {
after := listInstancesJSON(ctx, t)
for _, it := range after.CoreInstances {
if _, ok := beforeSet[it.Address]; !ok {
newAddr = it.Address
if _, ok := beforeSet[it.CoreAddress]; !ok {
newAddr = it.CoreAddress
return true, ""
}
}
@@ -88,12 +88,12 @@ func TestStartStopStress(t *testing.T) {
}
// Get PID using runtime discovery
corePID := getCorePID(t, info.Address)
corePID := getCorePID(t, info.CoreAddress)
if corePID <= 0 {
t.Fatalf("could not find PID for new instance at %s", info.Address)
t.Fatalf("could not find PID for new instance at %s", info.CoreAddress)
}
t.Logf("Killing new instance %s (PID %d) for iteration %d", info.Address, corePID, i)
t.Logf("Killing new instance %s (PID %d) for iteration %d", info.CoreAddress, corePID, i)
if err := syscall.Kill(corePID, syscall.SIGKILL); err != nil {
t.Fatalf("kill pid %d: %v", corePID, err)
}
+1 -1
View File
@@ -55,7 +55,7 @@ func readInstancesFromSQLite(t *testing.T, clineDir string) []common.CoreInstanc
// Create InstanceInfo
info := common.CoreInstanceInfo{
Address: heldBy,
CoreAddress: heldBy,
HostServiceAddress: lockTarget,
Status: grpc_health_v1.HealthCheckResponse_UNKNOWN, // Will be updated by health check
LastSeen: time.Unix(lockedAt/1000, 0), // Convert from milliseconds
+14 -14
View File
@@ -29,7 +29,7 @@ func TestStartAndList(t *testing.T) {
t.Fatalf("expected 1 instance, got %d", len(out.CoreInstances))
}
addr := out.CoreInstances[0].Address
addr := out.CoreInstances[0].CoreAddress
t.Logf("Instance address: %s, status: %s", addr, out.CoreInstances[0].Status)
t.Logf("Waiting for address %s to become healthy...", addr)
@@ -44,8 +44,8 @@ func TestStartAndList(t *testing.T) {
if out.DefaultInstance == "" {
t.Fatalf("default_instance not set")
}
if out.DefaultInstance != out.CoreInstances[0].Address {
t.Fatalf("expected default_instance=%s, got %s", out.CoreInstances[0].Address, out.DefaultInstance)
if out.DefaultInstance != out.CoreInstances[0].CoreAddress {
t.Fatalf("expected default_instance=%s, got %s", out.CoreInstances[0].CoreAddress, out.DefaultInstance)
}
t.Logf("TestStartAndList completed successfully")
@@ -64,7 +64,7 @@ func TestTaskNewDefault(t *testing.T) {
if len(out.CoreInstances) != 1 {
t.Fatalf("expected 1 instance, got %d", len(out.CoreInstances))
}
addr := out.CoreInstances[0].Address
addr := out.CoreInstances[0].CoreAddress
waitForAddressHealthy(t, addr, defaultTimeout)
// Create a new task at default (success is sufficient)
@@ -108,21 +108,21 @@ func TestCrashCleanup(t *testing.T) {
// Test 1: Graceful shutdown (SIGTERM) - should clean up both processes
gracefulTarget := out.CoreInstances[0]
waitForAddressHealthy(t, gracefulTarget.Address, defaultTimeout)
waitForAddressHealthy(t, gracefulTarget.CoreAddress, defaultTimeout)
// Get PID using runtime discovery
gracefulPID := getCorePID(t, gracefulTarget.Address)
gracefulPID := getCorePID(t, gracefulTarget.CoreAddress)
if gracefulPID <= 0 {
t.Fatalf("could not find PID for graceful target at %s", gracefulTarget.Address)
t.Fatalf("could not find PID for graceful target at %s", gracefulTarget.CoreAddress)
}
t.Logf("Testing graceful shutdown (SIGTERM) for instance %s (PID %d)", gracefulTarget.Address, gracefulPID)
t.Logf("Testing graceful shutdown (SIGTERM) for instance %s (PID %d)", gracefulTarget.CoreAddress, gracefulPID)
if err := syscall.Kill(gracefulPID, syscall.SIGTERM); err != nil {
t.Fatalf("kill SIGTERM pid %d: %v", gracefulPID, err)
}
// Wait for registry cleanup
waitForAddressRemoved(t, gracefulTarget.Address, longTimeout)
waitForAddressRemoved(t, gracefulTarget.CoreAddress, longTimeout)
// Verify both core and host ports are freed (no dangling processes)
waitForPortsClosed(t, gracefulTarget.CorePort(), gracefulTarget.HostPort(), defaultTimeout)
@@ -132,21 +132,21 @@ func TestCrashCleanup(t *testing.T) {
// Test 2: Crash cleanup (SIGKILL) - creates dangling host process that we must clean up
crashTarget := out.CoreInstances[1]
waitForAddressHealthy(t, crashTarget.Address, defaultTimeout)
waitForAddressHealthy(t, crashTarget.CoreAddress, defaultTimeout)
// Get PID using runtime discovery
crashPID := getCorePID(t, crashTarget.Address)
crashPID := getCorePID(t, crashTarget.CoreAddress)
if crashPID <= 0 {
t.Fatalf("could not find PID for crash target at %s", crashTarget.Address)
t.Fatalf("could not find PID for crash target at %s", crashTarget.CoreAddress)
}
t.Logf("Testing crash cleanup (SIGKILL) for instance %s (PID %d)", crashTarget.Address, crashPID)
t.Logf("Testing crash cleanup (SIGKILL) for instance %s (PID %d)", crashTarget.CoreAddress, crashPID)
if err := syscall.Kill(crashPID, syscall.SIGKILL); err != nil {
t.Fatalf("kill SIGKILL pid %d: %v", crashPID, err)
}
// Wait for registry cleanup
waitForAddressRemoved(t, crashTarget.Address, longTimeout)
waitForAddressRemoved(t, crashTarget.CoreAddress, longTimeout)
// Verify the instance is removed from SQLite (no file to check anymore)
// The waitForAddressRemoved already confirms the instance is gone from the registry
+58
View File
@@ -298,6 +298,64 @@ cline task view
\f[I]# Start interactive chat with this task\f[R]
cline task chat
.EE
.SH ENVIRONMENT
.TP
\f[B]CLINE_COMMAND_PERMISSIONS\f[R]
JSON configuration for restricting which shell commands Cline can
execute.
When set, commands are validated against allow/deny patterns before
execution.
When not set, all commands are allowed (backward compatibility).
.RS
.PP
Format:
\f[CR]{"allow": ["pattern1", "pattern2"], "deny": ["pattern3"], "allowRedirects": true}\f[R]
.PP
\f[B]Fields:\f[R]
.IP \(bu 2
\f[B]allow\f[R] (array of strings): Glob patterns for allowed commands.
If specified, only matching commands are permitted.
Uses \f[CR]*\f[R] to match any characters and \f[CR]?\f[R] to match a
single character.
.IP \(bu 2
\f[B]deny\f[R] (array of strings): Glob patterns for denied commands.
Deny rules take precedence over allow rules.
.IP \(bu 2
\f[B]allowRedirects\f[R] (boolean): Whether to allow shell redirects
(\f[CR]>\f[R], \f[CR]>>\f[R], \f[CR]<\f[R], etc.).
Defaults to false.
.PP
\f[B]Rule evaluation:\f[R]
.IP "1." 3
Check for dangerous characters (backticks outside single quotes,
unquoted newlines)
.IP "2." 3
Parse command into segments split by operators (\f[CR]&&\f[R],
\f[CR]||\f[R], \f[CR]|\f[R], \f[CR];\f[R])
.IP "3." 3
If redirects detected and \f[CR]allowRedirects\f[R] is not true, command
is denied
.IP "4." 3
Each segment is validated against deny rules first, then allow rules
.IP "5." 3
Subshell contents (\f[CR]$(...)\f[R] and \f[CR](...)\f[R]) are
recursively validated
.IP "6." 3
All segments must pass for the command to be allowed
.PP
\f[B]Examples:\f[R]
.IP
.EX
\f[I]# Allow only npm and git commands\f[R]
export CLINE_COMMAND_PERMISSIONS=\(aq{"allow": ["npm *", "git *"]}\(aq
\f[I]# Allow development commands but deny dangerous ones\f[R]
export CLINE_COMMAND_PERMISSIONS=\(aq{"allow": ["npm *", "git *", "node *"], "deny": ["rm \-rf *", "sudo *"]}\(aq
\f[I]# Allow file operations with redirects\f[R]
export CLINE_COMMAND_PERMISSIONS=\(aq{"allow": ["cat *", "echo *"], "allowRedirects": true}\(aq
.EE
.RE
.SH ARCHITECTURE
Cline operates on a three\-layer architecture:
.TP
+36
View File
@@ -323,6 +323,42 @@ cline task view
cline task chat
```
# ENVIRONMENT
**CLINE_COMMAND_PERMISSIONS**
: JSON configuration for restricting which shell commands Cline can execute. When set, commands are validated against allow/deny patterns before execution. When not set, all commands are allowed.
Format: `{"allow": ["pattern1", "pattern2"], "deny": ["pattern3"], "allowRedirects": true}`
**Fields:**
- **allow** (array of strings): Glob patterns for allowed commands. If specified, only matching commands are permitted. Uses `*` to match any characters and `?` to match a single character. Setting allow on anything will deny all others.
- **deny** (array of strings): Glob patterns for denied commands. Deny rules take precedence over allow rules.
- **allowRedirects** (boolean): Whether to allow shell redirects (`>`, `>>`, `<`, etc.). Defaults to false.
**Rule evaluation:**
1. Check for dangerous characters (backticks outside single quotes, unquoted newlines)
2. Parse command into segments split by operators (`&&`, `||`, `|`, `;`)
3. If redirects detected and `allowRedirects` is not true, command is denied
4. Each segment is validated against deny rules first, then allow rules
5. Subshell contents (`$(...)` and `(...)`) are recursively validated
6. All segments must pass for the command to be allowed
**Examples:**
```bash
# Allow only npm and git commands.
export CLINE_COMMAND_PERMISSIONS='{"allow": ["npm *", "git *"]}'
# Allow development commands but deny dangerous ones. Deny not strictly required here since allow is set.
export CLINE_COMMAND_PERMISSIONS='{"allow": ["npm *", "git *", "node *"], "deny": ["rm -rf *", "sudo *"]}'
# Allow file operations with redirects
export CLINE_COMMAND_PERMISSIONS='{"allow": ["cat *", "echo *"], "allowRedirects": true}'
```
# ARCHITECTURE
Cline operates on a three-layer architecture:
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "cline",
"version": "1.0.3",
"version": "1.0.10",
"description": "Autonomous coding agent CLI - capable of creating/editing files, running commands, using the browser, and more",
"main": "cline-core.js",
"bin": {
+8 -10
View File
@@ -46,21 +46,21 @@ const (
// It spawns a fresh instance for auth operations and cleans it up when done
func RunAuthFlow(ctx context.Context, args []string) error {
// Spawn a fresh instance for auth operations
instanceInfo, err := global.Clients.StartNewInstance(ctx)
instanceInfo, err := global.Instances.StartNewInstance(ctx)
if err != nil {
return fmt.Errorf("failed to start auth instance: %w", err)
}
// Cleanup when done (success, error, or panic)
defer func() {
verboseLog("Shutting down auth instance at %s", instanceInfo.Address)
if err := global.KillInstanceByAddress(context.Background(), global.Clients.GetRegistry(), instanceInfo.Address); err != nil {
verboseLog("Shutting down auth instance at %s", instanceInfo.CoreAddress)
if err := global.KillInstanceByAddress(context.Background(), global.Instances.GetRegistry(), instanceInfo.CoreAddress); err != nil {
verboseLog("Warning: Failed to kill auth instance: %v", err)
}
}()
// Store instance address in context for all auth handlers to use
authCtx := context.WithValue(ctx, authInstanceAddressKey, instanceInfo.Address)
authCtx := context.WithValue(ctx, authInstanceAddressKey, instanceInfo.CoreAddress)
// Route to existing auth flow
return HandleAuthCommand(authCtx, args)
@@ -108,12 +108,10 @@ func HandleAuthMenuNoArgs(ctx context.Context) error {
// Get current provider config for display
var currentProvider string
var currentModel string
if manager, err := createTaskManager(ctx); err == nil {
if providerList, err := GetProviderConfigurations(ctx, manager); err == nil {
if providerList.ActProvider != nil {
currentProvider = GetProviderDisplayName(providerList.ActProvider.Provider)
currentModel = providerList.ActProvider.ModelID
}
if providerList, err := GetProviderConfigurations(ctx); err == nil {
if providerList.ActProvider != nil {
currentProvider = GetProviderDisplayName(providerList.ActProvider.Provider)
currentModel = providerList.ActProvider.ModelID
}
}
+6 -2
View File
@@ -29,13 +29,17 @@ type ProviderListResult struct {
}
// GetProviderConfigurations retrieves and parses provider configurations from Cline Core state
func GetProviderConfigurations(ctx context.Context, manager *task.Manager) (*ProviderListResult, error) {
func GetProviderConfigurations(ctx context.Context) (*ProviderListResult, error) {
if global.Config.Verbose {
fmt.Println("[DEBUG] Retrieving provider configurations from Cline Core")
}
// Get latest state from Cline Core
state, err := manager.GetClient().State.GetLatestState(ctx, &cline.EmptyRequest{})
grpcClient, err := global.GetDefaultClient(ctx)
if err != nil {
return nil, fmt.Errorf("failed to get provider configs due to unable to get gRPC client: %w", err)
}
state, err := grpcClient.State.GetLatestState(ctx, &cline.EmptyRequest{})
if err != nil {
return nil, fmt.Errorf("failed to get state: %w", err)
}
@@ -358,6 +358,9 @@ func AddProviderPartial(ctx context.Context, manager *task.Manager, provider cli
if openRouterInfo, ok := modelInfo.(*cline.OpenRouterModelInfo); ok {
apiConfig.PlanModeOpenRouterModelInfo = openRouterInfo
apiConfig.ActModeOpenRouterModelInfo = openRouterInfo
} else if ocaInfo, ok := modelInfo.(*cline.OcaModelInfo); ok {
apiConfig.PlanModeOcaModelInfo = ocaInfo
apiConfig.ActModeOcaModelInfo = ocaInfo
}
}
@@ -426,6 +429,9 @@ func UpdateProviderPartial(ctx context.Context, manager *task.Manager, provider
if openRouterInfo, ok := updates.ModelInfo.(*cline.OpenRouterModelInfo); ok {
apiConfig.PlanModeOpenRouterModelInfo = openRouterInfo
apiConfig.ActModeOpenRouterModelInfo = openRouterInfo
} else if ocaInfo, ok := updates.ModelInfo.(*cline.OcaModelInfo); ok {
apiConfig.PlanModeOcaModelInfo = ocaInfo
apiConfig.ActModeOcaModelInfo = ocaInfo
}
}
+7 -8
View File
@@ -190,7 +190,7 @@ func (pw *ProviderWizard) handleAddOcaProvider() error {
}
// Step 3: Select model
modelID, _, err := pw.selectModel(cline.ApiProvider_OCA, "")
modelID, modelInfo, err := pw.selectModel(cline.ApiProvider_OCA, "")
if err != nil {
return fmt.Errorf("model selection failed: %w", err)
}
@@ -198,7 +198,7 @@ func (pw *ProviderWizard) handleAddOcaProvider() error {
// Step 4: Apply the OCA model configuration and set as active
updates := ProviderUpdatesPartial{
ModelID: &modelID,
ModelInfo: nil,
ModelInfo: modelInfo,
}
if err := UpdateProviderPartial(pw.ctx, pw.manager, cline.ApiProvider_OCA, updates, true); err != nil {
@@ -215,7 +215,7 @@ func (pw *ProviderWizard) handleAddOcaProvider() error {
// handleListProviders retrieves and displays configured providers
func (pw *ProviderWizard) handleListProviders() error {
result, err := GetProviderConfigurations(pw.ctx, pw.manager)
result, err := GetProviderConfigurations(pw.ctx)
if err != nil {
return fmt.Errorf("failed to retrieve provider configurations: %w", err)
}
@@ -413,7 +413,7 @@ func (pw *ProviderWizard) manualModelEntry(provider cline.ApiProvider) (string,
// handleChangeModel allows changing the model for any configured provider
func (pw *ProviderWizard) handleChangeModel() error {
// Step 1: Get current provider configurations
result, err := GetProviderConfigurations(pw.ctx, pw.manager)
result, err := GetProviderConfigurations(pw.ctx)
if err != nil {
return fmt.Errorf("failed to retrieve provider configurations: %w", err)
}
@@ -585,11 +585,11 @@ 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 {
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"
}
@@ -648,7 +648,7 @@ func convertMapToOpenRouterModelInfo(data map[string]interface{}) *cline.OpenRou
// handleRemoveProvider allows removing a configured provider by clearing its API key
func (pw *ProviderWizard) handleRemoveProvider() error {
// Step 1: Get current provider configurations
result, err := GetProviderConfigurations(pw.ctx, pw.manager)
result, err := GetProviderConfigurations(pw.ctx)
if err != nil {
return fmt.Errorf("failed to retrieve provider configurations: %w", err)
}
@@ -748,7 +748,6 @@ 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 {
+3 -3
View File
@@ -40,7 +40,7 @@ func ensureConfigManager(ctx context.Context, address string) error {
}
// Always set the instance we're using as the default
registry := global.Clients.GetRegistry()
registry := global.Instances.GetRegistry()
if err := registry.SetDefaultInstance(instanceAddress); err != nil {
// Log warning but don't fail - this is not critical
fmt.Printf("Warning: failed to set default instance: %v\n", err)
@@ -123,11 +123,11 @@ 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),
Args: cobra.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
+2 -3
View File
@@ -32,8 +32,8 @@ func NewManager(ctx context.Context, address string) (*Manager, error) {
// Get the actual address being used
clientAddress := address
if address == "" && global.Clients != nil {
clientAddress = global.Clients.GetRegistry().GetDefaultInstance()
if address == "" && global.Instances != nil {
clientAddress = global.Instances.GetRegistry().GetDefaultInstance()
}
return &Manager{
@@ -110,7 +110,6 @@ func (m *Manager) ListSettings(ctx context.Context) error {
"dictationSettings",
"autoCondenseThreshold",
"autoApprovalSettings",
"hooksEnabled",
}
// Render each field using the renderer
+1 -1
View File
@@ -79,7 +79,7 @@ func RenderField(key string, value interface{}, censor bool) error {
"planActSeparateModelsSetting", "enableCheckpointsSetting",
"terminalReuseEnabled", "mcpResponsesCollapsed", "strictPlanModeEnabled",
"useAutoCondense", "yoloModeToggled", "shellIntegrationTimeout",
"terminalOutputLineLimit", "autoCondenseThreshold", "hooksEnabled":
"terminalOutputLineLimit", "autoCondenseThreshold":
fmt.Printf("%s: %s\n", camelToKebab(key), formatValue(value, key, censor))
return nil
+144
View File
@@ -0,0 +1,144 @@
package display
import (
"fmt"
"strings"
"github.com/cline/cli/pkg/cli/types"
)
// HookRenderer renders hook status messages in a CLI-native style.
//
// Goals:
// - Match ToolRenderers markdown look
// - Keep executions ungrouped
// - Render status + high-signal metadata (script paths, error summary)
//
// Note: hook stdout/stderr currently arrives as separate `hook_output_stream` messages.
// The CLI suppresses those by default and prints them only in --verbose mode.
// Future work could group streamed output under the corresponding hook block.
//
// It returns markdown (or rendered markdown when enabled); callers should print the
// returned string.
type HookRenderer struct {
mdRenderer *MarkdownRenderer
outputFormat string
}
func NewHookRenderer(mdRenderer *MarkdownRenderer, outputFormat string) *HookRenderer {
return &HookRenderer{mdRenderer: mdRenderer, outputFormat: outputFormat}
}
func (hr *HookRenderer) RenderHookStatus(h types.HookMessage) string {
statusText := strings.TrimSpace(h.Status)
if statusText == "" {
statusText = "unknown"
}
// Header: aligned with ToolRenderers phrasing so transcripts scan consistently.
// Example: "### Cline hook completed: PreToolUse (tool: read_file) (exit 0)"
var headerBuilder strings.Builder
headerBuilder.WriteString(fmt.Sprintf("### Cline hook %s: %s", statusText, h.HookName))
if h.ToolName != "" {
headerBuilder.WriteString(" ")
headerBuilder.WriteString(fmt.Sprintf("(tool: %s)", h.ToolName))
}
if statusText == "failed" && h.ExitCode != 0 {
headerBuilder.WriteString(" ")
headerBuilder.WriteString(fmt.Sprintf("(exit %d)", h.ExitCode))
}
header := headerBuilder.String()
var lines []string
lines = append(lines, header)
// Pending tool info (PreToolUse): show one high-signal line directly under the header.
if h.PendingToolInfo != nil {
if pending := hr.formatPendingToolInfo(h.PendingToolInfo); pending != "" {
lines = append(lines, fmt.Sprintf("- Pending: %s", pending))
}
}
// Script paths: one per line.
paths := make([]string, 0, len(h.ScriptPaths))
for _, p := range h.ScriptPaths {
p = strings.TrimSpace(p)
if p != "" {
paths = append(paths, p)
}
}
if len(paths) == 0 {
// Fallback when no script paths are provided.
lines = append(lines, "- *(no hook scripts found)*")
} else {
for _, p := range paths {
lines = append(lines, fmt.Sprintf("- Running hook: `%s`", p))
}
}
// On failure, show a minimal summary (full stderr reserved for verbose).
if statusText == "failed" && h.Error != nil {
if msg := strings.TrimSpace(h.Error.Message); msg != "" {
lines = append(lines, fmt.Sprintf("- Error: %s", msg))
}
// If we have a specific script path, include it as a hint.
if sp := strings.TrimSpace(h.Error.ScriptPath); sp != "" {
lines = append(lines, fmt.Sprintf("- Script: `%s`", sp))
}
}
markdown := strings.Join(lines, "\n")
return hr.renderMarkdown(markdown)
}
func (hr *HookRenderer) formatPendingToolInfo(info *types.ToolInfo) string {
if info == nil {
return ""
}
tool := strings.TrimSpace(info.Tool)
if tool == "" {
return ""
}
// Keep this intentionally compact and readable.
// Format: "<tool> <identifier>" where identifier is the most relevant param.
var ident string
switch {
case strings.TrimSpace(info.Path) != "":
ident = strings.TrimSpace(info.Path)
case strings.TrimSpace(info.Command) != "":
ident = strings.TrimSpace(info.Command)
case strings.TrimSpace(info.Url) != "":
ident = strings.TrimSpace(info.Url)
case strings.TrimSpace(info.McpTool) != "" && strings.TrimSpace(info.McpServer) != "":
ident = fmt.Sprintf("%s %s", strings.TrimSpace(info.McpServer), strings.TrimSpace(info.McpTool))
case strings.TrimSpace(info.ResourceUri) != "":
ident = strings.TrimSpace(info.ResourceUri)
case strings.TrimSpace(info.Regex) != "":
ident = strings.TrimSpace(info.Regex)
default:
ident = ""
}
if ident != "" {
return fmt.Sprintf("%s %s", tool, ident)
}
return tool
}
func (hr *HookRenderer) renderMarkdown(markdown string) string {
// Align with ToolRenderer: in plain mode or non-TTY, return markdown as-is.
if hr.outputFormat == "plain" || !isTTY() {
return markdown
}
if hr.mdRenderer == nil {
return markdown
}
rendered, err := hr.mdRenderer.Render(markdown)
if err != nil {
return markdown
}
return rendered
}
+69
View File
@@ -0,0 +1,69 @@
package display
import (
"strings"
"testing"
"github.com/cline/cli/pkg/cli/types"
)
func TestHookRenderer_RenderHookStatus_FailedShowsErrorAndScript(t *testing.T) {
hr := NewHookRenderer(nil, "plain")
msg := hr.RenderHookStatus(types.HookMessage{
HookName: "PreToolUse",
ToolName: "execute_command",
Status: "failed",
ExitCode: 2,
ScriptPaths: []string{"repo/.clinerules/hooks/PreToolUse"},
Error: &types.HookError{
Message: "boom",
ScriptPath: "repo/.clinerules/hooks/PreToolUse",
},
})
if !strings.Contains(msg, "### Cline hook failed: PreToolUse") {
t.Fatalf("expected header in rendered output, got: %q", msg)
}
if !strings.Contains(msg, "- Error: boom") {
t.Fatalf("expected error line in rendered output, got: %q", msg)
}
if !strings.Contains(msg, "- Script: `repo/.clinerules/hooks/PreToolUse`") {
t.Fatalf("expected script line in rendered output, got: %q", msg)
}
}
func TestHookRenderer_RenderHookStatus_PendingToolInfoAppearsDirectlyUnderHeader(t *testing.T) {
hr := NewHookRenderer(nil, "plain")
msg := hr.RenderHookStatus(types.HookMessage{
HookName: "PreToolUse",
ToolName: "write_to_file",
Status: "running",
PendingToolInfo: &types.ToolInfo{
Tool: "write_to_file",
Path: "src/foo.ts",
},
ScriptPaths: []string{"repo/.clinerules/hooks/PreToolUse"},
})
header := "### Cline hook running: PreToolUse"
pending := "- Pending: write_to_file src/foo.ts"
runningHook := "- Running hook: `repo/.clinerules/hooks/PreToolUse`"
headerIdx := strings.Index(msg, header)
if headerIdx == -1 {
t.Fatalf("expected header %q in output, got: %q", header, msg)
}
pendingIdx := strings.Index(msg, pending)
if pendingIdx == -1 {
t.Fatalf("expected pending line %q in output, got: %q", pending, msg)
}
runningIdx := strings.Index(msg, runningHook)
if runningIdx == -1 {
t.Fatalf("expected running hook line %q in output, got: %q", runningHook, msg)
}
if !(headerIdx < pendingIdx && pendingIdx < runningIdx) {
t.Fatalf("expected header < pending < runningHook ordering, got indexes header=%d pending=%d running=%d\nfull=%q", headerIdx, pendingIdx, runningIdx, msg)
}
}
+13 -3
View File
@@ -39,9 +39,12 @@ func NewStreamingSegment(sayType, prefix string, mdRenderer *MarkdownRenderer, s
// Render rich header immediately when creating segment (if in rich mode and TTY)
if shouldMarkdown && outputFormat != "plain" && isTTY() {
header := ss.generateRichHeader()
rendered, _ := mdRenderer.Render(header)
output.Println("")
output.Print(rendered)
// Skip empty headers.
if strings.TrimSpace(header) != "" {
rendered, _ := mdRenderer.Render(header)
output.Println("")
output.Print(rendered)
}
}
return ss
@@ -110,6 +113,9 @@ func (ss *StreamingSegment) renderFinal(currentBuffer string) {
if err := json.Unmarshal([]byte(currentBuffer), &tool); err == nil {
bodyContent = ss.toolRenderer.GenerateToolContentBody(&tool)
}
} else if ss.sayType == string(types.SayTypeHookStatus) {
// Hooks are rendered via the state stream; nothing to render here.
bodyContent = ""
} else if ss.sayType == string(types.SayTypeCommand) {
// Command output
bodyContent = "```shell\n" + currentBuffer + "\n```"
@@ -160,6 +166,10 @@ func (ss *StreamingSegment) generateRichHeader() string {
case string(types.SayTypeTool):
return ss.generateToolHeader()
case string(types.SayTypeHookStatus):
// Hooks are rendered from the state stream; dont emit a partial-stream header.
return ""
case "ask":
// Check the specific ask type
@@ -0,0 +1,20 @@
package display
import (
"testing"
"github.com/cline/cli/pkg/cli/types"
)
func TestStreamingSegment_generateRichHeader_HookIsEmpty(t *testing.T) {
ss := &StreamingSegment{
sayType: string(types.SayTypeHookStatus),
prefix: "HOOK",
msg: &types.ClineMessage{},
}
header := ss.generateRichHeader()
if header != "" {
t.Fatalf("expected empty header for hook segments to avoid double-render, got: %q", header)
}
}
+19 -1
View File
@@ -38,6 +38,18 @@ func (s *StreamingDisplay) HandlePartialMessage(msg *types.ClineMessage) error {
s.mu.Lock()
defer s.mu.Unlock()
// Render hooks from the state stream only (not partial stream) to avoid duplicates.
//
// Rationale: hook status messages are often updated/reordered by the backend (e.g. PreToolUse
// hooks are moved above the corresponding tool message). The state stream represents the
// authoritative, “final” message ordering, while the partial stream is best-effort for
// incremental display.
//
// Only suppress *partial* hook messages; complete ones still flow through dedupe.
if msg.Partial && msg.Say == string(types.SayTypeHookStatus) {
return nil
}
// Check for deduplication
if s.dedupe.IsDuplicate(msg) {
return nil
@@ -91,7 +103,11 @@ func (s *StreamingDisplay) HandlePartialMessage(msg *types.ClineMessage) error {
func (s *StreamingDisplay) shouldRenderMarkdown(sayType string) bool {
switch sayType {
case string(types.SayTypeReasoning), string(types.SayTypeText), string(types.SayTypeCompletionResult), string(types.SayTypeTool), "ask":
case string(types.SayTypeReasoning),
string(types.SayTypeText),
string(types.SayTypeCompletionResult),
string(types.SayTypeTool),
"ask":
return true
default:
return false
@@ -110,6 +126,8 @@ func (s *StreamingDisplay) getPrefix(sayType string) string {
return "ASK"
case string(types.SayTypeCommand):
return "TERMINAL"
case string(types.SayTypeHookStatus):
return "HOOK"
default:
return strings.ToUpper(sayType)
}
+1 -1
View File
@@ -264,6 +264,6 @@ func (sr *SystemMessageRenderer) RenderInfo(title, message string) error {
func (sr *SystemMessageRenderer) RenderCheckpoint(timestamp string, id int64) error {
markdown := fmt.Sprintf("## [%s] Checkpoint created `%d`", timestamp, id)
rendered := sr.renderer.RenderMarkdown(markdown)
fmt.Printf(rendered)
fmt.Print(rendered)
return nil
}
+7
View File
@@ -339,6 +339,13 @@ func (tr *ToolRenderer) RenderCommandOutput(output string) string {
return result.String()
}
func (tr *ToolRenderer) RenderCommandPermissionDenied(command string) string {
command = strings.TrimSpace(command)
rendered := tr.renderMarkdown("### Command was denied")
message := fmt.Sprintf("Cline does not have permission to execute this command: `%s`", command)
return fmt.Sprintf("\n%s\n\n%s\n", rendered, message)
}
// RenderUserResponse renders user approval/rejection feedback
func (tr *ToolRenderer) RenderUserResponse(approved bool, feedback string) string {
var symbol, status string
@@ -14,21 +14,20 @@ import (
"github.com/cline/grpc-go/cline"
)
// ClineClients manages Cline instances using the new registry system
type ClineClients struct {
registry *ClientRegistry
type ClineInstances struct {
registry *InstanceRegistry
}
// NewClineClients creates a new ClineClients instance
func NewClineClients(configPath string) *ClineClients {
registry := NewClientRegistry(configPath)
return &ClineClients{
// NewClineInstances creates a new ClineInstances instance
func NewClineInstances(configPath string) *ClineInstances {
registry := NewInstanceRegistry(configPath)
return &ClineInstances{
registry: registry,
}
}
// Initialize performs cleanup of stale instances
func (c *ClineClients) Initialize(ctx context.Context) error {
func (c *ClineInstances) Initialize(ctx context.Context) error {
// Clean up stale entries (direct SQLite operations)
_ = c.registry.CleanupStaleInstances(ctx)
@@ -36,7 +35,8 @@ func (c *ClineClients) Initialize(ctx context.Context) error {
}
// StartNewInstance starts a new Cline instance and waits for cline-core to self-register
func (c *ClineClients) StartNewInstance(ctx context.Context, workspaces ...string) (*common.CoreInstanceInfo, error) {
// An "instance" is a pair of cline-core and cline-host processes
func (c *ClineInstances) StartNewInstance(ctx context.Context, workspaces ...string) (*common.CoreInstanceInfo, error) {
// Find available ports
corePort, hostPort, err := common.FindAvailablePortPair()
if err != nil {
@@ -102,7 +102,7 @@ func (c *ClineClients) StartNewInstance(ctx context.Context, workspaces ...strin
if Config.Verbose {
fmt.Println("Services started and registered successfully!")
fmt.Printf(" Address: %s\n", instance.Address)
fmt.Printf(" Address: %s\n", instance.CoreAddress)
fmt.Printf(" Core Port: %d\n", instance.CorePort())
fmt.Printf(" Host Bridge Port: %d\n", instance.HostPort())
fmt.Printf(" Process PID: %d\n", coreCmd.Process.Pid)
@@ -120,7 +120,7 @@ func (c *ClineClients) StartNewInstance(ctx context.Context, workspaces ...strin
}
// StartNewInstanceAtPort starts a new Cline instance at the specified port and waits for self-registration
func (c *ClineClients) StartNewInstanceAtPort(ctx context.Context, corePort int, workspaces ...string) (*common.CoreInstanceInfo, error) {
func (c *ClineInstances) StartNewInstanceAtPort(ctx context.Context, corePort int, workspaces ...string) (*common.CoreInstanceInfo, error) {
// Find available host port (core port + 1000)
hostPort := corePort + 1000
coreAddress := fmt.Sprintf("localhost:%d", corePort)
@@ -189,7 +189,7 @@ func (c *ClineClients) StartNewInstanceAtPort(ctx context.Context, corePort int,
if Config.Verbose {
fmt.Println("Services started and registered successfully!")
fmt.Printf(" Address: %s\n", instance.Address)
fmt.Printf(" Address: %s\n", instance.CoreAddress)
fmt.Printf(" Core Port: %d\n", instance.CorePort())
fmt.Printf(" Host Bridge Port: %d\n", instance.HostPort())
fmt.Printf(" Process PID: %d\n", coreCmd.Process.Pid)
@@ -207,12 +207,12 @@ func (c *ClineClients) StartNewInstanceAtPort(ctx context.Context, corePort int,
}
// GetRegistry returns the client registry
func (c *ClineClients) GetRegistry() *ClientRegistry {
func (c *ClineInstances) GetRegistry() *InstanceRegistry {
return c.registry
}
// EnsureInstanceAtAddress ensures an instance exists at the given address, starting one if needed
func (c *ClineClients) EnsureInstanceAtAddress(ctx context.Context, address string) error {
func (c *ClineInstances) EnsureInstanceAtAddress(ctx context.Context, address string) error {
// Expect host:port everywhere
normalized := address
if normalized == "" {
@@ -230,7 +230,7 @@ func (c *ClineClients) EnsureInstanceAtAddress(ctx context.Context, address stri
return fmt.Errorf("invalid address format %s", address)
}
// Use IPv6-compatible localhost detection
// Use IPv6-compatible localhost detection
if common.IsLocalAddress(host) {
_, err := c.StartNewInstanceAtPort(ctx, port)
if err != nil {
@@ -305,7 +305,7 @@ func startClineHost(hostPort int, workspaces []string) (*exec.Cmd, error) {
}
// KillInstanceByAddress kills a Cline instance by its address
func KillInstanceByAddress(ctx context.Context, registry *ClientRegistry, address string) error {
func KillInstanceByAddress(ctx context.Context, registry *InstanceRegistry, address string) error {
// Check if the instance exists in the registry
_, err := registry.GetInstance(address)
if err != nil {
@@ -355,9 +355,9 @@ func KillInstanceByAddress(ctx context.Context, registry *ClientRegistry, addres
defaultInstance := registry.GetDefaultInstance()
if defaultInstance == address || defaultInstance == "" {
if len(instances) > 0 {
if err := registry.SetDefaultInstance(instances[0].Address); err == nil {
if err := registry.SetDefaultInstance(instances[0].CoreAddress); err == nil {
if Config.Verbose {
fmt.Printf("Updated default instance to: %s\n", instances[0].Address)
fmt.Printf("Updated default instance to: %s\n", instances[0].CoreAddress)
}
}
}
+21 -15
View File
@@ -22,8 +22,8 @@ type GlobalConfig struct {
}
var (
Config *GlobalConfig
Clients *ClineClients
Config *GlobalConfig
Instances *ClineInstances
// Version info - set at build time via ldflags
// Version is the Cline Core version (from root package.json)
@@ -37,11 +37,16 @@ var (
func InitializeGlobalConfig(cfg *GlobalConfig) error {
if cfg.ConfigPath == "" {
homeDir, err := os.UserHomeDir()
if err != nil {
return fmt.Errorf("failed to get home directory: %w", err)
// Check CLINE_DIR environment variable first
if clineDir := os.Getenv("CLINE_DIR"); clineDir != "" {
cfg.ConfigPath = clineDir
} else {
homeDir, err := os.UserHomeDir()
if err != nil {
return fmt.Errorf("failed to get home directory: %w", err)
}
cfg.ConfigPath = filepath.Join(homeDir, ".cline")
}
cfg.ConfigPath = filepath.Join(homeDir, ".cline")
}
// Ensure .cline directory exists
@@ -56,11 +61,11 @@ func InitializeGlobalConfig(cfg *GlobalConfig) error {
// Otherwise lipgloss auto-detects terminal capabilities (default behavior)
Config = cfg
Clients = NewClineClients(cfg.ConfigPath)
Instances = NewClineInstances(cfg.ConfigPath)
// Initialize the clients registry
ctx := context.Background()
if err := Clients.Initialize(ctx); err != nil {
if err := Instances.Initialize(ctx); err != nil {
return fmt.Errorf("failed to initialize clients: %w", err)
}
@@ -71,25 +76,25 @@ func InitializeGlobalConfig(cfg *GlobalConfig) error {
func GetDefaultClient(ctx context.Context) (*client.ClineClient, error) {
if Config.CoreAddress != "" && Config.CoreAddress != fmt.Sprintf("localhost:%d", common.DEFAULT_CLINE_CORE_PORT) {
// User specified a specific address, use that
return Clients.GetRegistry().GetClient(ctx, Config.CoreAddress)
return Instances.GetRegistry().GetClient(ctx, Config.CoreAddress)
}
// Use the default instance from registry
return Clients.GetRegistry().GetDefaultClient(ctx)
return Instances.GetRegistry().GetDefaultClient(ctx)
}
// GetClientForAddress returns a client for a specific address
func GetClientForAddress(ctx context.Context, address string) (*client.ClineClient, error) {
return Clients.GetRegistry().GetClient(ctx, address)
return Instances.GetRegistry().GetClient(ctx, address)
}
// EnsureDefaultInstance ensures a default instance exists
func EnsureDefaultInstance(ctx context.Context) error {
if Clients == nil {
if Instances == nil {
return fmt.Errorf("global clients not initialized")
}
registry := Clients.GetRegistry()
registry := Instances.GetRegistry()
// First, check if there are any instances already registered in SQLite
instances := registry.ListInstances()
@@ -103,11 +108,12 @@ func EnsureDefaultInstance(ctx context.Context) error {
if registry.GetDefaultInstance() == "" {
// No instances exist, start a new one
// Note: StartNewInstance will automatically set it as default since it's the first instance
_, err := Clients.StartNewInstance(ctx)
_, err := Instances.StartNewInstance(ctx)
if err != nil {
return fmt.Errorf("failed to start new default instance: %w", err)
}
}
return nil
}
}
+27 -28
View File
@@ -18,28 +18,28 @@ import (
"google.golang.org/grpc/health/grpc_health_v1"
)
// ClientRegistry manages Cline client connections using direct SQLite operations
type ClientRegistry struct {
// InstanceRegistry manages Cline client connections using direct SQLite operations
type InstanceRegistry struct {
lockManager *sqlite.LockManager
configPath string
}
// NewClientRegistry creates a new client registry
func NewClientRegistry(configPath string) *ClientRegistry {
// NewInstanceRegistry creates a new instance registry
func NewInstanceRegistry(configPath string) *InstanceRegistry {
lockManager, err := sqlite.NewLockManager(configPath)
if err != nil {
// Log error but continue - we can still function without SQLite
log.Fatalf("Warning: Failed to initialize SQLite lock manager: %v\n", err)
}
return &ClientRegistry{
return &InstanceRegistry{
lockManager: lockManager,
configPath: configPath,
}
}
// GetDefaultInstance returns the default instance address from settings file
func (r *ClientRegistry) GetDefaultInstance() string {
func (r *InstanceRegistry) GetDefaultInstance() string {
defaultAddr, err := sqlite.GetDefaultInstance(r.configPath)
if err != nil {
return ""
@@ -48,7 +48,7 @@ func (r *ClientRegistry) GetDefaultInstance() string {
}
// SetDefaultInstance sets the default instance (writes default.json)
func (r *ClientRegistry) SetDefaultInstance(address string) error {
func (r *InstanceRegistry) SetDefaultInstance(address string) error {
// Verify the instance exists in SQLite
if r.lockManager != nil {
exists, err := r.lockManager.HasInstanceAtAddress(address)
@@ -64,7 +64,7 @@ func (r *ClientRegistry) SetDefaultInstance(address string) error {
}
// GetInstance returns instance information directly from SQLite
func (r *ClientRegistry) GetInstance(address string) (*common.CoreInstanceInfo, error) {
func (r *InstanceRegistry) GetInstance(address string) (*common.CoreInstanceInfo, error) {
if r.lockManager == nil {
return nil, fmt.Errorf("lock manager not available")
}
@@ -73,7 +73,7 @@ func (r *ClientRegistry) GetInstance(address string) (*common.CoreInstanceInfo,
}
// GetClient returns a connected client for the given address (created on-demand)
func (r *ClientRegistry) GetClient(ctx context.Context, address string) (*client.ClineClient, error) {
func (r *InstanceRegistry) GetClient(ctx context.Context, address string) (*client.ClineClient, error) {
// Verify instance exists in SQLite
if r.lockManager != nil {
exists, err := r.lockManager.HasInstanceAtAddress(address)
@@ -104,7 +104,7 @@ func (r *ClientRegistry) GetClient(ctx context.Context, address string) (*client
}
// GetDefaultClient returns a client for the default instance
func (r *ClientRegistry) GetDefaultClient(ctx context.Context) (*client.ClineClient, error) {
func (r *InstanceRegistry) GetDefaultClient(ctx context.Context) (*client.ClineClient, error) {
defaultAddr := r.GetDefaultInstance()
if defaultAddr == "" {
return nil, fmt.Errorf("no default instance configured")
@@ -117,7 +117,7 @@ func (r *ClientRegistry) GetDefaultClient(ctx context.Context) (*client.ClineCli
// Database is unavailable - Return error instead of attempting cleanup
return nil, fmt.Errorf("cannot verify default instance: database unavailable: %w", err)
}
if !exists {
// Instance doesn't exist in database but config file references it
// This is a stale config - remove it and try to find another instance
@@ -127,14 +127,14 @@ func (r *ClientRegistry) GetDefaultClient(ctx context.Context) (*client.ClineCli
} else {
fmt.Printf("Removed stale default instance config (instance %s not found in database)\n", defaultAddr)
}
// Try to find and set a new default instance
instances := r.ListInstances()
if len(instances) > 0 {
if err := r.EnsureDefaultInstance(instances); err != nil {
return nil, fmt.Errorf("failed to set new default instance: %w", err)
}
// Retry with the new default
newDefaultAddr := r.GetDefaultInstance()
if newDefaultAddr != "" {
@@ -142,7 +142,7 @@ func (r *ClientRegistry) GetDefaultClient(ctx context.Context) (*client.ClineCli
return r.GetClient(ctx, newDefaultAddr)
}
}
return nil, fmt.Errorf("no default instance configured")
}
}
@@ -151,7 +151,7 @@ func (r *ClientRegistry) GetDefaultClient(ctx context.Context) (*client.ClineCli
}
// ListInstances returns all registered instances directly from SQLite
func (r *ClientRegistry) ListInstances() []*common.CoreInstanceInfo {
func (r *InstanceRegistry) ListInstances() []*common.CoreInstanceInfo {
if r.lockManager == nil {
return []*common.CoreInstanceInfo{}
}
@@ -170,7 +170,7 @@ func (r *ClientRegistry) ListInstances() []*common.CoreInstanceInfo {
}
// HasInstanceAtAddress checks if an instance exists at the given address (delegates to SQLite)
func (r *ClientRegistry) HasInstanceAtAddress(address string) bool {
func (r *InstanceRegistry) HasInstanceAtAddress(address string) bool {
if r.lockManager == nil {
return false
}
@@ -185,7 +185,7 @@ func (r *ClientRegistry) HasInstanceAtAddress(address string) bool {
}
// CleanupStaleInstances removes stale instances using direct SQLite operations
func (r *ClientRegistry) CleanupStaleInstances(ctx context.Context) error {
func (r *InstanceRegistry) CleanupStaleInstances(ctx context.Context) error {
if r.lockManager == nil {
return nil
}
@@ -202,15 +202,15 @@ func (r *ClientRegistry) CleanupStaleInstances(ctx context.Context) error {
// Try to gracefully shutdown the paired host process before cleanup
fmt.Printf("Attempting to shutdown dangling host service %s for stale cline core instance %s\n",
instance.HostServiceAddress, instance.Address)
instance.HostServiceAddress, instance.CoreAddress)
r.tryShutdownHostProcess(instance.HostServiceAddress)
// Remove from SQLite database
if err := r.lockManager.RemoveInstanceLock(instance.Address); err != nil {
return fmt.Errorf("failed to remove stale instance %s: %w", instance.Address, err)
if err := r.lockManager.RemoveInstanceLock(instance.CoreAddress); err != nil {
return fmt.Errorf("failed to remove stale instance %s: %w", instance.CoreAddress, err)
}
fmt.Printf("Removed stale instance: %s\n", instance.Address)
fmt.Printf("Removed stale instance: %s\n", instance.CoreAddress)
}
}
@@ -218,8 +218,7 @@ func (r *ClientRegistry) CleanupStaleInstances(ctx context.Context) error {
}
// tryShutdownHostProcess attempts to gracefully shutdown a host process via RPC
// Best effort, don't throw errors i guess
func (r *ClientRegistry) tryShutdownHostProcess(hostServiceAddress string) {
func (r *InstanceRegistry) tryShutdownHostProcess(hostServiceAddress string) {
err := common.RetryOperation(3, 2*time.Second, func() error {
// Create context with timeout
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
@@ -252,7 +251,7 @@ func (r *ClientRegistry) tryShutdownHostProcess(hostServiceAddress string) {
}
// ListInstancesCleaned performs cleanup and returns instances with health checks
func (r *ClientRegistry) ListInstancesCleaned(ctx context.Context) ([]*common.CoreInstanceInfo, error) {
func (r *InstanceRegistry) ListInstancesCleaned(ctx context.Context) ([]*common.CoreInstanceInfo, error) {
// 1. Clean up stale entries (best-effort)
_ = r.CleanupStaleInstances(ctx)
@@ -268,7 +267,7 @@ func (r *ClientRegistry) ListInstancesCleaned(ctx context.Context) ([]*common.Co
}
// EnsureDefaultInstance ensures a default instance is set if instances exist but no default is configured
func (r *ClientRegistry) EnsureDefaultInstance(instances []*common.CoreInstanceInfo) error {
func (r *InstanceRegistry) EnsureDefaultInstance(instances []*common.CoreInstanceInfo) error {
currentDefault := r.GetDefaultInstance()
// If we have no instances, clear any stale default and remove settings file
@@ -283,13 +282,13 @@ func (r *ClientRegistry) EnsureDefaultInstance(instances []*common.CoreInstanceI
// If we have instances but no default, pick the first one
if currentDefault == "" {
return sqlite.SetDefaultInstance(r.configPath, instances[0].Address)
return sqlite.SetDefaultInstance(r.configPath, instances[0].CoreAddress)
}
// Validate current default still exists in the instances
defaultExists := false
for _, instance := range instances {
if instance.Address == currentDefault {
if instance.CoreAddress == currentDefault {
defaultExists = true
break
}
@@ -297,7 +296,7 @@ func (r *ClientRegistry) EnsureDefaultInstance(instances []*common.CoreInstanceI
if !defaultExists {
// Current default doesn't exist, pick a new one from available instances
return sqlite.SetDefaultInstance(r.configPath, instances[0].Address)
return sqlite.SetDefaultInstance(r.configPath, instances[0].CoreAddress)
}
return nil
+1
View File
@@ -25,6 +25,7 @@ type DisplayContext struct {
State *types.ConversationState
Renderer *display.Renderer
ToolRenderer *display.ToolRenderer
HookRenderer *display.HookRenderer
SystemRenderer *display.SystemMessageRenderer
IsLast bool
IsPartial bool
+40 -12
View File
@@ -6,8 +6,8 @@ import (
"strings"
"github.com/cline/cli/pkg/cli/clerror"
"github.com/cline/cli/pkg/cli/types"
"github.com/cline/cli/pkg/cli/output"
"github.com/cline/cli/pkg/cli/types"
)
// SayHandler handles SAY type messages
@@ -90,6 +90,12 @@ func (h *SayHandler) Handle(msg *types.ClineMessage, dc *DisplayContext) error {
return h.handleInfo(msg, dc)
case string(types.SayTypeTaskProgress):
return h.handleTaskProgress(msg, dc)
case string(types.SayTypeHookStatus):
return h.handleHookStatus(msg, dc)
case string(types.SayTypeHookOutputStream):
return h.handleHookOutputStream(msg, dc)
case string(types.SayTypeCommandPermissionDenied):
return h.handleCommandPermissionDenied(msg, dc)
default:
return h.handleDefault(msg, dc)
}
@@ -242,18 +248,17 @@ func (h *SayHandler) handleCompletionResult(msg *types.ClineMessage, dc *Display
}
func formatUserMessage(text string) string {
lines := strings.Split(text, "\n")
// Wrap each line in backticks
for i, line := range lines {
if line != "" {
lines[i] = fmt.Sprintf("`%s`", line)
}
}
return strings.Join(lines, "\n")
}
lines := strings.Split(text, "\n")
// Wrap each line in backticks
for i, line := range lines {
if line != "" {
lines[i] = fmt.Sprintf("`%s`", line)
}
}
return strings.Join(lines, "\n")
}
// handleUserFeedback handles user feedback messages
func (h *SayHandler) handleUserFeedback(msg *types.ClineMessage, dc *DisplayContext) error {
@@ -343,6 +348,18 @@ func (h *SayHandler) handleCommandOutput(msg *types.ClineMessage, dc *DisplayCon
return nil
}
func (h *SayHandler) handleCommandPermissionDenied(msg *types.ClineMessage, dc *DisplayContext) error {
if msg.Text == "" {
return nil
}
// Use unified ToolRenderer
rendered := dc.ToolRenderer.RenderCommandPermissionDenied(msg.Text)
output.Print(rendered)
return nil
}
func (h *SayHandler) handleTool(msg *types.ClineMessage, dc *DisplayContext) error {
var tool types.ToolMessage
if err := json.Unmarshal([]byte(msg.Text), &tool); err != nil {
@@ -517,5 +534,16 @@ func (h *SayHandler) handleTaskProgress(msg *types.ClineMessage, dc *DisplayCont
// handleDefault handles unknown SAY message types
func (h *SayHandler) handleDefault(msg *types.ClineMessage, dc *DisplayContext) error {
// Debug: log unhandled say types to help identify missing cases using output.Printf for CLI consistency
if dc.Verbose {
output.Printf("[DEBUG] Unhandled SAY type: '%s' (text preview: %s)\n", msg.Say, truncateForDisplay(msg.Text, 50))
}
return dc.Renderer.RenderMessage("SAY", msg.Text, true)
}
func truncateForDisplay(text string, maxLen int) string {
if len(text) <= maxLen {
return text
}
return text[:maxLen] + "..."
}
+198
View File
@@ -0,0 +1,198 @@
package handlers
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/cline/cli/pkg/cli/output"
"github.com/cline/cli/pkg/cli/types"
)
// Hook-specific SAY handlers and helpers.
// Kept in a separate file to keep say_handlers.go focused on routing.
// handleHookStatus handles hook execution status messages.
func (h *SayHandler) handleHookStatus(msg *types.ClineMessage, dc *DisplayContext) error {
hook, err := parseHookMessage(msg.Text)
if err != nil {
// Fallback to basic output if JSON parsing fails
return dc.Renderer.RenderMessage("HOOK", msg.Text, true)
}
logHookDebug(hook, dc)
hook.ScriptPaths = formatHookPaths(hook.ScriptPaths)
return renderHookStatus(hook, dc)
}
// handleHookOutputStream handles streaming output from hooks.
//
// Hook stdout/stderr currently arrives line-by-line from the backend as
// `hook_output_stream` messages. The CLI intentionally suppresses these by default
// to keep the transcript high-signal.
//
// In --verbose mode, we print each non-empty line prefixed with "HOOK>" for easy grepping.
// Future work could associate these lines with a specific hook execution and render them
// as a grouped section under the hook status header.
func (h *SayHandler) handleHookOutputStream(msg *types.ClineMessage, dc *DisplayContext) error {
if !dc.Verbose {
return nil
}
line := strings.TrimRight(msg.Text, "\n")
if strings.TrimSpace(line) == "" {
return nil
}
output.Printf("HOOK> %s\n", line)
return nil
}
func parseHookMessage(jsonText string) (types.HookMessage, error) {
var hook types.HookMessage
if err := json.Unmarshal([]byte(jsonText), &hook); err != nil {
return types.HookMessage{}, err
}
return hook, nil
}
func logHookDebug(hook types.HookMessage, dc *DisplayContext) {
if dc.Verbose {
output.Printf("[DEBUG] Hook parsed: name=%s, status=%s, toolName=%s, scriptPaths=%v\n",
hook.HookName, hook.Status, hook.ToolName, hook.ScriptPaths)
}
}
func formatHookPaths(paths []string) []string {
if len(paths) == 0 {
return paths
}
formatted := make([]string, 0, len(paths))
for _, p := range paths {
if strings.TrimSpace(p) == "" {
continue
}
formatted = append(formatted, formatHookPath(p))
}
return formatted
}
func renderHookStatus(hook types.HookMessage, dc *DisplayContext) error {
if dc.HookRenderer != nil {
rendered := dc.HookRenderer.RenderHookStatus(hook)
// Match ToolRenderers spacing: one leading newline, one trailing newline.
output.Print("\n")
output.Print(rendered)
output.Print("\n")
return nil
}
// Fallback: if HookRenderer not available
return dc.Renderer.RenderMessage("HOOK", fmt.Sprintf("%s %s", hook.HookName, hook.Status), true)
}
func formatHookPath(fullPath string) string {
// Normalize for display and prefix checks. This is display-only; do not use for IO.
normalized := normalizeSlashes(fullPath)
// If this is a repo-scoped hook script (i.e. lives under <repo>/.clinerules/hooks/),
// always include the repo name for disambiguation even in single-repo workspaces.
//
// This intentionally runs before workspace-relative formatting, which would otherwise
// collapse to ".clinerules/hooks/..." and lose the repo context.
if p, ok := tryRepoScopedHooksPath(normalized); ok {
return p
}
// Prefer workspace-relative paths first for readability, since most hook scripts
// live inside the current project.
if p, ok := tryWorkspaceRelativeHookPath(normalized); ok {
return p
}
// Follow existing CLI pattern: resolve home via os.UserHomeDir.
if p, ok := tryHomeTildePath(normalized); ok {
return p
}
// Secondary heuristic: if hook lives under <repo>/.clinerules, collapse to repo-relative.
if p, ok := tryRepoRelativeHookPath(normalized); ok {
return p
}
return fallbackLastComponents(normalized, 3)
}
func normalizeSlashes(p string) string {
return filepath.ToSlash(p)
}
func tryWorkspaceRelativeHookPath(normalizedPath string) (string, bool) {
root, err := os.Getwd()
if err != nil {
return "", false
}
// filepath.Rel expects OS-native paths, so we need to convert the normalized path
// back to OS-native format before calling Rel, then normalize the result for display.
targetOS := filepath.FromSlash(normalizedPath)
rel, err := filepath.Rel(root, targetOS)
if err != nil {
return "", false
}
// If it's not within the workspace, Rel will start with "..".
if rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) {
return "", false
}
return normalizeSlashes(rel), true
}
func tryHomeTildePath(normalizedPath string) (string, bool) {
homeDir, err := os.UserHomeDir()
if err != nil || strings.TrimSpace(homeDir) == "" {
return "", false
}
homeDir = normalizeSlashes(homeDir)
if !strings.HasPrefix(normalizedPath, homeDir) {
return "", false
}
rel := strings.TrimPrefix(normalizedPath, homeDir)
rel = strings.TrimPrefix(rel, "/")
return "~/" + rel, true
}
func tryRepoRelativeHookPath(normalizedPath string) (string, bool) {
parts := strings.Split(normalizedPath, "/")
for i, part := range parts {
if part == ".clinerules" && i > 0 {
repoName := parts[i-1]
return repoName + "/" + strings.Join(parts[i:], "/"), true
}
}
return "", false
}
// tryRepoScopedHooksPath returns a repo-prefixed path like
// "myrepo/.clinerules/hooks/PreToolUse" when the given path points to a hook script
// under a repo's .clinerules/hooks directory.
//
// This is more specific than tryRepoRelativeHookPath and is used to ensure hook script
// paths always include repo context.
func tryRepoScopedHooksPath(normalizedPath string) (string, bool) {
// Fast path check to avoid split work.
if !strings.Contains(normalizedPath, "/.clinerules/hooks/") {
return "", false
}
return tryRepoRelativeHookPath(normalizedPath)
}
func fallbackLastComponents(normalizedPath string, n int) string {
parts := strings.Split(normalizedPath, "/")
if len(parts) >= n {
return strings.Join(parts[len(parts)-n:], "/")
}
return normalizedPath
}
@@ -0,0 +1,41 @@
package handlers
import (
"os"
"path/filepath"
"testing"
)
func TestFormatHookPath_PrefersWorkspaceRelative(t *testing.T) {
// Create a stable workspace root (avoid TempDir's nested ".../001" patterns)
// so that workspace-relative formatting is deterministic.
root := filepath.Join(t.TempDir(), "workspace")
if err := os.MkdirAll(root, 0o755); err != nil {
t.Fatalf("MkdirAll: %v", err)
}
oldWd, err := os.Getwd()
if err != nil {
t.Fatalf("Getwd: %v", err)
}
defer func() { _ = os.Chdir(oldWd) }()
if err := os.Chdir(root); err != nil {
t.Fatalf("Chdir: %v", err)
}
inside := filepath.Join(root, ".clinerules", "hooks", "pre.sh")
got := formatHookPath(inside)
// Repo-scoped hook scripts should always include the repo name (the directory
// immediately containing .clinerules) even when running inside that repo.
expected := "workspace/" + filepath.ToSlash(filepath.Join(".clinerules", "hooks", "pre.sh"))
if got != expected {
t.Fatalf("expected formatted path to be %q. got=%q", expected, got)
}
}
func TestFormatHookPath_FallsBackToLastComponents(t *testing.T) {
// Use an obviously non-workspace path (relative, but not prefixed with cwd).
got := formatHookPath("/var/tmp/foo/bar/baz.sh")
if got != "foo/bar/baz.sh" {
t.Fatalf("expected last 3 components fallback, got=%q", got)
}
}
+35
View File
@@ -0,0 +1,35 @@
package handlers
import (
"os"
"testing"
)
func TestFormatHookPath_HomeDirToTilde(t *testing.T) {
home, err := os.UserHomeDir()
if err != nil || home == "" {
t.Skip("home dir not available; skipping")
}
got := formatHookPath(home + "/Documents/Cline/Hooks/TaskStart")
want := "~/Documents/Cline/Hooks/TaskStart"
if got != want {
t.Fatalf("expected %q, got %q", want, got)
}
}
func TestFormatHookPath_WorkspaceRepoRelative(t *testing.T) {
got := formatHookPath("/Users/alice/dev/repo-name/.clinerules/hooks/TaskStart")
want := "repo-name/.clinerules/hooks/TaskStart"
if got != want {
t.Fatalf("expected %q, got %q", want, got)
}
}
func TestFormatHookPath_FallbackLast3Components(t *testing.T) {
got := formatHookPath("/a/b/c/d/e")
want := "c/d/e"
if got != want {
t.Fatalf("expected %q, got %q", want, got)
}
}
+27 -27
View File
@@ -92,12 +92,12 @@ func newInstanceKillCommand() *cobra.Command {
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
if global.Clients == nil {
if global.Instances == nil {
return fmt.Errorf("clients not initialized")
}
ctx := cmd.Context()
registry := global.Clients.GetRegistry()
registry := global.Instances.GetRegistry()
if killAllCLI {
return killAllCLIInstances(ctx, registry)
@@ -112,7 +112,7 @@ func newInstanceKillCommand() *cobra.Command {
return cmd
}
func killAllCLIInstances(ctx context.Context, registry *global.ClientRegistry) error {
func killAllCLIInstances(ctx context.Context, registry *global.InstanceRegistry) error {
// Get all instances from registry
instances, err := registry.ListInstancesCleaned(ctx)
if err != nil {
@@ -135,7 +135,7 @@ func killAllCLIInstances(ctx context.Context, registry *global.ClientRegistry) e
cliInstances = append(cliInstances, instance)
} else {
skippedNonCLI++
fmt.Printf("⊘ Skipping %s instance: %s\n", platform, instance.Address)
fmt.Printf("⊘ Skipping %s instance: %s\n", platform, instance.CoreAddress)
}
}
}
@@ -160,16 +160,16 @@ func killAllCLIInstances(ctx context.Context, registry *global.ClientRegistry) e
// Kill all CLI instances
for _, instance := range cliInstances {
result := killInstanceProcess(ctx, registry, instance.Address)
result := killInstanceProcess(ctx, registry, instance.CoreAddress)
killResults = append(killResults, result)
if result.err != nil {
fmt.Printf("✗ Failed to kill %s: %v\n", instance.Address, result.err)
fmt.Printf("✗ Failed to kill %s: %v\n", instance.CoreAddress, result.err)
} else if result.alreadyDead {
fmt.Printf("⚠ Instance %s appears to be already dead\n", instance.Address)
fmt.Printf("⚠ Instance %s appears to be already dead\n", instance.CoreAddress)
} else {
fmt.Printf("✓ Killed %s (PID %d)\n", instance.Address, result.pid)
killedAddresses[instance.Address] = true
fmt.Printf("✓ Killed %s (PID %d)\n", instance.CoreAddress, result.pid)
killedAddresses[instance.CoreAddress] = true
}
}
@@ -190,8 +190,8 @@ func killAllCLIInstances(ctx context.Context, registry *global.ClientRegistry) e
// Check if any of the killed instances are still in the registry
stillPresent := []string{}
for _, remaining := range remainingInstances {
if killedAddresses[remaining.Address] {
stillPresent = append(stillPresent, remaining.Address)
if killedAddresses[remaining.CoreAddress] {
stillPresent = append(stillPresent, remaining.CoreAddress)
}
}
@@ -247,7 +247,7 @@ type killResult struct {
err error
}
func killInstanceProcess(ctx context.Context, registry *global.ClientRegistry, address string) killResult {
func killInstanceProcess(ctx context.Context, registry *global.InstanceRegistry, address string) killResult {
// Get gRPC client and process info
client, err := registry.GetClient(ctx, address)
if err != nil {
@@ -276,12 +276,12 @@ func newInstanceListCommand() *cobra.Command {
Short: "List all registered Cline instances",
Long: `List all registered Cline instances with their status and connection details.`,
RunE: func(cmd *cobra.Command, args []string) error {
if global.Clients == nil {
if global.Instances == nil {
return fmt.Errorf("clients not initialized")
}
ctx := cmd.Context()
registry := global.Clients.GetRegistry()
registry := global.Instances.GetRegistry()
// Load, cleanup stale local entries, and update health
instances, err := registry.ListInstancesCleaned(ctx)
@@ -310,7 +310,7 @@ func newInstanceListCommand() *cobra.Command {
var rows []instanceRow
for _, instance := range instances {
isDefault := ""
if instance.Address == defaultInstance {
if instance.CoreAddress == defaultInstance {
isDefault = "✓"
}
@@ -324,7 +324,7 @@ func newInstanceListCommand() *cobra.Command {
platform := platformNA
if instance.Status == grpc_health_v1.HealthCheckResponse_SERVING {
// Get PID from core
if client, err := registry.GetClient(ctx, instance.Address); err == nil {
if client, err := registry.GetClient(ctx, instance.CoreAddress); err == nil {
if processInfo, err := client.State.GetProcessInfo(ctx, &cline.EmptyRequest{}); err == nil {
pid = fmt.Sprintf("%d", processInfo.ProcessId)
// Update version from RPC if available
@@ -341,7 +341,7 @@ func newInstanceListCommand() *cobra.Command {
}
rows = append(rows, instanceRow{
address: instance.Address,
address: instance.CoreAddress,
status: instance.Status.String(),
version: instance.Version,
lastSeen: lastSeen,
@@ -407,7 +407,7 @@ func newInstanceListCommand() *cobra.Command {
fmt.Print(strings.TrimLeft(rendered, "\n"))
}
fmt.Println("\n")
fmt.Println()
}
}
@@ -428,11 +428,11 @@ func newInstanceDefaultCommand() *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
address := args[0]
if global.Clients == nil {
if global.Instances == nil {
return fmt.Errorf("clients not initialized")
}
registry := global.Clients.GetRegistry()
registry := global.Instances.GetRegistry()
// Verify the instance exists
_, err := registry.GetInstance(address)
@@ -464,34 +464,34 @@ func newInstanceNewCommand() *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
if global.Clients == nil {
if global.Instances == nil {
return fmt.Errorf("clients not initialized")
}
fmt.Println("Starting new Cline instance...")
instance, err := global.Clients.StartNewInstance(ctx)
instance, err := global.Instances.StartNewInstance(ctx)
if err != nil {
return fmt.Errorf("failed to start instance: %w", err)
}
fmt.Printf("Successfully started new instance:\n")
fmt.Printf(" Address: %s\n", instance.Address)
fmt.Printf(" Address: %s\n", instance.CoreAddress)
fmt.Printf(" Core Port: %d\n", instance.CorePort())
fmt.Printf(" Host Bridge Port: %d\n", instance.HostPort())
registry := global.Clients.GetRegistry()
registry := global.Instances.GetRegistry()
// If --default flag provided, set this instance as the default
if setDefault {
if err := registry.SetDefaultInstance(instance.Address); err != nil {
if err := registry.SetDefaultInstance(instance.CoreAddress); err != nil {
fmt.Printf("Warning: Failed to set as default: %v\n", err)
} else {
fmt.Printf(" Status: Set as default instance\n")
}
} else {
// Otherwise, check if EnsureDefaultInstance already set it as default
if registry.GetDefaultInstance() == instance.Address {
if registry.GetDefaultInstance() == instance.CoreAddress {
fmt.Printf(" Status: Default instance\n")
}
}
@@ -503,4 +503,4 @@ func newInstanceNewCommand() *cobra.Command {
cmd.Flags().BoolVarP(&setDefault, "default", "d", false, "set as default instance")
return cmd
}
}
+72 -1
View File
@@ -348,7 +348,7 @@ func (m *InputModel) handleSubmit() (tea.Model, tea.Cmd) {
return ChangeInputTypeMsg{
InputType: InputTypeFeedback,
Title: "Your feedback",
Placeholder: "/plan or /act to switch modes\nctrl+e to open editor",
Placeholder: "/plan or /act to switch modes\nctrl+e to open editor\nctrl+c to exit",
}
}
}
@@ -547,3 +547,74 @@ func (m *InputModel) openEditor() tea.Cmd {
func (m *InputModel) SetSlashRegistry(registry *slash.Registry) {
m.completion.SetRegistry(registry)
}
// initialPromptWrapper wraps InputModel to capture the submit result for initial task prompts
type initialPromptWrapper struct {
model *InputModel
result string
cancelled bool
}
func (w *initialPromptWrapper) Init() tea.Cmd {
return w.model.Init()
}
func (w *initialPromptWrapper) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case InputSubmitMsg:
w.result = msg.Value
clearCodes := w.model.ClearScreen()
if clearCodes != "" {
fmt.Print(clearCodes)
}
return w, tea.Quit
case InputCancelMsg:
w.cancelled = true
clearCodes := w.model.ClearScreen()
if clearCodes != "" {
fmt.Print(clearCodes)
}
return w, tea.Quit
}
// Forward to wrapped model
_, cmd := w.model.Update(msg)
return w, cmd
}
func (w *initialPromptWrapper) View() string {
return w.model.View()
}
// ErrUserAborted is returned when the user cancels the input prompt
var ErrUserAborted = fmt.Errorf("user aborted")
// PromptForInitialTask displays an interactive prompt for the initial task with slash command autocomplete.
// Returns the entered text, or ErrUserAborted if cancelled.
func PromptForInitialTask(title, placeholder, mode string, registry *slash.Registry) (string, error) {
model := NewInputModelWithRegistry(
InputTypeMessage,
title,
placeholder,
mode,
registry,
)
wrapper := &initialPromptWrapper{
model: &model,
}
p := tea.NewProgram(wrapper)
_, err := p.Run()
if err != nil {
return "", fmt.Errorf("input prompt failed: %w", err)
}
if wrapper.cancelled {
return "", ErrUserAborted
}
return strings.TrimSpace(wrapper.result), nil
}
+39 -22
View File
@@ -2,10 +2,11 @@ package slash
import (
"context"
"fmt"
"strings"
"sync"
"github.com/cline/grpc-go/client"
"github.com/cline/cli/pkg/cli/global"
"github.com/cline/grpc-go/cline"
)
@@ -32,25 +33,31 @@ var cliLocalCommands = []Command{
}
// NewRegistry creates a new slash command registry
func NewRegistry() *Registry {
return &Registry{
commands: make([]Command, 0),
func NewRegistry(ctx context.Context) *Registry {
defaultCommands := append([]Command{}, cliLocalCommands...)
r := &Registry{
commands: defaultCommands,
}
r.FetchFromBackend(ctx)
return r
}
// FetchFromBackend fetches available commands from cline-core backend
func (r *Registry) FetchFromBackend(ctx context.Context, c *client.ClineClient) error {
resp, err := c.Slash.GetAvailableSlashCommands(ctx, &cline.EmptyRequest{})
if err != nil {
return err
func (r *Registry) FetchFromBackend(ctx context.Context) error {
grpcClient, err := global.GetDefaultClient(ctx)
if err != nil && global.Config.Verbose {
fmt.Printf("Warning: could not get gRPC client: %v\n", err)
return nil
}
resp, err := grpcClient.Slash.GetAvailableSlashCommands(ctx, &cline.EmptyRequest{})
if err != nil && global.Config.Verbose {
fmt.Printf("Warning: could not get gRPC client: %v\n", err)
return nil
}
r.mu.Lock()
defer r.mu.Unlock()
// Start with CLI-local commands
r.commands = append([]Command{}, cliLocalCommands...)
// Add backend commands (only CLI-compatible ones)
for _, cmd := range resp.Commands {
if cmd.CliCompatible {
@@ -66,17 +73,6 @@ func (r *Registry) FetchFromBackend(ctx context.Context, c *client.ClineClient)
return nil
}
// GetCommands returns all available commands
func (r *Registry) GetCommands() []Command {
r.mu.RLock()
defer r.mu.RUnlock()
// Return a copy to avoid race conditions
result := make([]Command, len(r.commands))
copy(result, r.commands)
return result
}
// GetMatching returns commands that start with the given prefix (case-insensitive)
func (r *Registry) GetMatching(prefix string) []Command {
r.mu.RLock()
@@ -123,3 +119,24 @@ func (r *Registry) HasCommands() bool {
defer r.mu.RUnlock()
return len(r.commands) > 0
}
// ParseModeSwitch checks if message starts with /act or /plan and extracts the mode and remaining message.
// Returns (mode, remainingMessage, isModeSwitch).
// This is a package-level function so it can be used both during initial task creation
// and during interactive input handling.
func ParseModeSwitch(message string) (string, string, bool) {
trimmed := strings.TrimSpace(message)
lower := strings.ToLower(trimmed)
if strings.HasPrefix(lower, "/plan") {
remaining := strings.TrimSpace(trimmed[5:])
return "plan", remaining, true
}
if strings.HasPrefix(lower, "/act") {
remaining := strings.TrimSpace(trimmed[4:])
return "act", remaining, true
}
return "", message, false
}
+7 -7
View File
@@ -19,20 +19,20 @@ import (
// Handles localhost/127.0.0.1 equivalence by returning both forms.
func normalizeAddressVariants(address string) []string {
variants := []string{address}
// Extract host and port
host, port, err := net.SplitHostPort(address)
if err != nil {
return variants
}
// Add the alternate form for localhost/127.0.0.1
if host == "localhost" {
variants = append(variants, net.JoinHostPort("127.0.0.1", port))
} else if host == "127.0.0.1" {
variants = append(variants, net.JoinHostPort("localhost", port))
}
return variants
}
@@ -182,7 +182,7 @@ func (lm *LockManager) GetInstanceInfo(address string) (*common.CoreInstanceInfo
query := common.SelectInstanceLockByHolderSQL
variants := normalizeAddressVariants(address)
var heldBy, lockTarget string
var lockedAt int64
var lastErr error
@@ -193,7 +193,7 @@ func (lm *LockManager) GetInstanceInfo(address string) (*common.CoreInstanceInfo
if err == nil {
// Found it!
return &common.CoreInstanceInfo{
Address: heldBy,
CoreAddress: heldBy,
HostServiceAddress: lockTarget,
Status: grpc_health_v1.HealthCheckResponse_UNKNOWN,
LastSeen: time.Unix(lockedAt/1000, 0),
@@ -204,7 +204,7 @@ func (lm *LockManager) GetInstanceInfo(address string) (*common.CoreInstanceInfo
lastErr = err
}
}
// None of the variants were found
if lastErr != nil {
return nil, fmt.Errorf("failed to query instance: %w", lastErr)
@@ -235,7 +235,7 @@ func (lm *LockManager) ListInstancesWithHealthCheck(ctx context.Context) ([]*com
}
info := &common.CoreInstanceInfo{
Address: lock.HeldBy,
CoreAddress: lock.HeldBy,
HostServiceAddress: lock.LockTarget,
Status: status,
LastSeen: time.Unix(lock.LockedAt/1000, 0),
+5 -10
View File
@@ -80,7 +80,7 @@ func ensureTaskManager(ctx context.Context, address string) error {
}
// Always set the instance we're using as the default
registry := global.Clients.GetRegistry()
registry := global.Instances.GetRegistry()
if err := registry.SetDefaultInstance(instanceAddress); err != nil {
// Log warning but don't fail - this is not critical
fmt.Printf("Warning: failed to set default instance: %v\n", err)
@@ -91,10 +91,10 @@ func ensureTaskManager(ctx context.Context, address string) error {
// ensureInstanceAtAddress ensures an instance exists at the given address
func ensureInstanceAtAddress(ctx context.Context, address string) error {
if global.Clients == nil {
if global.Instances == nil {
return fmt.Errorf("global clients not initialized")
}
return global.Clients.EnsureInstanceAtAddress(ctx, address)
return global.Instances.EnsureInstanceAtAddress(ctx, address)
}
func newTaskNewCommand() *cobra.Command {
@@ -117,7 +117,7 @@ func newTaskNewCommand() *cobra.Command {
ctx := cmd.Context()
// Check if an instance exists when no address specified
if address == "" && global.Clients.GetRegistry().GetDefaultInstance() == "" {
if address == "" && global.Instances.GetRegistry().GetDefaultInstance() == "" {
fmt.Println("No instances available for creating tasks")
return nil
}
@@ -230,7 +230,7 @@ func newTaskSendCommand() *cobra.Command {
ctx := cmd.Context()
// Check if an instance exists when no address specified
if address == "" && global.Clients.GetRegistry().GetDefaultInstance() == "" {
if address == "" && global.Instances.GetRegistry().GetDefaultInstance() == "" {
fmt.Println("No instances available for sending messages")
return nil
}
@@ -620,11 +620,6 @@ func CleanupTaskManager() {
}
}
// NewTaskManagerForAddress is an exported wrapper around task.NewManagerForAddress
func NewTaskManagerForAddress(ctx context.Context, address string) (*task.Manager, error) {
return task.NewManagerForAddress(ctx, address)
}
// CreateAndFollowTask creates a new task and immediately follows it in interactive mode
// This is used by the root command to provide a streamlined UX
func CreateAndFollowTask(ctx context.Context, prompt string, opts TaskOptions) error {
+29 -44
View File
@@ -13,38 +13,41 @@ import (
"github.com/charmbracelet/lipgloss"
"github.com/cline/cli/pkg/cli/global"
"github.com/cline/cli/pkg/cli/output"
"github.com/cline/cli/pkg/cli/slash"
"github.com/cline/cli/pkg/cli/types"
)
// 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
approvalMessage *types.ClineMessage // Store the approval message for determining action
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
slashCommandRegistry *slash.Registry // Slash command registry for autocomplete
ctx context.Context // Context for restart callback
}
// NewInputHandler creates a new input handler
func NewInputHandler(manager *Manager, coordinator *StreamCoordinator, cancelFunc context.CancelFunc) *InputHandler {
return &InputHandler{
manager: manager,
coordinator: coordinator,
cancelFunc: cancelFunc,
isRunning: false,
pollTicker: time.NewTicker(500 * time.Millisecond),
resultChan: make(chan output.InputSubmitMsg, 1),
cancelChan: make(chan struct{}, 1),
manager: manager,
coordinator: coordinator,
cancelFunc: cancelFunc,
isRunning: false,
pollTicker: time.NewTicker(500 * time.Millisecond),
resultChan: make(chan output.InputSubmitMsg, 1),
slashCommandRegistry: slash.NewRegistry(context.Background()),
cancelChan: make(chan struct{}, 1),
}
}
@@ -163,7 +166,7 @@ func (ih *InputHandler) Start(ctx context.Context, errChan chan error) {
if shouldSend {
// Check for mode switch commands first
newMode, remainingMessage, isModeSwitch := ih.parseModeSwitch(message)
newMode, remainingMessage, isModeSwitch := slash.ParseModeSwitch(message)
if isModeSwitch {
// Create styles for mode switch messages (respect global color profile)
actStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("39")).Bold(true)
@@ -284,9 +287,9 @@ func (ih *InputHandler) promptForInput(ctx context.Context) (string, bool, error
model := output.NewInputModelWithRegistry(
output.InputTypeMessage,
"Cline is ready for your message...",
"/plan or /act to switch modes\nctrl+e to open editor\ntab to autocomplete commands",
"/plan or /act to switch modes\ntab to autocomplete commands\nctrl+e to open editor\nctrl+c to exit",
currentMode,
ih.manager.GetSlashRegistry(),
ih.slashCommandRegistry,
)
return ih.runInputProgram(ctx, model)
@@ -302,7 +305,7 @@ func (ih *InputHandler) promptForApproval(ctx context.Context, msg *types.ClineM
"Let Cline use this tool?",
"",
ih.manager.GetCurrentMode(),
ih.manager.GetSlashRegistry(), // Pass registry for feedback input after approval
ih.slashCommandRegistry,
)
message, shouldSend, err := ih.runInputProgram(ctx, model)
@@ -478,24 +481,6 @@ func (w *inputProgramWrapper) View() string {
return w.model.View()
}
// parseModeSwitch checks if message starts with /act or /plan and extracts the mode and remaining message
func (ih *InputHandler) parseModeSwitch(message string) (string, string, bool) {
trimmed := strings.TrimSpace(message)
lower := strings.ToLower(trimmed)
if strings.HasPrefix(lower, "/plan") {
remaining := strings.TrimSpace(trimmed[5:])
return "plan", remaining, true
}
if strings.HasPrefix(lower, "/act") {
remaining := strings.TrimSpace(trimmed[4:])
return "act", remaining, true
}
return "", message, false
}
// handleSpecialCommand processes special commands like /cancel, /exit
func (ih *InputHandler) handleSpecialCommand(ctx context.Context, message string) bool {
switch strings.ToLower(strings.TrimSpace(message)) {
+36 -29
View File
@@ -14,7 +14,6 @@ import (
"github.com/cline/cli/pkg/cli/display"
"github.com/cline/cli/pkg/cli/global"
"github.com/cline/cli/pkg/cli/handlers"
"github.com/cline/cli/pkg/cli/slash"
"github.com/cline/cli/pkg/cli/types"
"github.com/cline/grpc-go/client"
"github.com/cline/grpc-go/cline"
@@ -33,11 +32,11 @@ type Manager struct {
clientAddress string
state *types.ConversationState
renderer *display.Renderer
hookRenderer *display.HookRenderer
toolRenderer *display.ToolRenderer
systemRenderer *display.SystemMessageRenderer
streamingDisplay *display.StreamingDisplay
handlerRegistry *handlers.HandlerRegistry
slashRegistry *slash.Registry
isStreamingMode bool
isInteractive bool
currentMode string // "plan" or "act"
@@ -48,6 +47,7 @@ func NewManager(client *client.ClineClient) *Manager {
state := types.NewConversationState()
renderer := display.NewRenderer(global.Config.OutputFormat)
toolRenderer := display.NewToolRenderer(renderer.GetMdRenderer(), global.Config.OutputFormat)
hookRenderer := display.NewHookRenderer(renderer.GetMdRenderer(), global.Config.OutputFormat)
systemRenderer := display.NewSystemMessageRenderer(renderer, renderer.GetMdRenderer(), global.Config.OutputFormat)
streamingDisplay := display.NewStreamingDisplay(state, renderer)
@@ -61,11 +61,11 @@ func NewManager(client *client.ClineClient) *Manager {
clientAddress: "", // Will be set when client is provided
state: state,
renderer: renderer,
hookRenderer: hookRenderer,
toolRenderer: toolRenderer,
systemRenderer: systemRenderer,
streamingDisplay: streamingDisplay,
handlerRegistry: registry,
slashRegistry: slash.NewRegistry(),
currentMode: "plan", // Default mode
}
}
@@ -80,9 +80,6 @@ func NewManagerForAddress(ctx context.Context, address string) (*Manager, error)
manager := NewManager(client)
manager.clientAddress = address
// Fetch slash commands from backend (non-blocking, errors are logged)
manager.fetchSlashCommands(ctx)
return manager, nil
}
@@ -96,29 +93,13 @@ func NewManagerForDefault(ctx context.Context) (*Manager, error) {
manager := NewManager(client)
// Get the default instance address
if global.Clients != nil {
manager.clientAddress = global.Clients.GetRegistry().GetDefaultInstance()
if global.Instances != nil {
manager.clientAddress = global.Instances.GetRegistry().GetDefaultInstance()
}
// Fetch slash commands from backend (non-blocking, errors are logged)
manager.fetchSlashCommands(ctx)
return manager, nil
}
// fetchSlashCommands fetches available slash commands from the backend
// This is non-blocking and errors are logged but don't prevent manager creation
func (m *Manager) fetchSlashCommands(ctx context.Context) {
if err := m.slashRegistry.FetchFromBackend(ctx, m.client); err != nil {
if global.Config.Verbose {
m.renderer.RenderDebug("Failed to fetch slash commands: %v", err)
}
// Non-fatal: CLI-local commands are still available
} else if global.Config.Verbose {
m.renderer.RenderDebug("Loaded %d slash commands", len(m.slashRegistry.GetCommands()))
}
}
// SwitchToInstance switches the manager to use a different Cline instance
func (m *Manager) SwitchToInstance(ctx context.Context, address string) error {
m.mu.Lock()
@@ -989,6 +970,15 @@ func (m *Manager) processStateUpdate(stateUpdate *cline.State, coordinator *Stre
coordinator.MarkProcessedInCurrentTurn(msgKey)
}
case msg.Say == string(types.SayTypeCommandPermissionDenied):
msgKey := fmt.Sprintf("%d", msg.Timestamp)
if !coordinator.IsProcessedInCurrentTurn(msgKey) {
fmt.Println()
m.displayMessage(msg, false, false, i)
coordinator.MarkProcessedInCurrentTurn(msgKey)
}
case msg.Say == string(types.SayTypeBrowserActionLaunch):
msgKey := fmt.Sprintf("%d", msg.Timestamp)
if !coordinator.IsProcessedInCurrentTurn(msgKey) {
@@ -1043,6 +1033,26 @@ func (m *Manager) processStateUpdate(stateUpdate *cline.State, coordinator *Stre
coordinator.MarkProcessedInCurrentTurn(msgKey)
}
case msg.Say == string(types.SayTypeHookStatus):
msgKey := fmt.Sprintf("%d", msg.Timestamp)
if !coordinator.IsProcessedInCurrentTurn(msgKey) {
fmt.Println()
m.displayMessage(msg, false, false, i)
coordinator.MarkProcessedInCurrentTurn(msgKey)
}
case msg.Say == string(types.SayTypeHookOutputStream):
// Hook stdout/stderr streaming arrives as hook_output_stream messages.
// These are intentionally suppressed unless verbose (see SayHandler.handleHookOutputStream),
// but we still need to route them through the normal handler pipeline in streaming/follow
// mode so verbose users actually see `HOOK> ...` lines.
msgKey := fmt.Sprintf("%d", msg.Timestamp)
if !coordinator.IsProcessedInCurrentTurn(msgKey) {
m.displayMessage(msg, false, false, i)
coordinator.MarkProcessedInCurrentTurn(msgKey)
}
case msg.Say == string(types.SayTypeAPIReqStarted):
msgKey := fmt.Sprintf("%d", msg.Timestamp)
apiInfo := types.APIRequestInfo{Cost: -1}
@@ -1177,9 +1187,11 @@ func (m *Manager) displayMessage(msg *types.ClineMessage, isLast, isPartial bool
State: m.state,
Renderer: m.renderer,
ToolRenderer: m.toolRenderer,
HookRenderer: m.hookRenderer,
SystemRenderer: m.systemRenderer,
IsLast: isLast,
IsPartial: isPartial,
Verbose: global.Config.Verbose,
MessageIndex: messageIndex,
IsStreamingMode: isStreaming,
IsInteractive: isInteractive,
@@ -1286,11 +1298,6 @@ func (m *Manager) GetCurrentMode() string {
return m.currentMode
}
// GetSlashRegistry returns the slash command registry
func (m *Manager) GetSlashRegistry() *slash.Registry {
return m.slashRegistry
}
// extractModeFromState extracts the current mode from state JSON
func (m *Manager) extractModeFromState(stateJson string) string {
var rawState map[string]interface{}
+2 -2
View File
@@ -290,12 +290,12 @@ func setSimpleField(settings *cline.Settings, key, value string) error {
return err
}
settings.ActModeAwsBedrockCustomSelected = boolPtr(val)
case "hooks_enabled":
case "azure_identity":
val, err := parseBool(value)
if err != nil {
return err
}
settings.HooksEnabled = boolPtr(val)
settings.AzureIdentity = boolPtr(val)
// Integer fields
case "request_timeout_ms":
+52 -5
View File
@@ -47,11 +47,11 @@ const (
AskTypeResumeTask AskType = "resume_task"
AskTypeResumeCompletedTask AskType = "resume_completed_task"
AskTypeMistakeLimitReached AskType = "mistake_limit_reached"
AskTypeBrowserActionLaunch AskType = "browser_action_launch"
AskTypeUseMcpServer AskType = "use_mcp_server"
AskTypeNewTask AskType = "new_task"
AskTypeCondense AskType = "condense"
AskTypeReportBug AskType = "report_bug"
AskTypeBrowserActionLaunch AskType = "browser_action_launch"
AskTypeUseMcpServer AskType = "use_mcp_server"
AskTypeNewTask AskType = "new_task"
AskTypeCondense AskType = "condense"
AskTypeReportBug AskType = "report_bug"
)
// SayType represents different types of SAY messages
@@ -87,6 +87,11 @@ const (
SayTypeLoadMcpDocumentation SayType = "load_mcp_documentation"
SayTypeInfo SayType = "info"
SayTypeTaskProgress SayType = "task_progress"
// Hook status streaming from the backend.
// These values must match the backend "say" strings emitted by the extension.
SayTypeHookStatus SayType = "hook_status"
SayTypeHookOutputStream SayType = "hook_output_stream"
SayTypeCommandPermissionDenied SayType = "command_permission_denied"
)
// ToolMessage represents a tool-related message
@@ -145,6 +150,42 @@ type APIRequestRetryStatus struct {
ErrorSnippet string `json:"errorSnippet,omitempty"`
}
// HookMessage represents hook execution metadata sent from the backend
type HookMessage struct {
HookName string `json:"hookName"` // Type of hook (TaskStart, PreToolUse, etc.)
ToolName string `json:"toolName,omitempty"` // Optional tool name for tool-specific hooks
Status string `json:"status"` // "running", "completed", "cancelled", or "failed"
ScriptPaths []string `json:"scriptPaths,omitempty"` // Full paths to hook script(s)
PendingToolInfo *ToolInfo `json:"pendingToolInfo,omitempty"` // Metadata about the pending tool execution (PreToolUse)
ExitCode int `json:"exitCode,omitempty"` // Exit code for completed/failed hooks
HasJsonResponse bool `json:"hasJsonResponse,omitempty"` // Whether hook returned JSON
Error *HookError `json:"error,omitempty"` // Error details if hook failed
}
// ToolInfo represents a compact subset of tool parameters for UI display.
// This mirrors the extension's pendingToolInfo shape and is used by the CLI to
// show what tool the PreToolUse hook is gating.
type ToolInfo struct {
Tool string `json:"tool"`
Path string `json:"path,omitempty"`
Command string `json:"command,omitempty"`
Content string `json:"content,omitempty"`
Diff string `json:"diff,omitempty"`
Regex string `json:"regex,omitempty"`
Url string `json:"url,omitempty"`
McpTool string `json:"mcpTool,omitempty"`
McpServer string `json:"mcpServer,omitempty"`
ResourceUri string `json:"resourceUri,omitempty"`
}
// HookError represents structured error information from a failed hook
type HookError struct {
Type string `json:"type"` // Error type: "execution", "timeout", "validation", etc.
Message string `json:"message"` // Human-readable error message
Details string `json:"details,omitempty"` // Additional error details
ScriptPath string `json:"scriptPath,omitempty"` // Path to script that failed
}
// GetTimestamp returns a formatted timestamp string
func (m *ClineMessage) GetTimestamp() string {
return time.Unix(m.Timestamp/1000, 0).Format("15:04:05")
@@ -324,6 +365,12 @@ func convertProtoSayType(sayType cline.ClineSay) string {
return string(SayTypeInfo)
case cline.ClineSay_TASK_PROGRESS:
return string(SayTypeTaskProgress)
case cline.ClineSay_HOOK_STATUS:
return string(SayTypeHookStatus)
case cline.ClineSay_HOOK_OUTPUT_STREAM:
return string(SayTypeHookOutputStream)
case cline.ClineSay_COMMAND_PERMISSION_DENIED:
return string(SayTypeCommandPermissionDenied)
default:
return "unknown"
}
+14 -11
View File
@@ -8,6 +8,19 @@ import (
"github.com/spf13/cobra"
)
// VersionString returns the full version information string
func VersionString() string {
return fmt.Sprintf(`Cline CLI
Cline CLI Version: %s
Cline Core Version: %s
Commit: %s
Built: %s
Built by: %s
Go version: %s
OS/Arch: %s/%s
`, global.CliVersion, global.Version, global.Commit, global.Date, global.BuiltBy, runtime.Version(), runtime.GOOS, runtime.GOARCH)
}
// NewVersionCommand creates the version command
func NewVersionCommand() *cobra.Command {
var short bool
@@ -18,21 +31,11 @@ func NewVersionCommand() *cobra.Command {
Short: "Show version information",
Long: `Display version information for the Cline CLI.`,
RunE: func(cmd *cobra.Command, args []string) error {
// Versions are injected at build time via ldflags
if short {
fmt.Println(global.CliVersion)
return nil
}
fmt.Printf("Cline CLI\n")
fmt.Printf("Cline CLI Version: %s\n", global.CliVersion)
fmt.Printf("Cline Core Version: %s\n", global.Version)
fmt.Printf("Commit: %s\n", global.Commit)
fmt.Printf("Built: %s\n", global.Date)
fmt.Printf("Built by: %s\n", global.BuiltBy)
fmt.Printf("Go version: %s\n", runtime.Version())
fmt.Printf("OS/Arch: %s/%s\n", runtime.GOOS, runtime.GOARCH)
fmt.Print(VersionString())
return nil
},
}
+2 -2
View File
@@ -10,7 +10,7 @@ import (
// This is the canonical definition used across all CLI packages
type CoreInstanceInfo struct {
// Full core address including port
Address string `json:"address"`
CoreAddress string `json:"address"`
// Host bridge service address that core holds (host is ALWAYS running on localhost FYI)
HostServiceAddress string `json:"host_port"`
Status grpc_health_v1.HealthCheckResponse_ServingStatus `json:"status"`
@@ -20,7 +20,7 @@ type CoreInstanceInfo struct {
}
func (c *CoreInstanceInfo) CorePort() int {
_, port, _ := ParseHostPort(c.Address)
_, port, _ := ParseHostPort(c.CoreAddress)
return port
}
+17 -5
View File
@@ -47,7 +47,10 @@ func (s *DiffService) generateDiffID() string {
return fmt.Sprintf("diff_%d_%d", os.Getpid(), id)
}
// splitLines splits content into lines, preserving line ending information
// splitLines splits content into lines, preserving trailing newlines.
// This matches the behavior of JavaScript's String.split("\n"):
// - "hello\nworld\n" -> ["hello", "world", ""]
// - "hello\nworld" -> ["hello", "world"]
func splitLines(content string) []string {
if content == "" {
return []string{}
@@ -65,10 +68,9 @@ func splitLines(content string) []string {
}
}
// Add the last line if it doesn't end with newline
if current != "" {
lines = append(lines, current)
}
// Always add the last segment - if content ends with newline, this will be
// an empty string which preserves the trailing newline when joined back
lines = append(lines, current)
return lines
}
@@ -176,9 +178,19 @@ func (s *DiffService) ReplaceText(ctx context.Context, req *proto.ReplaceTextReq
endLine = startLine
}
// Check if we're replacing to the end of the document
replacingToEnd := endLine >= len(session.lines)
// Split new content into lines
newLines := splitLines(newContent)
// Remove trailing empty line for proper splicing, BUT only when NOT replacing
// to the end of the document. When replacing to the end, keep the trailing
// empty string to preserve trailing newlines from the content.
if !replacingToEnd && len(newLines) > 0 && newLines[len(newLines)-1] == "" {
newLines = newLines[:len(newLines)-1]
}
// Ensure we have enough lines in the current content
for len(session.lines) < endLine {
session.lines = append(session.lines, "")
-39
View File
@@ -1,39 +0,0 @@
package hostbridge
import (
"log"
"github.com/cline/grpc-go/host"
)
// WatchService implements the host.WatchServiceServer interface
type WatchService struct {
host.UnimplementedWatchServiceServer
coreAddress string
verbose bool
}
// NewWatchService creates a new WatchService
func NewWatchService(coreAddress string, verbose bool) *WatchService {
return &WatchService{
coreAddress: coreAddress,
verbose: verbose,
}
}
// SubscribeToFile subscribes to file change notifications
func (s *WatchService) SubscribeToFile(req *host.SubscribeToFileRequest, stream host.WatchService_SubscribeToFileServer) error {
if s.verbose {
log.Printf("SubscribeToFile called for path: %s", req.GetPath())
}
// For console implementation, we'll just log that we would watch the file
// In a real implementation, we'd use fsnotify or similar to watch file changes
log.Printf("[Cline] Would watch file: %s", req.GetPath())
// Keep the stream open but don't send any events for now
// In a real implementation, we'd send FileChangeEvent messages when files change
<-stream.Context().Done()
return nil
}
+6 -4
View File
@@ -5,6 +5,7 @@ import (
"log"
"os"
"github.com/cline/grpc-go/cline"
"github.com/cline/grpc-go/host"
)
@@ -43,24 +44,25 @@ func (s *WorkspaceService) GetWorkspacePaths(ctx context.Context, req *host.GetW
// SaveOpenDocumentIfDirty saves an open document if it has unsaved changes
func (s *WorkspaceService) SaveOpenDocumentIfDirty(ctx context.Context, req *host.SaveOpenDocumentIfDirtyRequest) (*host.SaveOpenDocumentIfDirtyResponse, error) {
if s.verbose {
log.Printf("SaveOpenDocumentIfDirty called for path: %s", req.GetPath())
log.Printf("SaveOpenDocumentIfDirty called for path: %v", req.FilePath)
}
// For console implementation, we'll assume the document is already saved
// In a real implementation, we'd check if the file has unsaved changes
f := false
return &host.SaveOpenDocumentIfDirtyResponse{
WasSaved: false, // Assume no changes to save
WasSaved: &f, // Assume no changes to save
}, nil
}
// GetDiagnostics returns diagnostic information for a file
func (s *WorkspaceService) GetDiagnostics(ctx context.Context, req *host.GetDiagnosticsRequest) (*host.GetDiagnosticsResponse, error) {
if s.verbose {
log.Printf("GetDiagnostics called for path: %s", req.GetPath())
log.Printf("GetDiagnostics called")
}
// For console implementation, return empty diagnostics
return &host.GetDiagnosticsResponse{
Diagnostics: []*host.Diagnostic{},
FileDiagnostics: []*cline.FileDiagnostics{},
}, nil
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

+8 -1
View File
@@ -115,6 +115,7 @@
},
"features/auto-approve",
"features/auto-compact",
"features/background-edit",
"features/checkpoints",
"features/cline-rules",
{
@@ -147,8 +148,10 @@
"features/hooks/samples"
]
},
"features/jupyter-notebooks",
"features/multiroot-workspace",
"features/plan-and-act",
"features/skills",
{
"group": "Slash Commands",
"pages": [
@@ -175,6 +178,7 @@
"features/tasks/task-management"
]
},
"features/worktrees",
"features/yolo-mode"
]
},
@@ -195,6 +199,7 @@
"provider-config/anthropic",
"provider-config/claude-code",
"provider-config/openai",
"provider-config/openai-codex",
"provider-config/openrouter",
"provider-config/cerebras",
"provider-config/deepseek",
@@ -278,6 +283,7 @@
"pages": [
"enterprise-solutions/overview",
"enterprise-solutions/onboarding",
"enterprise-solutions/sso-setup",
"enterprise-solutions/team-management/managing-members",
{
"group": "SaaS Provider Configuration",
@@ -317,7 +323,8 @@
"pages": [
"enterprise-solutions/monitoring/overview",
"enterprise-solutions/monitoring/telemetry",
"enterprise-solutions/monitoring/opentelemetry"
"enterprise-solutions/monitoring/opentelemetry",
"enterprise-solutions/monitoring/opentelemetry_override"
]
}
]
@@ -31,7 +31,7 @@ Check which models are available in your region first. Some newer models might n
<Frame>
<img
src="https://storage.googleapis.com/cline-static-assets-prod/assets/AWS%20Remote%20Config.gif"
src="https://assets.int.cline.bot/assets/AWS%20Remote%20Config.gif"
/>
</Frame>
@@ -27,7 +27,7 @@ If you don't have AWS credentials yet, reach out to your IT or cloud team to get
<Frame>
<img
src="https://storage.googleapis.com/cline-static-assets-prod/assets/VS%20Code%20Bedrock%20API%20Key.gif"
src="https://assets.int.cline.bot/assets/VS%20Code%20Bedrock%20API%20Key.gif"
/>
</Frame>
@@ -42,76 +42,45 @@ Cline supports three OTLP export protocols:
- **HTTP/protobuf**
- **HTTP/JSON**
### Export Destinations
You can export to:
- **Console** (for testing)
- **OTLP endpoint** (your own collector or observability platform)
## Configuration
OpenTelemetry is configured using environment variables before launching Cline.
OpenTelemetry is configured using [Remote Configuration](/enterprise-solutions/configuration/remote-configuration/overview#how-remote-configuration-works) from the [dashboard](https://app.cline.bot/dashboard/organization?tab=settings).
### Basic Setup
Enable OpenTelemetry and configure an OTLP endpoint:
Enable OpenTelemetry, configure an OTLP endpoint and select a protocol:
```bash
# Enable OpenTelemetry
export OTEL_TELEMETRY_ENABLED=1
<Frame>
<img
src="https://assets.int.cline.bot/assets/open_telemetry_main_options.png"
/>
</Frame>
# Configure metrics and logs export
export OTEL_METRICS_EXPORTER=otlp
export OTEL_LOGS_EXPORTER=otlp
If you're using gRPC, you can opt out of TLS.
# Set your OTLP endpoint
export OTEL_EXPORTER_OTLP_ENDPOINT=https://your-collector:4317
Once the collector has been configured, you can enable logs and/or metrics collection. At least one of them needs to be enabled.
# Optional: Set protocol (default is grpc)
export OTEL_EXPORTER_OTLP_PROTOCOL=grpc
```
### Environment Variables
| Variable | Description | Default |
|----------|-------------|---------|
| `OTEL_TELEMETRY_ENABLED` | Enable OpenTelemetry (`1` or `true`) | Disabled |
| `OTEL_METRICS_EXPORTER` | Metrics exporter type (`console`, `otlp`, or both) | None |
| `OTEL_LOGS_EXPORTER` | Logs exporter type (`console`, `otlp`, or both) | None |
| `OTEL_EXPORTER_OTLP_ENDPOINT` | OTLP endpoint URL | None |
| `OTEL_EXPORTER_OTLP_PROTOCOL` | Protocol (`grpc`, `http/protobuf`, `http/json`) | `grpc` |
| `OTEL_EXPORTER_OTLP_INSECURE` | Allow insecure connections | `false` |
| `OTEL_EXPORTER_OTLP_HEADERS` | Custom headers (comma-separated `key=value` pairs) | None |
You only need to configure it further if you need an advanced configuration.
### Advanced Configuration
**Separate endpoints for metrics and logs:**
```bash
export OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=https://metrics-collector:4317
export OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=https://logs-collector:4317
```
You can add custom protocols and endpoints for both, logs and metrics. You can also configure the metrics export interval, and the logs batch size, batch timeout and max queue size.
<Frame>
<img
src="https://assets.int.cline.bot/assets/open_telemetry_metrics_and_logs.png"
/>
</Frame>
**Custom headers for authentication:**
```bash
export OTEL_EXPORTER_OTLP_HEADERS="api-key=your-key,x-custom-header=value"
```
**Multiple exporters (console + OTLP):**
```bash
export OTEL_METRICS_EXPORTER=console,otlp
export OTEL_LOGS_EXPORTER=console,otlp
```
Finally, if your collector needs authentication headers, you can add key value pairs in the headers section.
**Export intervals:**
```bash
# Metrics export interval in milliseconds (default: 60000)
export OTEL_METRIC_EXPORT_INTERVAL=30000
# Logs batch size and timeout
export OTEL_LOG_BATCH_SIZE=512
export OTEL_LOG_BATCH_TIMEOUT=5000
export OTEL_LOG_MAX_QUEUE_SIZE=2048
```
<Frame>
<img
src="https://assets.int.cline.bot/assets/open_telemetry_headers.png"
/>
</Frame>
## Integration Examples
@@ -119,73 +88,46 @@ export OTEL_LOG_MAX_QUEUE_SIZE=2048
Export to Datadog using their OTLP endpoint:
```bash
export OTEL_TELEMETRY_ENABLED=1
export OTEL_METRICS_EXPORTER=otlp
export OTEL_LOGS_EXPORTER=otlp
export OTEL_EXPORTER_OTLP_ENDPOINT=https://api.datadoghq.com
export OTEL_EXPORTER_OTLP_HEADERS="dd-api-key=YOUR_DD_API_KEY"
```
<Frame>
<img
src="https://assets.int.cline.bot/assets/open_telemetry_datadog_example.png"
/>
</Frame>
### New Relic
Export to New Relic:
```bash
export OTEL_TELEMETRY_ENABLED=1
export OTEL_METRICS_EXPORTER=otlp
export OTEL_LOGS_EXPORTER=otlp
export OTEL_EXPORTER_OTLP_ENDPOINT=https://otlp.nr-data.net:4317
export OTEL_EXPORTER_OTLP_HEADERS="api-key=YOUR_NEW_RELIC_LICENSE_KEY"
```
<Frame>
<img
src="https://assets.int.cline.bot/assets/open_telemetry_relic_example.png"
/>
</Frame>
### Grafana Cloud
Export to Grafana Cloud:
```bash
export OTEL_TELEMETRY_ENABLED=1
export OTEL_METRICS_EXPORTER=otlp
export OTEL_LOGS_EXPORTER=otlp
export OTEL_EXPORTER_OTLP_ENDPOINT=https://otlp-gateway-prod-us-central-0.grafana.net/otlp
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Basic YOUR_BASE64_CREDENTIALS"
```
<Frame>
<img
src="https://assets.int.cline.bot/assets/open_telemetry_grafana_example.png"
/>
</Frame>
## Testing Configuration
Test your configuration with console output before sending to a real endpoint:
```bash
# Enable console output to see what data would be exported
export OTEL_TELEMETRY_ENABLED=1
export OTEL_METRICS_EXPORTER=console
export OTEL_LOGS_EXPORTER=console
```
Then launch Cline and check the console output for metrics and logs.
To test your configuration, log in to your account, perform some actions in a task, wait for the export interval, and verify that the data has arrived at your collector.
## Troubleshooting
### No Data Being Exported
If you arent getting any data in your collector, the easiest way to verify your integration is to enable the developer tools in your editor.
1. **Verify OpenTelemetry is enabled:**
```bash
echo $OTEL_TELEMETRY_ENABLED
```
Should output `1` or `true`
To do this, open the [webview developer tools](https://code.visualstudio.com/api/extension-guides/webview#inspecting-and-debugging-webviews).
2. **Check exporters are configured:**
```bash
echo $OTEL_METRICS_EXPORTER
echo $OTEL_LOGS_EXPORTER
```
Once youve done so, if you perform some actions that trigger metrics and/or logs (such as doing a task with Cline),
you will see error logs if any error occurs when sending the data to your collector.
3. **Test with console exporter first:**
```bash
export OTEL_METRICS_EXPORTER=console
export OTEL_LOGS_EXPORTER=console
```
If you don't see any logs, enable [debug mode](#debug-mode).
### Connection Errors
@@ -194,10 +136,7 @@ Then launch Cline and check the console output for metrics and logs.
curl -v https://your-otlp-endpoint:4317
```
2. **Check if insecure mode is needed:**
```bash
export OTEL_EXPORTER_OTLP_INSECURE=true
```
2. **Check if insecure mode is needed** by opting out of TLS
3. **Verify authentication headers:**
Double-check your API keys and authentication headers are correct
@@ -207,7 +146,7 @@ Then launch Cline and check the console output for metrics and logs.
Enable debug logging to see detailed OpenTelemetry information:
```bash
export TEL_DEBUG_DIAGNOSTICS=true
TEL_DEBUG_DIAGNOSTICS=true code .
```
This will output detailed information about:
@@ -218,7 +157,7 @@ This will output detailed information about:
## What Gets Exported
When Opentelemetry is enabled, Cline exports:
When OpenTelemetry is enabled, Cline exports:
### Metrics
- Feature usage counts
@@ -238,9 +177,9 @@ Exported data is already anonymous and doesn't include code content, file paths,
## Limitations
Current OpenTelemetry support in Cline:
- ✅ OTLP metrics export (console, gRPC, HTTP)
- ✅ OTLP logs export (console, gRPC, HTTP)
- ✅ Basic configuration via environment variables
- ✅ OTLP metrics export (gRPC, HTTP)
- ✅ OTLP logs export (gRPC, HTTP)
- ✅ Basic configuration via [Remote Configuration](/enterprise-solutions/configuration/remote-configuration/overview#how-remote-configuration-works)
- ❌ Distributed tracing (not yet implemented)
- ❌ Custom instrumentation API (not yet exposed)
- ❌ Sampling configuration (uses defaults)
@@ -0,0 +1,266 @@
---
title: "OpenTelemetry Integration Override"
sidebarTitle: "OpenTelemetry Override"
description: "Export Cline telemetry to your observability platform using OpenTelemetry Protocol (OTLP)"
---
Cline includes opt-in OpenTelemetry support for exporting metrics and logs to your own observability infrastructure using the OpenTelemetry Protocol (OTLP).
<Note>
OpenTelemetry integration is **optional** and intended for advanced users with existing observability infrastructure. Most users won't need this feature.
</Note>
## What is OpenTelemetry?
[OpenTelemetry](https://opentelemetry.io/) is an industry-standard observability framework that provides a unified way to collect and export telemetry data (metrics, logs, and traces).
Cline's OpenTelemetry support allows you to:
- Export telemetry to your own systems
- Integrate with observability platforms like Datadog, New Relic, Grafana Cloud, etc.
- Maintain full control over your monitoring data
- Use your organization's existing monitoring infrastructure
## Supported Features
Cline supports OpenTelemetry's **OTLP (OpenTelemetry Protocol)** export with:
<CardGroup cols={2}>
<Card title="Metrics Export" icon="chart-bar">
Export metrics about Cline usage, performance, and errors
</Card>
<Card title="Logs Export" icon="file-lines">
Export structured logs for debugging and analysis
</Card>
</CardGroup>
### Export Formats
Cline supports three OTLP export protocols:
- **gRPC** (default, recommended)
- **HTTP/protobuf**
- **HTTP/JSON**
### Export Destinations
You can export to:
- **Console** (for testing)
- **OTLP endpoint** (your own collector or observability platform)
## Configuration
OpenTelemetry is configured using environment variables before launching Cline.
### Basic Setup
Enable OpenTelemetry and configure an OTLP endpoint:
```bash
# Enable OpenTelemetry
export CLINE_OTEL_TELEMETRY_ENABLED=true
# Configure metrics and logs export
export CLINE_OTEL_METRICS_EXPORTER=otlp
export CLINE_OTEL_LOGS_EXPORTER=otlp
# Set your OTLP endpoint
export CLINE_OTEL_EXPORTER_OTLP_ENDPOINT=https://your-collector:4317
# Optional: Set protocol (default is grpc)
export CLINE_OTEL_EXPORTER_OTLP_PROTOCOL=grpc
```
### Environment Variables
| Variable | Description | Default |
|----------|-------------|---------|
| `CLINE_OTEL_TELEMETRY_ENABLED` | Enable OpenTelemetry (`true`) | Disabled |
| `CLINE_OTEL_METRICS_EXPORTER` | Metrics exporter type (`console`, `otlp`, or both) | None |
| `CLINE_OTEL_LOGS_EXPORTER` | Logs exporter type (`console`, `otlp`, or both) | None |
| `CLINE_OTEL_EXPORTER_OTLP_ENDPOINT` | OTLP endpoint URL | None |
| `CLINE_OTEL_EXPORTER_OTLP_PROTOCOL` | Protocol (`grpc`, `http/protobuf`, `http/json`) | `grpc` |
| `CLINE_OTEL_EXPORTER_OTLP_INSECURE` | Allow insecure connections | `false` |
| `CLINE_OTEL_EXPORTER_OTLP_HEADERS` | Custom headers (comma-separated `key=value` pairs) | None |
### Advanced Configuration
**Separate endpoints for metrics and logs:**
```bash
export CLINE_OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=https://metrics-collector:4317
export CLINE_OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=https://logs-collector:4317
```
**Custom headers for authentication:**
```bash
export CLINE_OTEL_EXPORTER_OTLP_HEADERS="api-key=your-key,x-custom-header=value"
```
**Multiple exporters (console + OTLP):**
```bash
export CLINE_OTEL_METRICS_EXPORTER=console,otlp
export CLINE_OTEL_LOGS_EXPORTER=console,otlp
```
**Export intervals:**
```bash
# Metrics export interval in milliseconds (default: 60000)
export CLINE_OTEL_METRIC_EXPORT_INTERVAL=30000
# Logs batch size and timeout
export CLINE_OTEL_LOG_BATCH_SIZE=512
export CLINE_OTEL_LOG_BATCH_TIMEOUT=5000
export CLINE_OTEL_LOG_MAX_QUEUE_SIZE=2048
```
## Integration Examples
### Datadog
Export to Datadog using their OTLP endpoint:
```bash
export CLINE_OTEL_TELEMETRY_ENABLED=true
export CLINE_OTEL_METRICS_EXPORTER=otlp
export CLINE_OTEL_LOGS_EXPORTER=otlp
export CLINE_OTEL_EXPORTER_OTLP_ENDPOINT=https://api.datadoghq.com
export CLINE_OTEL_EXPORTER_OTLP_HEADERS="dd-api-key=YOUR_DD_API_KEY"
```
### New Relic
Export to New Relic:
```bash
export CLINE_OTEL_TELEMETRY_ENABLED=true
export CLINE_OTEL_METRICS_EXPORTER=otlp
export CLINE_OTEL_LOGS_EXPORTER=otlp
export CLINE_OTEL_EXPORTER_OTLP_ENDPOINT=https://otlp.nr-data.net:4317
export CLINE_OTEL_EXPORTER_OTLP_HEADERS="api-key=YOUR_NEW_RELIC_LICENSE_KEY"
```
### Grafana Cloud
Export to Grafana Cloud:
```bash
export CLINE_OTEL_TELEMETRY_ENABLED=true
export CLINE_OTEL_METRICS_EXPORTER=otlp
export CLINE_OTEL_LOGS_EXPORTER=otlp
export CLINE_OTEL_EXPORTER_OTLP_ENDPOINT=https://otlp-gateway-prod-us-central-0.grafana.net/otlp
export CLINE_OTEL_EXPORTER_OTLP_HEADERS="Authorization=Basic YOUR_BASE64_CREDENTIALS"
```
## Testing Configuration
Test your configuration with console output before sending to a real endpoint:
```bash
# Enable console output to see what data would be exported
export CLINE_OTEL_TELEMETRY_ENABLED=true
export CLINE_OTEL_METRICS_EXPORTER=console
export CLINE_OTEL_LOGS_EXPORTER=console
```
Then launch Cline and check the console output for metrics and logs.
## Troubleshooting
### No Data Being Exported
1. **Verify OpenTelemetry is enabled:**
```bash
echo $CLINE_OTEL_TELEMETRY_ENABLED
```
Should output `true`
2. **Check exporters are configured:**
```bash
echo $CLINE_OTEL_METRICS_EXPORTER
echo $CLINE_OTEL_LOGS_EXPORTER
```
3. **Test with console exporter first:**
```bash
export CLINE_OTEL_METRICS_EXPORTER=console
export CLINE_OTEL_LOGS_EXPORTER=console
```
### Connection Errors
1. **Verify endpoint is accessible:**
```bash
curl -v https://your-otlp-endpoint:4317
```
2. **Check if insecure mode is needed:**
```bash
export CLINE_OTEL_EXPORTER_OTLP_INSECURE=true
```
3. **Verify authentication headers:**
Double-check your API keys and authentication headers are correct
### Debug Mode
Enable debug logging to see detailed OpenTelemetry information:
```bash
export TEL_DEBUG_DIAGNOSTICS=true
```
This will output detailed information about:
- Configuration being used
- Exporters being created
- Connection attempts
- Export successes/failures
## What Gets Exported
When OpenTelemetry is enabled, Cline exports:
### Metrics
- Feature usage counts
- Task execution metrics
- Error rates and types
- Performance measurements
### Logs
- System events
- Error logs with context
- Operational information
<Warning>
Exported data is already anonymous and doesn't include code content, file paths, or sensitive information. However, you're responsible for securing the data once exported to your systems.
</Warning>
## Limitations
Current OpenTelemetry support in Cline:
- ✅ OTLP metrics export (console, gRPC, HTTP)
- ✅ OTLP logs export (console, gRPC, HTTP)
- ✅ Basic configuration via environment variables
- ❌ Distributed tracing (not yet implemented)
- ❌ Custom instrumentation API (not yet exposed)
- ❌ Sampling configuration (uses defaults)
## Best Practices
1. **Test First**: Always test with console exporter before sending to production
2. **Secure Credentials**: Never hardcode API keys; use secure environment variable management
3. **Monitor Costs**: Be aware of data ingestion costs with your observability platform
4. **Start Simple**: Begin with metrics only, add logs if needed
5. **Use Compression**: OTLP supports compression; check if your endpoint requires it
## Next Steps
<CardGroup cols={2}>
<Card title="Cline Telemetry" icon="chart-simple" href="/enterprise-solutions/monitoring/telemetry">
Configure simple built-in telemetry
</Card>
<Card title="OpenTelemetry Docs" icon="book" href="https://opentelemetry.io/docs/">
Learn more about OpenTelemetry
</Card>
</CardGroup>
@@ -8,14 +8,20 @@ Cline includes optional monitoring capabilities for organizations that want to t
## Monitoring Options
<CardGroup cols={2}>
<CardGroup cols={2}>
<Card title="OpenTelemetry" icon="chart-line" href="/enterprise-solutions/monitoring/opentelemetry">
Export metrics and logs to your own observability backends
</Card>
<Card title="OpenTelemetry Override" icon="chart-line" href="/enterprise-solutions/monitoring/opentelemetry_override">
Export to your own observability backends through environment variables (advanced)
</Card>
</CardGroup>
<CardGroup cols={1}>
<Card title="Cline Telemetry" icon="chart-simple" href="/enterprise-solutions/monitoring/telemetry">
Built-in anonymous usage tracking that helps improve Cline (opt-in)
</Card>
<Card title="OpenTelemetry" icon="chart-line" href="/enterprise-solutions/monitoring/opentelemetry">
Export metrics and logs to your own observability backends (advanced)
</Card>
</Card>
</CardGroup>
## Cline Telemetry
+4
View File
@@ -22,6 +22,10 @@ Your IdP administrator will receive an email with a link to register their organ
### Step 2: Configure Your Identity Provider
<Info>
For a short overview of where SSO configuration lives (Cline dashboard vs WorkOS vs your IdP), see [SSO Setup](/enterprise-solutions/sso-setup).
</Info>
Connect your identity provider (IdP) to WorkOS:
1. In the WorkOS dashboard, go to **AuthKit → Connections**
+61
View File
@@ -0,0 +1,61 @@
---
title: "SSO Setup"
sidebarTitle: "SSO Setup"
description: "Configure Single Sign-On (SSO) for Cline Enterprise via WorkOS AuthKit."
---
## Overview
Cline Enterprise integrates with your identity provider (IdP) via **WorkOS AuthKit** for SSO.
This page describes, at a high level, how SSO is set up for Cline Enterprise using WorkOS AuthKit.
If you havent completed initial onboarding, start with [Onboarding](/enterprise-solutions/onboarding).
## Where setup happens
SSO setup spans two places:
1) **Cline Dashboard (app.cline.bot)**
- Where you sign in and verify SSO works for your organization.
2) **WorkOS dashboard**
- Where your IdP connection is configured (AuthKit → Connections). Your designated admin receives access to this during enterprise onboarding.
## Using the Cline Dashboard
Use the Cline Dashboard at https://app.cline.bot to:
- complete sign-in and onboarding flows
- verify users can authenticate via SSO
## Configure your IdP connection in WorkOS
During enterprise onboarding, your designated admin will receive an invitation email from WorkOS with a link to access your organization's WorkOS dashboard.
<Frame>
<img src="/assets/workos-invite-email.png" alt="WorkOS invitation email example" />
</Frame>
To connect your IdP to Cline Enterprise, configure your identity provider in **WorkOS AuthKit**:
1. In the WorkOS dashboard, go to **AuthKit → Connections**
2. Click **Add Connection**
3. Select your identity provider (e.g., Okta, Microsoft Entra ID/Azure AD, Google Workspace, Generic SAML/OIDC)
4. Follow the provider-specific instructions in WorkOS
WorkOSs UI and required fields vary by provider. For details, follow WorkOS documentation:
- https://workos.com/docs/authkit/sso
## Keycloak note (IdP compatibility)
Cline Enterprises default SSO integration is **via WorkOS**.
If you use **Keycloak** as your IdP, the supported path is to configure Keycloak in WorkOS as a **Generic SAML** or **Generic OIDC** provider (using the settings WorkOS requests for those provider types).
## Verification
After configuring WorkOS:
1) Attempt an SSO sign-in from https://app.cline.bot.
2) Confirm the sign-in completes (you are redirected back successfully).
## Troubleshooting
- **Redirect URI mismatch**: confirm the redirect/callback URL configured in WorkOS matches what was provided during your Cline Enterprise onboarding.
For additional troubleshooting guidance, refer to WorkOS documentation:
- https://workos.com/docs/authkit/sso
+4 -1
View File
@@ -48,7 +48,7 @@ These labels match what you see in the Auto Approve menu.
| Execute all commands | Run commands marked as requiring approval | Requires “Execute safe commands” |
| Use the browser | Allows use of the browser tool for web fetching and searching | Proxy issues can apply |
| Use MCP servers | Use MCP tools and access MCP resources | Some servers also have per-tool auto-approve |
| Enable notifications | Notifies you about long-running auto-approved commands | Helpful for terminal work |
| Enable notifications | Notifies you about long-running auto-approved commands | Accessible directly in the Auto Approve menu |
<Warning>
“Read all files” and “Edit all files” only matter if their base toggle is enabled. They extend access outside your workspace.
@@ -92,6 +92,9 @@ These are examples, not guarantees.
Auto-approved actions can run for a while, especially long terminal commands. If you enable notifications, Cline can notify you when an auto-approved command has been running for a while and may need attention.
The **Enable notifications** toggle is located at the bottom of the Auto Approve menu, below a separator line. This puts the notification setting right where you manage your auto-approval permissions, making it easy to discover and adjust.
## Recommendations
A good default setup is:
+51
View File
@@ -0,0 +1,51 @@
---
title: "Background Edit"
sidebarTitle: "Background Edit"
---
Background Edit lets Cline make file changes without opening the diff editor, so you can keep writing code while Cline works on other files in the background.
<Note>
This feature is marked as experimental.
</Note>
## How It Works
By default, Cline opens a side-by-side diff editor tab for each file it modifies. With Background Edit enabled:
- Edits write directly to your files without opening new tabs
- Changes appear as collapsible diff blocks in the chat panel
- Your editor focus stays on whatever file you had open
## Enabling Background Edit
1. Click the settings icon (gear) in the top-right corner of the Cline panel
2. Go to "**Feature Settings**"
3. Toggle "**Enable Background Edit**" on
## Viewing Changes
File changes display directly in the chat panel with:
- **File action icons** showing whether the file was added, updated, or deleted
- **Stats** showing additions (+) and deletions (-) at a glance
- **Collapsible diffs** you can expand or collapse by clicking the file header
- **Real-time streaming** as changes appear line-by-line
Green highlights additions, red highlights deletions.
## When to Use It
This feature works well when you:
- Use [auto-approve mode](/features/auto-approve) and prefer reviewing changes after the fact
- Work on tasks with many small file changes
- Want to stay focused on your current file
Stick with the default diff editor if you prefer reviewing each change before it saves, or need to make inline edits to Cline's proposed changes.
## Relationship with Other Features
- **Checkpoints**: Still created after each file operation
- **Auto-approve**: Pairs well for uninterrupted workflows
- **Message editing**: Restoring from a previous message works as expected
+112
View File
@@ -0,0 +1,112 @@
---
title: "Jupyter Notebooks"
description: "AI-assisted editing of Jupyter notebooks with cell-level context awareness"
---
# Jupyter Notebooks
Cline provides comprehensive support for Jupyter notebooks (`.ipynb` files), enabling AI-assisted editing with full cell-level context awareness. This feature was developed in collaboration with Amazon to bring AI coding assistance to data science workflows.
## Getting started
Jupyter notebook support is a built-in feature of Cline. To use it, you just need to have the Jupyter notebook extension enabled in VS Code. Once you open any `.ipynb` file, you'll see AI-assisted buttons in your notebook interface.
## How to use
### Generate Cell
Click the sparkle icon (✨) in the notebook toolbar to generate new cells with AI assistance.
![Generate Cell demo](../assets/jupyter-generate-cell.gif)
**How it works:** The AI receives context from surrounding cells, so it understands the variables and imports already in scope. This means you can reference existing DataFrames, functions, and other objects without re-explaining them.
**Example prompt:** "Create a visualization showing the correlation matrix of numeric columns with a heatmap"
The cell is inserted with proper notebook JSON structure, preserving metadata and ready to execute.
### Explain Cell
Click the Explain button in any cell's title bar to get a detailed explanation of what the cell does.
This is useful for:
- Revisiting old notebooks
- Onboarding to a teammate's analysis
- Understanding complex transformations
**How it works:** Cline extracts the full cell context, including outputs, so explanations can reference actual results like column names, row counts, and computed values.
### Improve Cell
Click the Improve button in any cell's title bar to enhance existing cells with AI suggestions.
![Explain and Improve Cell demo](../assets/jupyter-explain-improve-cell.gif)
Use this to:
- Optimize slow pandas operations
- Add error handling
- Refactor for clarity
- Convert loops to vectorized operations
**How it works:** Cline suggests improvements while preserving the cell's position and metadata in the notebook structure. The AI explains what was changed and why.
## How cell context works
Unlike traditional file editing, Jupyter notebooks are JSON documents containing arrays of cells. Each cell has its own type, source content, metadata, execution count, and outputs.
When you use a Jupyter command, Cline extracts structured context that includes:
- **Cell type** (code, markdown, or raw)
- **Source content** as an array of lines
- **Cell metadata** and configuration
- **Execution count** for code cells
- **Outputs** including data, text, and error traces
This structured representation allows the AI to understand not just the code, but its context within the notebook and its actual output.
### JSON structure preservation
Cline is designed to work carefully with the cell JSON structure, aiming to:
- Keep cell boundaries intact
- Preserve execution counts
- Maintain cell metadata
- Keep outputs associated with their source cells
The AI is specifically prompted to preserve notebook structure, though you should always review changes to ensure your notebook format remains correct.
## Keyboard shortcuts
You can bind any of these commands to keyboard shortcuts for faster access:
1. Open VS Code keyboard shortcuts (Cmd/Ctrl + K, Cmd/Ctrl + S)
2. Search for `cline.jupyterGenerateCell`, `cline.jupyterExplainCell`, or `cline.jupyterImproveCell`
3. Assign your preferred key combinations
## Tips for best results
**For Generate Cell:**
- Be specific about what you want the cell to do
- Reference existing variables by name (the AI can see them)
- Mention preferred libraries if you have a preference (e.g., "use seaborn" or "use plotly")
**For Explain Cell:**
- Works best on cells that have been executed (outputs provide additional context)
- Good for complex chained operations like pandas groupby/merge sequences
**For Improve Cell:**
- Mention what aspect you want to improve (performance, readability, error handling)
- The AI will explain the changes it suggests
## Limitations
- Notebook support requires the Jupyter notebook extension to be enabled in VS Code
- Cell context extraction depends on VS Code's notebook API
- Very large notebooks may require more context than some models can handle efficiently
## Related
- [Cline Tools Guide](/exploring-clines-tools/cline-tools-guide) -- Overview of all Cline commands
- [Model Selection Guide](/core-features/model-selection-guide) -- Choosing the right model for your workflow
+231
View File
@@ -0,0 +1,231 @@
---
title: "Skills"
sidebarTitle: "Skills"
description: "Extend Cline with reusable, on-demand instruction sets for specialized tasks"
---
Skills are modular instruction sets that extend Cline's capabilities for specific tasks. Each skill packages detailed guidance, workflows, and optional resources that Cline loads only when relevant to your request.
Unlike rules (which are always active), skills load on-demand. You can install dozens of skills without affecting context or performance because Cline only sees the skill name and description until it's actually needed.
<Note>
Skills is an experimental feature. Enable it in Settings → Features → Enable Skills.
</Note>
## Why Skills?
Consider how you'd onboard a new team member: you wouldn't dump every document on them at once. You'd give them a brief overview, then point them to detailed guides when they're working on specific tasks.
Skills work the same way:
- **At startup**: Cline sees only a brief description of each skill
- **When triggered**: Cline loads the full instructions for that specific skill
- **As needed**: Skills can bundle additional files that Cline reads only when referenced
This progressive loading means you can package extensive domain knowledge without burning context tokens on information that isn't relevant to the current task.
## Creating a Skill
Every skill is a directory containing a `SKILL.md` file with YAML frontmatter:
```
my-skill/
├── SKILL.md # Required: main instructions
├── docs/ # Optional: additional documentation
│ └── advanced.md
└── scripts/ # Optional: utility scripts
└── helper.sh
```
The `SKILL.md` file has two parts: metadata and instructions.
```yaml
---
name: my-skill
description: Brief description of what this skill does and when to use it.
---
# My Skill
Detailed instructions for Cline to follow when this skill is activated.
## Steps
1. First, do this
2. Then do that
3. For advanced usage, see [advanced.md](docs/advanced.md)
```
**Required fields:**
- `name`: Must exactly match the directory name
- `description`: Tells Cline when to use this skill (max 1024 characters)
The description is critical because it's how Cline decides whether to activate a skill. Be specific about what the skill does and when it should be used.
## Where Skills Live
Skills can be stored in two locations:
**Global Skills** apply to all your projects:
- **macOS/Linux:** `~/.cline/skills/`
- **Windows:** `C:\Users\USERNAME\.cline\skills\`
**Project Skills** apply only to the current workspace:
- `.cline/skills/` (recommended)
- `.clinerules/skills/`
- `.claude/skills/` (for Claude Code compatibility)
When a global skill and project skill have the same name, the global skill takes precedence. This lets you customize skills for your personal workflow while still using project defaults.
## Managing Skills
Click the scale icon below the chat input to open the rules and workflows panel. When skills are enabled, you'll see a Skills tab where you can:
- View all available skills (global and workspace)
- Toggle individual skills on or off
- Create new skills from a template
- Delete skills you no longer need
Skills are enabled by default when discovered. Toggle them off if you want them available but not active for the current project.
## How Cline Uses Skills
When you send a message, Cline sees a list of available skills with their descriptions. If your request matches a skill's description, Cline activates it using the `use_skill` tool, which loads the full instructions.
For example, if you have a skill for deploying to AWS:
```yaml
---
name: aws-deploy
description: Deploy applications to AWS using CDK. Use when deploying, updating infrastructure, or managing AWS resources.
---
```
Asking "deploy this to AWS" would trigger Cline to activate the skill, load its detailed instructions, and follow them to complete your request.
## Example: Data Analysis Skill
Here's a practical skill for data analysis tasks. Create a directory called `data-analysis/` with this `SKILL.md`:
```yaml
---
name: data-analysis
description: Analyze data files and generate insights. Use when working with CSV, Excel, or JSON data files that need exploration, cleaning, or visualization.
---
```
Then add the instructions in the body of the file:
````markdown
# Data Analysis
When analyzing data files, follow this workflow:
## 1. Understand the Data
- Read a sample of the file to understand its structure
- Identify column types and data quality issues
- Note any missing values or anomalies
## 2. Ask Clarifying Questions
Before diving in, ask the user:
- What specific insights are they looking for?
- Are there any known data quality issues?
- What format do they want for the output?
## 3. Perform Analysis
Use pandas for data manipulation:
```python
import pandas as pd
# Load and explore
df = pd.read_csv("data.csv")
print(df.head())
print(df.describe())
print(df.info())
```
For visualization, prefer matplotlib or seaborn depending on complexity.
## 4. Present Findings
- Start with a summary of key insights
- Support findings with specific numbers
- Include visualizations where they add clarity
- End with recommendations or next steps
````
## Bundling Supporting Files
Skills can include additional files that Cline accesses only when needed:
```
complex-skill/
├── SKILL.md
├── docs/
│ ├── setup.md
│ └── troubleshooting.md
├── templates/
│ └── config.yaml
└── scripts/
└── validate.py
```
Reference these in your instructions:
````markdown
For initial setup, follow [setup.md](docs/setup.md).
Use the config template at `templates/config.yaml` as a starting point.
Run the validation script to check your configuration:
```bash
python scripts/validate.py
```
````
Cline reads these files using `read_file` when the instructions reference them. Scripts can be executed directly, with only the output entering the context (not the script code itself).
## Ideas for Skills
Skills shine when you have tasks that:
- Require detailed, multi-step workflows
- Need domain-specific knowledge or best practices
- Would otherwise require repeating the same instructions across conversations
Some possibilities:
- **Release management**: Version bumping, changelog generation, git tagging, and publishing
- **Code review**: Your team's specific review checklist and quality standards
- **Database migrations**: Safely evolving schemas with rollback procedures
- **API integration**: Connecting to specific third-party services with proper error handling
- **Documentation**: Your preferred structure, style guide, and tooling
- **Debugging workflows**: Systematic approaches to diagnosing specific types of issues
- **Infrastructure**: Terraform/CDK patterns for your cloud setup
The best skills encode institutional knowledge that would otherwise live only in experienced developers' heads.
## Skills vs Rules vs Workflows
| Feature | Purpose | When Active |
|---------|---------|-------------|
| **Rules** | Define how Cline should behave | Always (or contextually) |
| **Workflows** | Step-by-step task automation | Invoked with `/workflow.md` |
| **Skills** | Domain expertise loaded on-demand | Triggered by matching requests |
**Rules** set constraints and preferences (like "always use TypeScript" or "follow this style guide").
**Workflows** are explicit sequences you invoke for specific tasks (like `/release.md` for a release process).
**Skills** are expertise that Cline activates automatically when relevant (like data analysis knowledge when you're working with CSV files).
Use rules for ongoing constraints, workflows for explicit automation, and skills for domain knowledge that should be available but not always active.
## Related Features
- [Cline Rules](/features/cline-rules) for always-active project guidance
- [Workflows](/features/slash-commands/workflows/index) for explicit task automation
- [Hooks](/features/hooks/index) for injecting custom logic at key moments
@@ -120,8 +120,38 @@ Controls a built-in browser to interact with websites or local servers. Useful f
</browser_action>
```
### Leverage MCP Tools
You can use Model Context Protocol (MCP) tools within your workflows to interact with external services like GitHub, Slack, or databases. This allows you to create powerful end-to-end automations.
### Leveraging MCP Tools
MCP tools allow Cline to interact with external services like GitHub, Slack, or databases. You can reference them in your workflows using natural language or explicit XML tags for deterministic control.
#### Natural Language (Heuristic)
Most of the time, the simplest way to use an MCP tool is to describe the action you want Cline to take.
```markdown
1. Fetch the latest issues from the github-repo MCP server.
2. Summarize the critical bugs.
3. Post the summary to the #engineering channel using the slack-notifications MCP.
```
#### Explicit XML Tag (Deterministic)
For critical automation where you need exact control over parameters, use the `use_mcp_tool` tag.
```xml
<use_mcp_tool>
<server_name>github-repo-manager</server_name>
<tool_name>create_issue</tool_name>
<arguments>
{
"owner": "cline",
"repo": "cline",
"title": "Automated Bug Report",
"body": "Found a regression in the latest build."
}
</arguments>
</use_mcp_tool>
```
### Manage Context Window
Be mindful of Cline's context window. If a workflow is too long or processes too much data, it might exceed the token limit.
+55
View File
@@ -0,0 +1,55 @@
---
title: "Web Tools"
sidebarTitle: "Web Tools"
description: "Search the web and fetch content from URLs directly within Cline"
---
Web Tools give Cline the ability to search the internet and fetch content from specific URLs during your tasks. This is useful when you need up-to-date information, documentation lookups, or research that goes beyond your local codebase and the LLM's internal knowledge.
<Warning>
Web Tools require the **Cline provider**. They are not available when using other providers like OpenRouter, Anthropic, AWS Bedrock, etc.
</Warning>
## How Web Tools Work
Cline has two web tools:
- **web_search**: Searches the web and returns a list of relevant webpages based on your query
- **web_fetch**: Fetches and analyzes content from a specific URL
When Cline determines that web information would help complete your task, it will use these tools automatically. The tools call Cline's backend API, which handles the search or fetch operation and returns the results.
## Enabling Web Tools
Web Tools are available when using the Cline provider. To use them:
1. Make sure you're signed in to Cline
2. Ensure you're using the Cline provider
3. Enable the Web Tools toggle in the Feature Settings menu
<Note>
Web tools can be auto-approved using the "Use the browser" setting in [Auto Approve](/features/auto-approve).
</Note>
## Use Cases
### Looking Up Documentation
When working with unfamiliar libraries or APIs:
- Search for official documentation
- Fetch specific API reference pages
- Get examples and usage patterns
### Research Before Implementation
Before implementing a feature:
- Search for best practices and common patterns
- Find recent discussions about approaches
- Look up known issues or limitations
### Checking Latest Information
For time-sensitive information:
- Latest release notes and changelogs
- Recent bug fixes or security updates
- Current recommended versions
+270
View File
@@ -0,0 +1,270 @@
---
title: "Worktrees"
sidebarTitle: "Worktrees"
---
Worktrees let you work on multiple branches simultaneously, each in its own folder. This enables Cline to work on tasks in parallel across separate VS Code windows, or lets Cline work independently while you continue coding in your main workspace.
<Frame>
<img
src="https://storage.googleapis.com/cline_public_images/docs/assets/worktrees-overview.png"
alt="Worktrees view showing multiple linked worktrees"
/>
</Frame>
## What Are Git Worktrees?
A Git worktree is a linked copy of your repository in a separate folder, checked out to a specific branch. All worktrees share the same Git history and `.git` directory, but each has its own working directory with different code checked out.
Key concepts:
- **Main worktree**: Your original repository folder where the `.git` directory lives
- **Linked worktrees**: Additional folders you create, each checked out to a different branch
- **Shared history**: All worktrees share commits, branches, and Git configuration
<Tip>
Unlike regular branch switching, worktrees let you have multiple branches checked out at the same time in different folders. This means you can have VS Code windows open for different features simultaneously.
</Tip>
## Why Use Worktrees with Cline?
Worktrees solve a common problem: **Cline takes over your VS Code window while working on a task**. With worktrees, you can:
1. **Run Cline in parallel** - Have Cline work on multiple tasks simultaneously, each in its own worktree and VS Code window
2. **Keep working while Cline works** - Let Cline handle a task in a separate worktree while you continue coding in your main workspace
3. **Isolate experimental changes** - Test risky changes in a worktree without affecting your main branch
4. **Quick context switching** - Jump between features without stashing or committing incomplete work
## Getting Started
### Quick Launch (Recommended)
The fastest way to start using worktrees is the **New Worktree Window** button on Cline's home screen:
1. Click **New Worktree Window** on the home screen
2. Enter a branch name and folder path (defaults are auto-filled)
3. Click **Create & Open**
A new VS Code window opens with your worktree, and Cline automatically opens ready to work.
<Tip>
The home screen also shows your current branch and worktree path. Click it to open the full Worktrees view.
</Tip>
### Full Worktrees View
For more control, open the full Worktrees view by clicking the **Worktrees** button in the Cline sidebar header, or by clicking your current branch info on the home screen:
<Steps>
<Step title="Create a New Worktree">
Click **New Worktree** at the bottom of the view. Enter a branch name and path (defaults are auto-filled).
</Step>
<Step title="Open in New Window">
Once created, click the **Open in new window** button to open the worktree in a separate VS Code window. Cline will automatically open in the new window.
</Step>
</Steps>
## Typical Workflow
Here's how a typical worktree session looks:
<Steps>
<Step title="Create a new worktree">
Click **New Worktree Window** on the home screen or use the Worktrees view. A new VS Code window opens with Cline ready to go.
</Step>
<Step title="Do your work">
Work on your feature or let Cline handle a task. Make commits as you go.
</Step>
<Step title="Close the worktree window">
When you're done, close the worktree's VS Code window.
</Step>
<Step title="Merge from your primary worktree">
Back in your main VS Code window, open the Worktrees view and click the **merge button** on the worktree you just worked in. This merges the branch and optionally deletes the worktree.
</Step>
</Steps>
## Managing Worktrees
### Viewing Worktrees
The Worktrees view shows all worktrees for your repository:
- **Current**: The worktree you're currently in (highlighted)
- **Main**: The primary worktree where your `.git` directory lives (cannot be deleted)
- **Locked**: Worktrees that are locked to prevent accidental deletion
### Opening Worktrees
Each worktree has two open options:
- **Open in current window**: Replace your current workspace with the worktree
- **Open in new window**: Open the worktree in a separate VS Code window (recommended for parallel Cline sessions)
Either way, Cline automatically opens in the new workspace, ready to start a task.
### Deleting Worktrees
Click the trash icon on any linked worktree to delete it. A confirmation dialog will show you exactly what will be deleted:
- The branch itself
- All project files in the worktree folder
<Warning>
Deleting a worktree permanently removes the branch and all files in that folder. Make sure any important changes are committed and pushed first.
</Warning>
<Note>
You cannot delete the main worktree. It's the primary repository where your `.git` directory lives.
</Note>
### Merging Worktrees
When you're done working in a worktree and ready to merge your changes back to the main branch:
1. Click the **merge icon** (git merge symbol) on any linked worktree
2. Review the merge details in the confirmation modal
3. Choose whether to delete the worktree after merging
4. Click **Merge**
<Frame>
<img
src="https://storage.googleapis.com/cline_public_images/docs/assets/worktrees-merge.png"
alt="Merge worktree modal"
/>
</Frame>
#### Handling Merge Conflicts
If your branch has conflicts with the main branch, Cline will detect them and show you the conflicting files. You have two options:
1. **Ask Cline to Resolve & Merge** - Creates a new Cline task with a prompt asking Cline to resolve the conflicts, complete the merge, and clean up the worktree
2. **Resolve Manually** - Close the modal and resolve conflicts yourself using your preferred Git tools
<Tip>
The "Ask Cline to Resolve" option is particularly useful for complex conflicts. Cline will analyze the conflicting files and attempt to merge them intelligently based on the intent of both branches.
</Tip>
## .worktreeinclude: Automatic File Copying
When you create a new worktree, it starts with a fresh checkout—no `node_modules`, no build artifacts, no IDE settings. This means you'd normally need to run `npm install` or similar setup commands.
The `.worktreeinclude` file solves this by automatically copying specified files to new worktrees.
### How It Works
1. Create a `.worktreeinclude` file in your repository root
2. Add glob patterns for files you want copied (using `.gitignore` syntax)
3. When Cline creates a new worktree, files matching **both** `.worktreeinclude` **and** `.gitignore` are copied automatically
<Note>
Only files that are both matched by `.worktreeinclude` AND listed in `.gitignore` are copied. This prevents accidentally duplicating tracked files.
</Note>
### Example `.worktreeinclude`
```gitignore
# Copy node_modules to avoid npm install
node_modules/
# Copy IDE settings
.vscode/
# Copy build cache
.next/
dist/
# Copy environment files (if gitignored)
.env.local
```
### Creating a `.worktreeinclude` File
The Worktrees view will show a tip if you don't have a `.worktreeinclude` file. If you have a `.gitignore`, you can click **Create from .gitignore** to create one pre-filled with your gitignore contents. Then edit it to keep only the patterns you want copied.
<Tip>
For most JavaScript/TypeScript projects, just including `node_modules/` in your `.worktreeinclude` saves significant setup time for each new worktree.
</Tip>
### Pro Tip: Symlink to .gitignore
Since `.gitignore` usually contains most of the files you'd want copied to new worktrees (dependencies, environment files, build caches, etc.), you can create a symlink so they stay in sync automatically:
```bash
# In your repository root
ln -s .gitignore .worktreeinclude
```
Now whenever you update your `.gitignore`, your `.worktreeinclude` will have the same patterns. This is especially useful for projects where gitignored files are exactly what you want copied—no need to maintain two separate files.
<Note>
If you need different patterns than your `.gitignore`, create a regular `.worktreeinclude` file instead of a symlink.
</Note>
## Best Practices
<AccordionGroup>
<Accordion title="For Parallel Cline Sessions">
1. **Create purpose-specific worktrees** - Name branches clearly (e.g., `cline/refactor-auth`, `cline/add-tests`)
2. **Open in new windows** - Always use "Open in new window" for true parallelism
3. **Use .worktreeinclude** - Set up automatic file copying to reduce setup time
</Accordion>
<Accordion title="For Solo Development">
1. **Keep your main branch clean** - Use worktrees for experimental or risky changes
2. **Quick feature switches** - Instead of stashing, create a worktree for interruptions
3. **Review in isolation** - Create worktrees to review PRs without disrupting your work
</Accordion>
<Accordion title="Worktree Hygiene">
1. **Delete unused worktrees** - Remove worktrees when their branches are merged
2. **Use meaningful names** - Branch names should indicate the worktree's purpose
3. **Check for stale worktrees** - Periodically review and clean up old worktrees
</Accordion>
</AccordionGroup>
## Limitations
Worktrees are not available in certain workspace configurations:
- **Multi-root workspaces**: If you have multiple folders open in VS Code, worktrees are disabled. Open a single repository folder instead.
- **Subfolder of a repository**: If you've opened a subfolder within a Git repository (not the root), worktrees are disabled. Open the repository root folder instead.
The Worktrees view will display a message explaining the limitation if either of these applies to your workspace.
## Troubleshooting
<AccordionGroup>
<Accordion title="Branch already exists error">
Git doesn't allow the same branch to be checked out in multiple worktrees. Either:
- Use a different branch name
- Delete the existing worktree using that branch
</Accordion>
<Accordion title="Worktree folder already exists">
The path you specified already contains files. Choose a different path or delete the existing folder first.
</Accordion>
<Accordion title="Can't delete worktree">
If a worktree is locked, you'll need to unlock it first using `git worktree unlock <path>` in the terminal. If the worktree has uncommitted changes, you may need to use force delete.
</Accordion>
<Accordion title=".worktreeinclude files not copying">
Make sure the files you want copied are:
1. Listed in your `.worktreeinclude` file
2. Also listed in your `.gitignore` (only gitignored files are copied)
3. Actually exist in your current worktree
</Accordion>
</AccordionGroup>
## Technical Details
<AccordionGroup>
<Accordion title="How Worktrees Work Internally">
- Worktrees are a native Git feature (`git worktree` command)
- All worktrees share the same `.git` directory and object database
- Each worktree has its own index, working directory, and HEAD
- Worktree list is stored in `.git/worktrees/`
</Accordion>
<Accordion title="Storage Considerations">
- Each worktree contains a full checkout of the repository
- `.worktreeinclude` can significantly increase worktree size (e.g., copying `node_modules`)
- Consider your disk space when creating many worktrees
</Accordion>
<Accordion title="Relationship with Checkpoints">
Worktrees are separate from Cline's [checkpoint system](/features/checkpoints). Each worktree has its own checkpoint history. Checkpoints track changes within a single worktree, while worktrees let you work across multiple branches simultaneously.
</Accordion>
</AccordionGroup>
Worktrees unlock true parallel development with Cline. Create a worktree, open it in a new window, and let Cline work independently while you continue coding!
+19 -2
View File
@@ -25,11 +25,28 @@ The easiest way to get started is with **Cline** as your provider:
## Alternative: Use Another Provider
If you prefer to use your own API keys, you can select from providers like:
If you prefer to use your own accounts or API keys, you have several options:
### Sign in with OpenAI (Recommended)
The easiest way to use OpenAI models is with **OpenAI Codex**—no API keys needed:
1. Select **"OpenAI Codex"** from the API Provider dropdown
2. Click **"Sign in with OpenAI"**
3. Authorize Cline in your browser
4. Choose your model
<Card title="OpenAI Codex Setup Guide" icon="key" href="/provider-config/openai-codex">
See the full setup guide with screenshots and troubleshooting tips.
</Card>
### Other Providers
You can also use API keys with these providers:
- **OpenRouter** - Great value, multiple models
- **Anthropic** - Direct access to Claude models
- **OpenAI** - Access to GPT models
- **OpenAI** - Access to GPT models via API key
- **Google Gemini** - Google's AI models
- **Ollama** - Run models locally on your computer
+2 -2
View File
@@ -18,7 +18,7 @@ Cerebras delivers the world's fastest AI inference through their revolutionary w
Cline supports the following Cerebras models:
- `zai-glm-4.6` - Intelligent general purpose model with 1,500 tokens/s
- `zai-glm-4.7` - Highly capable general-purpose model on Cerebras (up to 1,000 tokens/s), competitive with leading proprietary models on coding tasks.
- `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
@@ -89,7 +89,7 @@ Works with any OpenAI-compatible tool—Cursor, Continue.dev, Cline, or any othe
- **Speed Advantage:** Cerebras excels at making reasoning models practical for real-time use. Perfect for agentic workflows that require multiple LLM calls.
- **Free Tier:** Start with the free model to experience Cerebras speed before upgrading to paid plans.
- **Context Windows:** Models support context windows ranging from 64K to 128K tokens for including substantial code context.
- **Context Windows:** Models support context windows ranging from 64K to 131K tokens for including substantial code context.
- **Rate Limits:** Generous rate limits designed for development workflows. Check your dashboard for current limits.
- **Pricing:** Competitive pricing with significant speed advantages. Visit [Cerebras Cloud](https://cloud.cerebras.ai/) for current rates.
- **Real-Time Applications:** Ideal for applications where AI response time matters—code generation, debugging, and interactive development.
+105
View File
@@ -0,0 +1,105 @@
---
title: "OpenAI Codex"
sidebarTitle: "OpenAI Codex"
description: "Connect your OpenAI account to Cline via OAuth for seamless access to OpenAI models."
---
OpenAI Codex lets you connect your OpenAI account directly to Cline using OAuth, meaning you don't need to manage API keys. Simply sign in with your OpenAI account through a one-click browser authentication, and you'll automatically have access to all the models available on your OpenAI plan. No additional configuration required.
<Tip>
If you prefer to use API keys instead, see the [OpenAI (API Key)](/provider-config/openai) provider configuration.
</Tip>
## Prerequisites
- An [OpenAI account](https://platform.openai.com/signup)
- An active OpenAI subscription or API access plan
<Note>
The models available to you depend on your OpenAI account's subscription tier. See [OpenAI's pricing page](https://openai.com/pricing) for details on what's included in each plan.
</Note>
## Connecting Your OpenAI Account
<Steps>
<Step title="Open Cline Settings">
Click the settings gear icon in the top-right corner of the Cline extension.
</Step>
<Step title="Select OpenAI Codex Provider">
Choose **"OpenAI Codex"** from the "API Provider" dropdown menu.
</Step>
<Step title="Sign In with OpenAI">
Click the **"Sign in with OpenAI"** button. This will open a browser window for authentication.
</Step>
<Step title="Authorize Cline">
In the browser window that opens:
1. Sign in to your OpenAI account (if not already signed in)
2. Review the permissions Cline is requesting
3. Click **"Authorize"** to grant access
</Step>
<Step title="Select Your Model">
Once authorized, you'll be returned to Cline. Choose your desired model from the **"Model"** dropdown.
</Step>
</Steps>
<Note>
If a model doesn't appear in your dropdown, it may not be available on your OpenAI subscription tier.
</Note>
For the most current list of available models and their capabilities, see the official [OpenAI Models documentation](https://platform.openai.com/docs/models).
## Managing Your Connection
### Disconnecting Your Account
To disconnect your OpenAI account from Cline:
1. Open Cline Settings
2. With OpenAI Codex selected as the provider, click **"Sign out"**
3. Confirm the disconnection
This removes the OAuth connection. You can reconnect at any time by signing in again.
### Token Refresh
OAuth tokens are automatically refreshed by Cline. If you encounter authentication errors:
1. Try disconnecting and reconnecting your account
2. Ensure your OpenAI account is in good standing
3. Check that your subscription is active
## Troubleshooting
### "Authentication failed" error
- Ensure you're signing in with the correct OpenAI account
- Check that your OpenAI subscription is active
- Try clearing your browser cache and signing in again
### Models not appearing
- The available models depend on your OpenAI subscription tier
- Some models may require specific plan upgrades
- Check [OpenAI's pricing page](https://openai.com/pricing) for model availability by plan
### OAuth window doesn't open
- Check if pop-ups are blocked in your browser
- Try using a different browser
- Ensure you have a stable internet connection
### Connection keeps expiring
- This is rare but can happen if your OpenAI account session expired
- Disconnect and reconnect to refresh your authentication
## Good to Know
Cline only requests the permissions necessary to make API calls on your behalf. Your OpenAI credentials are never stored by Cline.
When using OpenAI Codex, Cline accesses models through your OpenAI/ChatGPT subscription. There is no separate per-token API billing or consumption of OpenAI API credits for this provider; usage and limits are governed by your ChatGPT (or OpenAI account) subscription plan, not by Cline.
## Related Documentation
- [OpenAI (API Key)](/provider-config/openai): Alternative configuration using API keys
- [Model Selection Guide](/core-features/model-selection-guide): Help choosing the right model
+2 -1
View File
@@ -16,7 +16,7 @@ This document focuses on setting up providers _other than_ the official OpenAI A
The key to using an OpenAI-compatible provider with Cline is to configure these main settings:
1. **Base URL:** This is the API endpoint specific to the provider. It will _not_ be `https://api.openai.com/v1` (that URL is for the official OpenAI API).
2. **API Key:** This is the secret key you obtain from your chosen provider.
2. **API Key:** This is the secret key you obtain from your chosen provider. (or **Use Azure Identity Authentication**)
3. **Model ID:** This is the specific name or identifier for the model you wish to use.
You'll find these settings in the Cline settings panel (click the ⚙️ icon):
@@ -25,6 +25,7 @@ You'll find these settings in the Cline settings panel (click the ⚙️ icon):
- **Base URL:** Enter the base URL provided by your chosen provider. **This is a crucial step.**
- **API Key:** Enter your API key from the provider.
- **Model:** Choose or enter the model ID.
- **Use Azure Identity Authentication:** Check the box to authenticate with your Azure managed identity (Note that this will not trigger the authentication, it will use the existing one (e.g., "az login"))
- **Model Configuration:** This section allows you to customize advanced parameters for the model, such as:
- Max Output Tokens
- Context Window size
+8 -11
View File
@@ -16,20 +16,17 @@ Cline supports accessing models directly through the official OpenAI API.
### Supported Models
Cline is compatible with a variety of OpenAI models, including but not limited to:
Cline is compatible with a variety of OpenAI models, including common choices from OpenAI's featured/frontier lists:
- 'o3'
- `o3-mini` (medium reasoning effort)
- 'o4-mini'
- `o3-mini-high` (high reasoning effort)
- `o3-mini-low` (low reasoning effort)
- `o1`
- `o1-preview`
- `o1-mini`
- `gpt-5.2`
- `gpt-5.2-codex`
- `gpt-5-mini`
- `gpt-5-nano`
- `gpt-4.1`
- `gpt-4o`
- `gpt-4o-mini`
- 'gpt-4.1'
- 'gpt-4.1-mini'
- `o3`
- `o4-mini`
For the most current list of available models and their capabilities, please refer to the official [OpenAI Models documentation](https://platform.openai.com/docs/models).
+4 -4
View File
@@ -73,16 +73,16 @@ Open VS Code and configure Cline:
### Recommended Models
For the best experience with Cline, use **Qwen3 Coder 30B**. This model provides strong coding capabilities and reliable tool use for local development.
For the best experience with Cline, use **Qwen 2.5 Coder 32B**. This model provides strong coding capabilities and reliable tool use for local development.
To download it:
```bash
ollama run qwen3-coder-30b
ollama pull qwen2.5-coder:32b
```
Other capable models include:
- `mistral-small` - Good balance of performance and speed
- `devstral-small` - Optimized for coding tasks
- `mistral-small:latest` - Good balance of performance and speed
- `codellama:34b-code` - Optimized for coding tasks
### Important Notes

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