Compare commits

..

86 Commits

Author SHA1 Message Date
CandiedUniverse 0e7e0099cd Changelog and version bump for release (#9775) 2026-03-11 17:21:04 -07:00
AJ Juaire 44cbfe9d9c Update Jupyter Notebook gifs (#9772)
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
2026-03-11 16:25:46 -07:00
Saoud Rizwan 4ecccf5105 feat(cli): add mcp add shortcuts for stdio and http servers (#9773) 2026-03-11 16:17:43 -07:00
Ara 6d29bc6551 fix(prompt): resolve native tool placeholder interpolation (#9764)
* fix(prompt): resolve CWD and MULTI_ROOT_HINT in native tool schemas

* refactor(prompt): share multi-root hint constant
2026-03-11 08:10:58 -07:00
Saoud Rizwan 820057308a feat(cli): add kanban alias command (#9763) 2026-03-10 21:57:16 -07:00
Ara 7355f7e3b9 fix(gemini): cap flash output tokens to 8192 across providers (#9749) 2026-03-10 14:10:14 -07:00
Max 156f18f7b2 call telemetry service after initializeCli (#9752)
- calling telemetry service before initializeCli call causes a
"hostprovider not initialized error", which invokes errorservice, which
causes another "hostprovider not initialized error", which was breaking
this cline use case: echo "say hello" | cline

Co-authored-by: Max Paulus 🥪 <max@cline.bot>
2026-03-10 11:50:34 -07:00
Tomás Barreiro 6129caa423 Get user consent before loading images in Markdown (#9745)
* Add an UsafeImage handler that asks for consent before loading specific images

* Fix div as child of p

* Render self-contained images without consent

* Render alt conditionally and store approved src

* use a block span
2026-03-09 21:06:57 -07:00
alex-lum ffa4785f43 fix(telemetry): restore cache token and cost metrics in captureTokenUsage (#9741)
* feat(telemetry): restore cache token and cost metrics in captureTokenUsage

Add optional `options` parameter to `captureTokenUsage()` to record
cache write/read token counters and cost histograms that were
previously missing from telemetry.

- Extend `captureTokenUsage` with `cacheWriteTokens`, `cacheReadTokens`,
  and `totalCost` fields via an options object
- Record `cline.tokens.cache_write`, `cline.tokens.cache_read` counters/
  histograms and `cline.tokens.cost` histogram when provided
- Forward cache/cost data from both streaming `onUsageChunk` and
  `getApiStreamUsage` fallback call sites in the task loop
- Add 3 test cases covering options forwarding, undefined skipping,
  and event property inclusion

* refactor(telemetry): extract shared TokenUsage type and add value assertions

Address PR review feedback:
- Extract shared TokenUsage interface used by both captureTokenUsage and
  captureConversationTurnEvent, preventing future drift
- Add numeric value assertions for cache/cost counters and histograms
  so regressions recording wrong values are caught
2026-03-09 15:55:23 -07:00
CandiedUniverse 92ec126dca Fix Windows unit test path normalization (#9742) 2026-03-09 13:23:47 -07:00
Ara c4c8b16afb chore: remove changesets bot artifacts (#9739) 2026-03-09 12:26:48 -07:00
Max 2eed79dcd7 use setSessionOverride instead of setGlobalState (#9707)
- cli was storing fields to persistent state when it shouldn't be. The
value of these flags should only live for the duration of the CLI
session

Co-authored-by: Max Paulus 🥪 <max@cline.bot>
2026-03-09 11:20:57 -07:00
shey-cline d39a53feed Add Padding to Retry Message (#9635) 2026-03-09 09:29:27 -07:00
Dominic Cooney cae9bff416 test: rebaseline vertex_gemini3.tools.snap (#9731)
Commit 55569efb7 changed the list_code_definition_names path parameter
description but did not update the snapshot baseline.
2026-03-09 07:57:05 -07:00
Saoud Rizwan 36618fb327 fix(cli): remount TUI only on width resize (#9736) 2026-03-09 04:03:59 -07:00
Saoud Rizwan a335b5cb3e fix(cli): prevent startup prompt replay on resize remount (#9735) 2026-03-09 03:30:01 -07:00
Saoud Rizwan bce71b4448 feat(cli): add --continue for current directory (#9726)
* feat: add cli continue flag for current directory

* docs(cli): clarify continue command examples
2026-03-08 19:54:44 -07:00
Robin Newhouse f6d2b4d9ac Add --auto-condense CLI flag to enable AI-powered context compaction (#9705)
Exposes the existing useAutoCondense setting as a CLI flag, following
the same pattern as --double-check-completion. This allows enabling
auto-condense in eval runs (e.g. SWE-bench via Harbor) to reduce
context exhaustion failures.

Made-with: Cursor
2026-03-08 16:28:48 -07:00
Robin Newhouse 911fcfda47 gemini-hc: add parameter descriptions to Gemini tool schemas (#9681)
The Gemini converter was the only provider that didn't include
parameter-level descriptions in native tool call schemas. Anthropic
and OpenAI converters both resolve param.instruction into each
parameter's description field. This was missing for Google/Gemini,
meaning the model only saw parameter names and types with no
explanation of what each parameter expects.

Made-with: Cursor
2026-03-08 16:27:50 -07:00
Robin Newhouse 55569efb74 fix(tools): prevent crash when list_files/list_code_definition_names receives a file path (#9680)
listFiles() passed unvalidated paths as globby's `cwd`, crashing with
"The cwd option must be a path to a directory" when the model provided a
file path instead of a directory. This affected ~22% of SWE-bench tasks.

- Add isDirectory guard in listFiles() before calling globby
- Fix listFiles() to use resolved absolutePath for cwd instead of raw dirPath
- Return actionable error in parseSourceCodeForDefinitionsTopLevel when
  path is a file, guiding the model to use read_file instead
- Clarify list_code_definition_names parameter description to
  distinguish directory input from file input

Made-with: Cursor
2026-03-08 16:07:03 -07:00
Robin Newhouse 18561ed59d prompt: add test verification rules and make CLI_RULES language-agnostic (#9679)
- S1: Don't modify test assertions to match buggy code
- S2: Run project's existing test suite to verify fixes
- CLI_RULES: Remove Node.js-specific examples (npm/tsc)

Made-with: Cursor
2026-03-08 15:58:13 -07:00
Saoud Rizwan fa7265fa33 feat: add Anthropic Opus 4.6 fast mode variants (#9725)
* feat: add Anthropic Opus 4.6 fast mode variants

* refactor: localize Anthropic fast mode beta constant

* fix: correct Anthropic 1M fast mode pricing
2026-03-08 15:31:37 -07:00
M.Yoshida(Jyuko Co.,Ltd) bf96223639 docs: add .github/copilot-instructions.md for AI coding agents (#9606)
* docs: add .github/copilot-instructions.md for AI coding agents

* docs: update copilot-instructions with provider, state, and CLI guidance
2026-03-08 14:51:55 -07:00
Saoud Rizwan c489f79508 fix(cli): apply task flags before welcome TUI mount (#9721) 2026-03-07 11:20:53 -08:00
CandiedUniverse 6ccd662a1f Hooks: Add feature toggle (toggled off by default) (#9671)
* Add implementation plan doc

* feat(hooks): reintroduce runtime hooks feature toggle

* fix: thread effective hooks toggle through hook execution

* test: cover hooks feature toggle visibility and settings wiring

* Remove implementation plan doc

* Move Hooks toggle to Advanced section in Feature Settings

* Fixes as per PR feedback

* Clarifying hooksEnabled

* Make hooksEnabled true by default

* Further fixes as per Greptile feedback

* Further fixes as per Greptile feedback

* Fix failing tests
2026-03-06 13:49:03 -08:00
Tomás Barreiro a2fa4d3b92 [ENG-1571] Enable CLI Error autocapture (#9686)
* Introduce the enableErrorAutocapture option

* Enable capturing CLI extension

* Capture exception immediately

* Handled uncaptured exceptions

* refactor

* Add tests

* Capture unhandledExceptions

* Add an error boundary to the ink app

* Wrap the App in the ErrorBoundary

* Check for consent before capturing error

* Add context to the error capturing

* Fix tests

* refactor

* Remove `unref`
2026-03-06 20:39:44 +01:00
CandiedUniverse c8f81374a3 Fix flaky hooks tests on Windows (#9690)
* test(hooks): reduce Windows hook-runner launch overhead

test(hooks): make Windows hook tests deterministic

refactor(hooks): use platform-agnostic launcher cache naming

Improvements as per Cline code review feedback

test(hooks): remove direct-node acceleration path and stabilize windows timings

* test(hooks): add deterministic launcher cache concurrency coverage

* Improvements as per Cline code review feedback

* Update src/core/hooks/__tests__/hookprocess.test.ts

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* Improvements as per Greptile feedback

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-03-06 10:46:34 -08:00
ClineXDiego eb25634bd4 fix(bedrock): handle thinking and redacted_thinking blocks in message conversion and streaming (#9424)
Fixes #9269 - Thinking blocks missing in Bedrock Opus 4.6

Changes:
- Add explicit handling for 'thinking' and 'redacted_thinking' content types
  in formatMessagesForConverseAPI() so they are silently skipped instead of
  triggering 'Unsupported content type: thinking' warnings
- Capture signature from additionalModelResponseFields thinking responses
- Add signature_delta handling in contentBlockDelta for streaming
- Add redacted_thinking block handling in contentBlockStart for streaming
- Extend ContentBlockStart/Delta interfaces with signature and data fields
- Add 'redacted_thinking' and 'document' to SupportedContentType union
- Add tests for thinking/redacted_thinking block filtering in message conversion
2026-03-06 10:41:43 -08:00
Max 1753d9dc15 release changelog and version bump (#9706)
Co-authored-by: Max Paulus 🥪 <max@cline.bot>
2026-03-06 09:45:20 -08:00
Saoud Rizwan a562868cb9 feat(cli): add --auto-approve-all flag for interactive mode (#9698) 2026-03-06 02:08:59 -08:00
Saoud Rizwan bf5265758f feat(hooks): add Notification hook for attention boundaries (#9699)
* feat(hooks): add Notification hook for attention and completion

* chore(hooks): use default Notification template

* Revert "chore(hooks): use default Notification template"

This reverts commit 85f4e942fc.

* fix(hooks): escape JSON quotes in bash templates

* Revert "fix(hooks): escape JSON quotes in bash templates"

This reverts commit 29dfb9e01e.

* chore(hooks): preserve single-backslash template escaping

* fix(hooks): keep single-backslash JSON escaping in templates

* Revert "fix(hooks): keep single-backslash JSON escaping in templates"

This reverts commit b4c829489f.

* fix(hooks): keep escaped JSON echo template pattern
2026-03-06 02:07:25 -08:00
Saoud Rizwan 469752a201 feat(cli): add --hooks-dir flag for runtime hook injection (#9658)
* feat(cli): add --hooks-dir flag for runtime hook injection

Adds a --hooks-dir <path> CLI flag that allows passing an additional
hooks directory at spawn time. This enables orchestration tools (like
Kanbanana) to inject per-session lifecycle hooks without mutating
the user's global or workspace hooks directories.

The runtime hooks directory is included alongside existing global
(~/Documents/Cline/Hooks/) and workspace (.clinerules/hooks/)
directories during hook discovery. All hooks from all directories
are merged and run in parallel, so runtime hooks are purely additive.

* fix(cli): initialize runtime hooks before interactive startup
2026-03-05 23:33:41 -08:00
Ara 8fb2e8a3aa Adding gpt 5.4 models to chatgpt subscripiton (#9692)
* Adding gpt-5.4 to chatgpt subscription

* Fixing stuff

* Apply suggestion from @greptile-apps[bot]

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-03-05 18:06:17 -08:00
ClineXDiego aef32f52f9 fix: bypass git hooks on checkpoint initial commit (#9688)
Add --no-verify to the initial checkpoint commit in
CheckpointGitOperations.ts. This was already used for subsequent
commits in CheckpointTracker.ts but was missing from the initial
empty commit, causing Cline to fail to initialize when users have
global pre-commit hooks (e.g., conventional commits enforcement).

Fixes #9672
2026-03-05 19:02:00 -03:00
Dominic Cooney 2baf966db7 fix: handle streamableHttp reconnects and preserve OAuth redirect URIs across sessions (#9642)
- Make redirect URIs with dynamic ports valid, or reregister.
- Handle reconnects for streaming HTTP MCP servers.
2026-03-05 09:00:20 +09:00
alex-lum 152ba674da feat: add OTel tracking for AI output line/file metrics (lines added/deleted/changed, files created/deleted/moved) (#9562)
* feat: add telemetry for AI output accepted/rejected across tool handlers

Add line-level diff stats and file operation tracking to telemetry
events when users accept or reject tool outputs. Introduces a shared
`computeLineDiffStats` utility and `captureAiOutputAccepted`/
`captureAiOutputRejected` methods on the telemetry service, wired
into ApplyPatch, WriteToFile, ExecuteCommand, InsertContent, and
SearchAndReplace handlers.

* feat(telemetry): add source tracking for agent vs human edits

Add telemetry differentiation between agent-generated changes and
human modifications to capture more granular edit metrics:

- Add 'source' field to captureAiOutputAccepted telemetry events
- Track human edits by computing diff stats between agent's proposed
  content and final saved content
- Apply source tracking to ApplyPatchHandler and WriteToFileToolHandler
- Enable separate analytics for agent vs human contributions

This allows measuring how often and to what extent users modify
AI-generated code, providing insights into AI output quality and
user trust patterns.

* refactor(telemetry): centralize ai output attribution across file edit handlers

- add shared `AiOutputTelemetry` utility for accepted/rejected events
- refactor `WriteToFileToolHandler` and `ApplyPatchHandler` to use shared helpers
- preserve existing telemetry behavior (`source: "agent" | "human"`) while reducing duplication
- keep line diff/file-op attribution semantics unchanged

* fix(telemetry): use pre-save content for human edit line diff stats

The source:"human" telemetry was diffing agent content against
finalContent (post-save), which includes auto-formatting changes
from the editor. This inflated linesChanged/linesDeleted counts
when the formatter modified lines alongside the user's actual edits.

Use diff.applyPatch() to reconstruct the user's pre-save content
from the existing userEdits patch, excluding formatter noise from
the line diff stats.

* fixing syntax error

* refactor(telemetry): make next-hunk bounds check explicit

* remove comment

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

---------

Co-authored-by: Tomás Barreiro <52393857+BarreiroT@users.noreply.github.com>
2026-03-04 14:45:57 -08:00
Max 12f5dc2e9e update changelog and bump version numbers (#9664)
Co-authored-by: Max Paulus 🥪 <max@cline.bot>
2026-03-04 13:39:15 -08:00
Saoud Rizwan 28d806a4e4 fix(openrouter): stop sending max_tokens in stream requests (#9634) 2026-03-04 18:48:59 +01:00
Ara e92c7de8c0 Restrict /test-jetbrains workflow trigger to authorized users (#9657)
Add author_association check so only MEMBER, OWNER, and COLLABORATOR
users can trigger the JetBrains test workflow via issue comments.
Previously any GitHub user could trigger it, allowing unauthorized
use of the GitHub App token and Actions minutes.

Fixes GHSA-5fq9-fh5x-w83r (SEC-29)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 09:13:41 -08:00
CandiedUniverse 718e5b53f6 Add model identifier to the JSON payload that hooks receive (#9646)
* Add provider/model context to all hook payloads

* Fixes as per Greptile feedback

* Further fixes as per Greptile feedback

* Further fixes as per Greptile feedback

* further fixes as per Greptile feedback

* Fix flapping hooks tests on Windows
2026-03-03 19:08:41 -08:00
Saoud Rizwan e484534bcb fix(task): process usage chunks independently from non-usage stream flow (#9576) 2026-03-03 12:27:20 -08:00
Dominic Cooney 2822723495 fix: stop infinite getLatestMcpServers RPC loop when opening MCP servers panel (#9643)
* fix: stop infinite getLatestMcpServers RPC loop when opening MCP servers panel

The ServersToggleModal useEffect had setMcpServers in its dependency array,
but the context provided an unstable inline wrapper around the useState setter,
creating a new function reference on every render. This caused the effect to
re-fire on every context re-render while the modal was visible, producing 14+
RPC calls in ~15ms.

- Remove setMcpServers from useEffect deps in ServersToggleModal (the effect
  should only fire when visibility changes)
- Replace inline arrow wrappers in ExtensionStateContext with direct references
  to the stable useState setters (setMcpServers, setRequestyModels,
  setHuggingFaceModels, setMcpMarketplaceCatalog)

* address review feedback: add setMcpServers back to deps, use property shorthand

- Add setMcpServers back to useEffect deps in ServersToggleModal now that the
  context passes the stable useState setter directly (per Copilot review)
- Remove eslint-disable comment since it's no longer needed
- Use property shorthand for setGroqModels and setBasetenModels in context value
2026-03-03 09:08:34 -08:00
Saoud Rizwan b5c0cc18d8 fix(subagent): fix subagents erroring out by handling context-window limit errors (#9637)
* fix(subagent): align error handling with main loop behavior

* fix(subagent): persist context truncation state across retries
2026-03-02 22:13:32 -08:00
Dominic Cooney fb04a9bd15 Log RPC size histogram. (#9626) 2026-03-03 14:53:30 +09:00
Renee Huang 6def83a5d9 [doc] api doc changes (#9581)
* initial doc changes

* rm general api endpoint

* Add API documentation section with endpoint reference pages

- Add new API docs: overview, getting-started, authentication, models,
  chat-completions, errors, and SDK examples
- Update api/reference.mdx with expanded endpoint documentation
- Update enterprise-solutions/api-reference.mdx with improvements
- Update docs.json with new API section navigation entries

---------

Co-authored-by: Juan Pablo <juan@cline.bot>
Co-authored-by: Tony Loehr <turingxo@gmail.com>
2026-03-02 16:22:39 -08:00
CandiedUniverse fc3d986d05 Release: version bump and changelog updates (#9640)
* Update changelog for release

* Version bump the package*.json files for release
2026-03-02 15:53:06 -08:00
Saoud Rizwan 520ddb83bb fix(checkpoints): retry nested git restore and prevent silent .git_disabled leftovers (#9620)
* fix(checkpoints): harden nested git repo restore cleanup

* Fix checkpoint initialization to take less time

---------

Co-authored-by: cline-test <132302818+candieduniverse@users.noreply.github.com>
2026-03-02 12:22:01 -08:00
Ara d96c5d4b40 workflow: add default auto-tag flow for Publish Release (#9584)
* workflow: add auto-tag mode to publish release

* workflow: pin auto-tag release to tested commit sha

* workflow: clarify publish release input semantics

* workflow: constrain publish tag input to refs/tags
2026-03-02 11:38:48 -08:00
Br1an e6cbae0edc fix: prevent Chinese filename escaping in diff view (#9612)
* fix: prevent Chinese filename escaping in diff view

Use Uri.parse() instead of Uri.from() for the diff view URI to prevent
non-ASCII characters (e.g. Chinese) in filenames from being
percent-encoded. This is consistent with how other diff URIs are created
in openMultiFileDiff.ts and VscodeCommentReviewController.ts.

Uri.from() encodes the path component, turning Chinese characters into
percent-encoded sequences like %E7%A0%94..., which causes the diff view
to display escaped filenames and fail to open properly.

* fix: encode URI-reserved delimiters in filename before Uri.parse

Encode %, #, and ? in the filename before passing to Uri.parse() to
prevent them from being interpreted as URI delimiters. This handles
edge cases where filenames contain these characters (valid on macOS/Linux)
while preserving non-ASCII characters like Chinese.
2026-03-02 10:35:48 -08:00
Saoud Rizwan 76bd0926e6 fix: trigger auto-compaction on OpenRouter context overflow errors (#9633)
* fix(context): detect wrapped OpenRouter 400 context errors

* fix(openrouter): preserve status for context overflow detection

* docs(context): clarify OpenRouter error-shape handling

* Revert "docs(context): clarify OpenRouter error-shape handling"

This reverts commit 9458d4472b.

* Revert "fix(openrouter): preserve status for context overflow detection"

This reverts commit a2c76d1693.

* Revert "fix(context): detect wrapped OpenRouter 400 context errors"

This reverts commit 3e8fb10b9e.

* Reapply "fix(context): detect wrapped OpenRouter 400 context errors"

This reverts commit 6df27d6853.

* Reapply "docs(context): clarify OpenRouter error-shape handling"

This reverts commit beb52e1429.

* fix(context): narrow OpenRouter status parsing fallback

* fix(context): align OpenRouter status parsing with agreed shape
2026-03-02 10:34:27 -08:00
Renee Huang 819f9ce00d show sdk docs in cline page (#9597) 2026-03-02 10:14:41 -08:00
Max fd8cecddd5 update cline sdk docs (#9532) 2026-02-27 11:28:48 -08:00
CandiedUniverse a502bd8653 WIP: Make hooks work on Windows PowerShell (#9552)
* feat(hooks): add Windows hook execution via PowerShell

* chore(changeset): add release note for Windows hooks

* Get hooks working on Windows

Remove changeset file (we no longer use changeset files)

feat(hooks): support Windows PowerShell hook resolution and management

feat(hooks): complete windows powershell hook support and tests

Detect linux-style hooks only on macOS and linux and detect PowerShell-style hooks only on Windows

Fixes for failing unit tests on Windows in CI

Fix failing unit tests on Windows in CI

Fix unit tests for hooks on Windows

Be clear about .ps1 file extension for hooks in PowerShell vs. bash/binary for linux-style hooks

Remove separate test suite step

Reapply hooks-specific test suite

Fix failing hooks tests

* Harden Windows hook PowerShell runtime and test coverage

* test: centralize hook test env and platform overrides

---------

Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
2026-02-27 10:51:46 -08:00
Robin Newhouse d48d5ee74d fix: restore gpt-oss native file editing on OpenAI-compatible models (#9434)
* fix(core): enable gpt-oss native file editing

* test(evals): add gpt-oss openai-compat smoke coverage
2026-02-27 10:18:25 -08:00
Tomás Barreiro cd320ea01f Add User-Agent to requests to the Cline back-end (#9583) 2026-02-26 18:52:31 -08:00
CandiedUniverse 60485277c4 Version bump and changelog for release (#9577) 2026-02-26 15:57:14 -08:00
ClineXDiego 0a8f1ef248 fix: clear all OCA secrets on auth refresh failure to prevent re-auth loop (#9569)
When OCA token refresh fails with 400 invalid_grant or 401, legacy secrets
ocaAccessToken and ocaTokenSet (from older Cline versions) were left in VS
Code's secret storage. clearAuth() only cleared ocaApiKey and ocaRefreshToken,
causing every subsequent re-auth attempt to fail in a loop requiring manual
SQLite deletion to recover.

Fix:
- Add ocaAccessToken and ocaTokenSet to SecretKeys in state-keys.ts
- Update OcaAuthProvider.clearAuth() to clear all 4 OCA secrets

Fixes #9567
2026-02-26 20:12:48 -03:00
ClineXDiego 4b2619daf7 fix: resolve "Could not find the file context" error in Explain Changes (#9449)
* fix: resolve 'Could not find the file context' error in Explain Changes

Both handleCommentReply() in explainChangesShared.ts and the onCommentStart
callback in explainChanges.ts were using a strict absolutePath-only match
when looking up files in changedFiles. If the VS Code comment controller
returns a path in a different format (relative vs absolute, different
separators on Windows), the lookup would silently fail and show
'Error: Could not find the file context'.

Add relativePath as a fallback in both lookup sites, making them
consistent with the already-correct logic in streamAIExplanationComments.

Fixes #9382

* Refactor to use parseInt instead of Number.parseInt
2026-02-26 20:12:37 -03:00
Ara 913cf4b74d feat: add dynamic Cline provider model fetching from Cline endpoint (#9102)
* Adding 1m

* fix: wire cline model proto fields for api config

* fix: wire cline picker to shared recommended model logic

* fix: address Cline model picker parity and startup model-info sync

* remove OpenRouter preset model ID support

* rename Cline endpoint feature flag

* Fixing stuff

* Fixing stuff

* Fixing stuff

* refactor: gate cline models endpoint behind feature flag

- Update refreshClineModels to use the EXTENSION_CLINE_MODELS_ENDPOINT feature flag instead of a hardcoded boolean, allowing controlled rollouts of the endpoint source.
- Remove recommended/free models fallback logic, featured model cards, and the initialTab property from OpenRouterModelPicker to simplify the UI component.
2026-02-26 14:58:45 -08:00
Robin Newhouse 8e5be3f648 fix: use JSON_SCHEMA for yaml.load to prevent unsafe deserialization (#9500)
* fix: use JSON_SCHEMA for yaml.load to prevent unsafe deserialization

Add { schema: yaml.JSON_SCHEMA } to both yaml.load() calls to reject
custom YAML tags (e.g. !!js/function) that could enable code execution
from untrusted .clinerules or skills files.

Add security tests verifying custom tags are rejected.

* add changeset
2026-02-26 14:48:58 -08:00
CandiedUniverse 6c519ff6e5 Increase timeout of flaky test; this is not the long term solution but it will be quick and easy today (#9568) 2026-02-26 12:45:18 -08:00
Robin Newhouse c61f9a9394 fix: fetch model info from API in CLI headless auth for Cline and Vercel providers (#9547)
The CLI's applyProviderConfig() was reading model info from a disk
cache (controller.readOpenRouterModels) instead of fetching from
the provider API. In headless/Docker environments (e.g., terminal-bench)
the cache doesn't exist, so model info was never set. Both handlers
then fell back to openRouterDefaultModelInfo with maxTokens: 8192,
causing write_to_file truncation on large outputs.

Changes:
- Replace controller.readOpenRouterModels() (disk cache) with
  refreshOpenRouterModels() (fetches from API, with cache fallback)
- Add vercel-ai-gateway to the model info fetch path using
  refreshVercelAiGatewayModels()

Relates to #7998

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-26 12:42:34 -08:00
Raushan Singh fa53f301a4 fix: generate commit message from staged changes only when staging exists (#9529)
The "Generate Commit Message" feature was using all changes instead of
only staged changes. Now prioritizes staged changes via getGitDiffStagedFirst(),
falling back to all changes only when nothing is staged.

Closes #5749

Co-authored-by: Raushan Singh <raushrak@Raushans-MacBook-Air.local>
2026-02-26 18:53:33 +01:00
Robin Newhouse c36e375af5 fix: update stale maxTokens values for Claude 3.7+ models across Anthropic, Bedrock, Vertex, and SAP AI Core (#9545)
* fix: update stale maxTokens values for Claude 3.7+ models

Every Claude model from 3.7 Sonnet onward had maxTokens set to 8192
in the static model definitions. These values were correct for Claude
3.5 and earlier, but Anthropic has significantly increased output
limits for newer models:

- Claude Opus 4.6: 128K (was 8192, 15.6x too low)
- Claude 3.7 Sonnet: 128K (was 8192, 15.6x too low)
- Claude Sonnet 4.6/4.5/4, Haiku 4.5, Opus 4.5: 64K (was 8192)
- Claude Opus 4, Opus 4.1: 32K (was 8192)

These static definitions are the source of truth for Anthropic direct,
Bedrock, Vertex, and SAP AI Core providers. With the old values, any
write_to_file call exceeding 8192 output tokens would be silently
truncated, producing a missing 'content' parameter error.

Values verified against Anthropic docs, AWS Bedrock docs, Google
Vertex AI docs, and the Vercel AI Gateway API.

Relates to #7998

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: update openRouterDefaultModelInfo.maxTokens to 64K

This fallback ModelInfo (representing claude-sonnet-4.5) is used
when dynamic model info isn't available — notably by the Cline and
Vercel providers in the CLI when the model cache is empty (e.g.,
fresh Docker containers in terminal-bench).

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-26 09:44:25 -08:00
Robin Newhouse 94b02bf052 fix: use model.info.maxTokens for OpenRouter instead of hardcoded 8192 (#9544)
The OpenRouter stream transform had a 30-line switch statement that
hardcoded max_tokens=8192 for every Claude model. This was written when
8192 was the actual max output for Claude, but modern Claude models
support much higher limits (e.g. 128K for Sonnet 4.6, 64K for others).

OpenRouter's API already reports the correct max_completion_tokens per
model, and model.info.maxTokens reflects this (128000 for Sonnet 4.6).
The hardcoded switch was silently overriding the dynamic value.

This caused write_to_file failures on OpenRouter (and the Cline
provider, which shares this code path) whenever the tool call content
exceeded 8192 output tokens. The response was truncated
(finish_reason: "length"), producing incomplete JSON that lost the
content parameter.

Runtime evidence:
- Before: max_tokens=8192 sent, completion_tokens=8192 (ceiling),
  finish_reason="length", write_to_file content missing
- After: max_tokens=128000 sent, completion_tokens=9824 (needed more
  than 8192), finish_reason="tool_calls", write_to_file succeeded

Fixes #7998

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-25 21:57:30 -08:00
shey-cline f10b6f39be Add Additional Markdown Formatting in CLI (#9392) 2026-02-24 14:43:53 -08:00
shey-cline 42e6a24d0f Add Focus Indicator on Action Buttons in Extension (#9487) 2026-02-24 14:43:16 -08:00
CandiedUniverse 452733c3fd Release changeset PR (#9528)
* Update package.json and package-lock.json version numbers for patch release

* Patch fix changeset PR

* update cli package.json

* fixup! Patch fix changeset PR

---------

Co-authored-by: Max Paulus 🥪 <max@cline.bot>
2026-02-24 14:10:39 -08:00
Juan Pablo Flores 7fadcfaa3f feat: add thinking to MiniMax M2.5 and add the M2.5-highspeed model to MiniMax provider (#9394)
* feat: add MiniMax M2.5 model to MiniMax provider

- Add MiniMax-M2.5 to minimaxModels with 192K context, 128K max tokens,
  prompt caching, and reasoning/thinking support
- Update minimaxDefaultModelId to MiniMax-M2.5
- Add minimax/minimax-m2.5 to OpenRouter prompt caching switch

Closes #9391

* fix: add temperature: 1 to MiniMax M2.5 for reasoning support

* docs: update MiniMax provider docs with M2.5 model

* feat: wire up thinking/reasoning support for MiniMax M2.5

- Pass thinkingBudgetTokens from factory to MinimaxHandler
- Use thinking param in API call when reasoning is enabled
- Disable temperature and forced tool_choice when thinking is on
- Add ThinkingBudgetSlider to MiniMaxProvider UI for M2.5

* Add MiniMax-M2.5-highspeed

* Add thinking for highspeed

* Refactor thinking logic

---------

Co-authored-by: BarreiroT <tomasmbarreiroi@gmail.com>
Co-authored-by: Tomás Barreiro <52393857+BarreiroT@users.noreply.github.com>
2026-02-24 22:24:41 +01:00
Tomás Barreiro 9989225b69 Parse CLINE_OTEL_EXPORTER_OTLP_HEADERS headers and force the build constant ones (#9534) 2026-02-24 22:07:55 +01:00
Ara 8a73f63189 feat: update recommended model from GPT-5.2 Codex to GPT-5.3 Codex (#9533)
- Update model ID and name from gpt-5.2-codex to gpt-5.3-codex
- Change tag from "HOT" to "NEW" for the updated model
- Add What's New banner entry promoting Codex 5.3 availability
2026-02-24 12:49:20 -08:00
Ara 31e8c85f0a Remove voice mode UI and disable dictation (#9511)
* remove voice mode UI and disable dictation flags

* remove legacy dictation settings path and dead voice recorder

* remove dictation feature stack and state/proto hooks
2026-02-24 12:26:15 -08:00
Bee 5b9916866d fix: remove placeholder tools from final native tool list (#9499)
This commit ensures that internal placeholder tools, specifically `focus_chain`, are filtered out from the final list of native tools exposed to the LLM.

- Added a test case in `PromptRegistry.test.ts` to verify `focus_chain` is excluded from native tools output.
- Updated snapshot files for various models (OpenAI GPT-5, Vertex Gemini 3, etc.) to reflect the removal of the `focus_chain` tool definition.
2026-02-24 12:07:55 -08:00
Tomás Barreiro f001e735f8 Add isLocatedInPath tests (#9526)
* Add isLocatedInPath tests

* Add another test case
2026-02-24 19:22:55 +01:00
Tomás Barreiro 32893ee343 Fix OpenAI Codex by setting Store to false (#9523) 2026-02-24 18:28:32 +01:00
Raushan Singh 0d4e47e5c3 fix: use isLocatedInPath() instead of string.includes() for path containment check (#9519)
Fixes false positives in getReadablePath() when directories share a prefix
(e.g., /home/user/project matching /home/user/project-backup). The existing
isLocatedInPath() function correctly handles path boundaries using path.relative().

Closes #8761

Co-authored-by: Raushan Singh <raushrak@Raushans-MacBook-Air.local>
2026-02-24 18:23:29 +01:00
Max c1a43482e7 sdk lib (#9259)
* sdk lib

* improve cline sdk api surface

- better api design and messages

* fix some types, fix session id retrieval, improve wording

* hide controller from sdk surface completely

---------

Co-authored-by: Max Paulus 🥪 <max@cline.bot>
2026-02-23 22:30:54 -08:00
Bee ea65383e16 chore: replace baseUrl with explicit relative paths in tsconfig files (#9508)
* chore: replace baseUrl with explicit relative paths in tsconfig files

Remove `baseUrl: "."` from tsconfig configurations and update all path aliases to use explicit relative paths (e.g., `./src/*` instead of `src/*`). This makes path resolution more explicit and avoids potential ambiguity in module resolution across the main project and webview-ui configurations.

* update package-lock.json
2026-02-23 18:00:09 -08:00
Ara b97d1487a7 Release V3.67.0 (#9509)
* Release V3.67.0

Bump version from 3.66.0 to 3.67.0 in package.json and
package-lock.json. Add changelog entry for v3.67.0 covering new
features (subagent skills, AgentConfigLoader, Responses API, websocket
preconnect, CLI /q command), bug fixes (reasoning delta crash, OpenAI
tool ID, auth checks, Gemini 3.1 Pro), and other changes. Update
WhatsNewItems fallback banners to reflect current promotions.

* Fixing stuff
2026-02-23 17:25:30 -08:00
Robin Newhouse 091cf945e4 Move PR skill to .agents/skills (#9505)
* Move PR skill to .agents/skills and add changeset guidance

* Remove changeset guidance from PR skill
2026-02-23 16:31:32 -08:00
Bee df4f551ba7 feat: add support for skills and optional modelId in subagent config (ENG-1564) (#9502)
* refactor: consolidate subagent request usage tracking into state object

Replace scattered per-request token tracking variables with a structured
`SubagentUsageState` interface containing `currentRequest` and
`lastRequest` states. This improves code organization by grouping related
token metrics (input, output, cache write/read, total tokens, cost) into
a cohesive `SubagentRequestUsageState` object, reducing variable sprawl
and making the usage lifecycle (current → last) more explicit.

* feat(subagent): add support for skills and optional modelId in agent config

- Update `AgentBaseConfigSchema` and `AgentConfigFrontmatterSchema` to include an optional `skills` field and make `modelId` optional.
- Implement `parseSkills` and `normalizeSkillName` in `AgentConfigLoader` to handle skill parsing from YAML frontmatter.
- Update `SubagentBuilder` to provide access to configured skills.
- Modify `SubagentRunner` to filter available skills based on the agent's configuration, falling back to all available skills if none are specified.
- Update host retrieval to use `HostRegistryInfo` instead of `HostProvider`.

This allows subagents to be restricted to specific skills and provides more flexibility in model configuration.

* update unit test
2026-02-23 16:23:36 -08:00
Ara 88da4ddf89 feat: fetch featured models from backend with local fallback (#9495)
* feat(cli): fetch featured models from backend with local fallback

- Add async getFeaturedModelsForCline() to fetch models via controller
- Load featured models dynamically in AuthView with useEffect
- Update FeaturedModelPicker to accept featuredModels as optional prop
- Refactor helper functions to accept models parameter for flexibility
- Keep local hardcoded models as fallback when backend fetch fails

* Fixing stuff

* Fixing stuff

* Fixing stuff
2026-02-23 16:13:51 -08:00
CandiedUniverse 93eb607e6d Remove all traces of changeset-converter.yml GitHub Action and npm run changeset (#9506) 2026-02-23 15:53:56 -08:00
Tony Loehr 810b5b78f5 added mcp enterprise configuration details (#9501)
Co-authored-by: Juan Pablo Flores <juan@cline.bot>
2026-02-23 15:51:05 -08:00
Tony Loehr 38ea422f6a Sso video (#9496)
* docs: add CVE scan sample to navigation, fix accordion labels, clarify --yolo flag

* added sso video

* Remove CVE scanner changes from SSO video PR
2026-02-23 15:29:48 -08:00
Robin Newhouse a7a35c0138 ci: add automatic retries for smoke test jobs (#9503) 2026-02-23 14:55:38 -08:00
299 changed files with 12609 additions and 6283 deletions
@@ -1,6 +1,6 @@
---
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.
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, PR template usage, and PR creation using the gh CLI tool.
---
# Create Pull Request
-8
View File
@@ -1,8 +0,0 @@
# Changesets
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
with multi-package repos, or single-package repos to help you version and publish your code. You can
find the full documentation for it [in our repository](https://github.com/changesets/changesets)
We have a quick list of common questions to get you started engaging with this project in
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
-5
View File
@@ -1,5 +0,0 @@
---
"cline": minor
---
Adds Messages API support to Oracle Code Assist, adding functionality for Claude models
-11
View File
@@ -1,11 +0,0 @@
{
"$schema": "https://unpkg.com/@changesets/config@3.0.5/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [],
"linked": [],
"access": "restricted",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": []
}
-5
View File
@@ -1,5 +0,0 @@
---
"cline": patch
---
Add /q command to quit CLI
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": patch
---
Fix a streaming crash when a chunk has usage data but no `delta` by guarding reasoning field checks in provider handlers. Add regression tests for OpenRouter, Cline, Vercel AI Gateway, and Fireworks handlers to cover usage-only chunks.
-4
View File
@@ -1,4 +0,0 @@
"claude-dev": patch
---
Add missing smoke evaluation npm scripts so documented commands like `npm run eval:smoke:run` work from the repository root.
-5
View File
@@ -1,5 +0,0 @@
---
"cline": patch
---
fix acp auth check so acp mode can be used with more providers
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": patch
---
Update SambaNova Provider models list and add temperature for models
-26
View File
@@ -1,26 +0,0 @@
changesDir: .changes
unreleasedDir: unreleased
headerPath: header.tpl.md
changelogPath: CHANGELOG.md
versionExt: md
versionFormat: '## {{.Version}} - {{.Time.Format "2006-01-02"}}'
kindFormat: "### {{.Kind}}"
changeFormat: "* {{.Body}}"
kinds:
- label: Added
auto: minor
- label: Changed
auto: major
- label: Deprecated
auto: minor
- label: Removed
auto: major
- label: Fixed
auto: patch
- label: Security
auto: patch
newlines:
afterChangelogHeader: 1
beforeChangelogVersion: 1
endOfVersion: 1
envPrefix: CHANGIE_
+1 -1
View File
@@ -14,7 +14,7 @@ This file is the secret sauce for working effectively in this codebase. It captu
## 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 creating PRs, contributors should not create changelog-entry files. Maintainers handle release versioning and changelog curation during the release process.
- 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
+1 -1
View File
@@ -19,7 +19,7 @@ Review and address all comments on the current branch's PR.
- Inline comments: `gh api repos/{owner}/{repo}/pulls/{pr_number}/comments`
- General comments: `gh pr view {pr_number} --json comments,reviews`
4. Present a summary of all comments with your recommendation for each (apply, skip, or respond). Ignore bot noise (changeset-bot, CI status, etc.).
4. Present a summary of all comments with your recommendation for each (apply, skip, or respond). Ignore bot noise (release automation, CI status, etc.).
5. **Wait for my approval** before proceeding.
-549
View File
@@ -1,549 +0,0 @@
The goal of this workflow is to take a changeset for a release of Cline, an autonomous coding agent extension that plugs right into your IDE, and write the updated announcement component, and the updated changelog.
For reference, here are some examples of how we converted previous changesets to announcement components / changelogs.
- 3.14
<changeset>
This PR was opened by the Changesets release GitHub action. When you're ready to do a release, you can merge this and publish to npm yourself or setup this action to publish automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated.
Releases
claude-dev@3.14.0
Minor Changes
77c9863: create clinerules folder if its currently a file and creating new rule
0ffb7dd: disabling shift hint for now & improving tooltip behavior
79b76fd: Add support for custom model ID in AWS Bedrock provider, enabling use of Application Inference Profile.
eb6e481: Full support for LaTeX rendering
df37f29: Add support for custom API request timeout. Previously, timeouts were hardcoded to 30 seconds for providers like Ollama or 15 seconds for OpenRouter and Cline. Now users can set a custom timeout value in milliseconds through the settings interface.
e4d26be: allow cursorrules and windsurfrules
c5de50f: Fix Handle @withRetry() SyntaxError when running extension locally issue
61d2f42: enabled pricing calculation for gemini and vertex + more robust caching & cache tracking for gemini & vertex
aed152b: add truncation notice when truncating manually
2fe2405: Migrate Cline Tools Section to new docs
19cc8bc: Add a timeout setting for the terminal connection, allowing users to adjust this if they are having timeout issues
03d4410: Added copy button to code blocks.
c78fe23: addressed race condition in terminal command usage
91e222f: add checkpoints after more messages
14230e7: add newrule slash command
1c7d33a: Add remote config with posthog allowing for disabling new features until they're reading, making for a better developer experience.
4196c14: add cache ui for open router and cline provider
d97424f: showing expanded task by default
5294e78: Refactor to not pass a message for showing the MCP View from the servers modal
70cc437: Fix Windows path issue: Correct handling of import.meta.url to avoid leading slash in pathname
4b697d8: Migrate the addRemoteServer to protobus
Patch Changes
c63d9a1: updated drag and drop text to say "drop" instead of "drag"
459adf0: Add markdown copy to chat
74ec823: Minor UX improvement to drag and drop ux
b0961f4: Remove linear pull request action
e9ce384: searchCommits protobus migration
5802b68: createRuleFile protobus migration
df7f9fc: Add dependsOn to more blocks in the tasks.json
41ae732: Fix for git commit mentions in repos with no git commits
7e78445: Adding args to allow Cursor to open workspaces (for checkpoint testing/development)
bdfda6f: feat(bedrock): Introduce Amazon Nova Premier
65243ad: Introduce UI library for future UI development
4565e06: checkIsImageURL migrated to protobus
5a8e9d8: protobus migration for openImage
deeda6e: Lowering Gemini cache TTL time
db0b022: Adding UI to show openrouter balance next to provider
4650ffa: deleteRuleFile protobus migration
d4bd755: fix cost calculation
</changeset>
<changelog>
## [3.14.0]
- Add UI to show openrouter balance next to provider
- Add support for custom model ID in AWS Bedrock provider, enabling use of Application Inference Profile (Thanks @clicube!)
- Add more robust caching & cache tracking for gemini & vertex providers
- Add support for LaTeX rendering
- Add support for custom API request timeout. Timeouts were 15-30s, but can now be configured via settings for OpenRouter/Cline & Ollama (Thanks @WingsDrafterwork!)
- Add truncation notice when truncating manually
- Add a timeout setting for the terminal connection, allowing users to set a time to wait for terminal startup
- Add copy button to code blocks
- Add copy button to markdown blocks (Thanks @weshoke!)
- Add checkpoints to more messages
- Add slash command to create a new rules file (/newrule)
- Add cache ui for open router and cline provider
- Add Amazon Nova Premier model to Bedrock (Thanks @watany!)
- Add support for cursorrules and windsurfrules
- Add support for batch history deletion (Thanks @danix800!)
- Improve Drag & Drop experience
- Create clinerules folder creating new rule if it's needed
- Enable pricing calculation for gemini and vertex providers
- Refactor message handling to not show the MCP View of the server modal
- Migrate the addRemoteServer to protobus (Thanks @DaveFres!)
- Update task header to be expanded by default
- Update Gemini cache TTL time to 15 minutes
- Fix race condition in terminal command usage
- Fix to correctly handle `import.meta.url`, avoiding leading slash in pathname for Windows (Thanks @DaveFres!)
- Fix @withRetry() decoration syntax error when running extension locally (Thanks @DaveFres!)
- Fix for git commit mentions in repos with no git commits
- Fix cost calculation (Thanks @BarreiroT!)
</changelog>
<announcement-component>
const Announcement = ({ version, hideAnnouncement }: AnnouncementProps) => {
const minorVersion = version.split(".").slice(0, 2).join(".") // 2.0.0 -> 2.0
return (
<div style={containerStyle}>
<VSCodeButton appearance="icon" onClick={hideAnnouncement} style={closeIconStyle}>
<span className="codicon codicon-close"></span>
</VSCodeButton>
<h3 style={h3TitleStyle}>
🎉{" "}New in v{minorVersion}
</h3>
<ul style={ulStyle}>
<li>
<b>Gemini prompt caching:</b> Gemini and Vertex providers now support prompt caching and price tracking for
Gemini models.
</li>
<li>
<b>Copy Buttons:</b> Buttons were added to Markdown and Code blocks that allow you to copy their contents
easily.
</li>
<li>
<b>/newrule command:</b> New slash command to have cline write your .clinerules for you based on your
workflow.
</li>
<li>
<b>Drag and drop improvements:</b> Don't forget to hold shift while dragging files!
</li>
<li>Added more checkpoints across the task, allowing you to restore from more than just file changes.</li>
<li>Added support for rendering LaTeX in message responses. (Try asking Cline to show the quadratic formula)</li>
</ul>
<Accordion isCompact className="pl-0">
<AccordionItem
key="1"
aria-label="Previous Updates"
title="Previous Updates:"
classNames={{
trigger: "bg-transparent border-0 pl-0 pb-0 w-fit",
title: "font-bold text-(--vscode-foreground)",
indicator:
"text-(--vscode-foreground) mb-0.5 -rotate-180 data-[open=true]:-rotate-90 rtl:rotate-0 rtl:data-[open=true]:-rotate-90",
}}>
<ul style={ulStyle}>
<li>
<b>Global Cline Rules:</b> store multiple rules files in Documents/Cline/Rules to share between
projects.
</li>
<li>
<b>Cline Rules Popup:</b> New button in the chat area to view workspace and global cline rules files
to plug and play specific rules for the task
</li>
<li>
<b>Slash Commands:</b> Type <code>/</code> in chat to see the list of quick actions, like starting a
new task (more coming soon!)
</li>
<li>
<b>Edit Messages:</b> You can now edit a message you sent previously by clicking on it. Optionally
restore your project when the message was sent!
</li>
</ul>
</AccordionItem>
</Accordion>
{/*
// Leave this here for an example of how to structure the announcement
<ul style={{ margin: "0 0 8px", paddingLeft: "12px" }}>
<li>
OpenRouter now supports prompt caching! They also have much higher rate limits than other providers,
so I recommend trying them out.
<br />
{!apiConfiguration?.openRouterApiKey && (
<VSCodeButtonLink
href={getOpenRouterAuthUrl(vscodeUriScheme)}
style={{
transform: "scale(0.85)",
transformOrigin: "left center",
margin: "4px -30px 2px 0",
}}>
Get OpenRouter API Key
</VSCodeButtonLink>
)}
{apiConfiguration?.openRouterApiKey && apiConfiguration?.apiProvider !== "openrouter" && (
<VSCodeButton
onClick={() => {
vscode.postMessage({
type: "apiConfiguration",
apiConfiguration: { ...apiConfiguration, apiProvider: "openrouter" },
})
}}
style={{
transform: "scale(0.85)",
transformOrigin: "left center",
margin: "4px -30px 2px 0",
}}>
Switch to OpenRouter
</VSCodeButton>
)}
</li>
<li>
<b>Edit Cline's changes before accepting!</b> When he creates or edits a file, you can modify his
changes directly in the right side of the diff view (+ hover over the 'Revert Block' arrow button in
the center to undo "<code>{"// rest of code here"}</code>" shenanigans)
</li>
<li>
New <code>search_files</code> tool that lets Cline perform regex searches in your project, letting
him refactor code, address TODOs and FIXMEs, remove dead code, and more!
</li>
<li>
When Cline runs commands, you can now type directly in the terminal (+ support for Python
environments)
</li>
</ul>*/}
<div style={hrStyle} />
<p style={linkContainerStyle}>
Join us on{" "}
<VSCodeLink style={linkStyle} href="https://x.com/cline">
X,
</VSCodeLink>{" "}
<VSCodeLink style={linkStyle} href="https://discord.gg/cline">
discord,
</VSCodeLink>{" "}
or{" "}
<VSCodeLink style={linkStyle} href="https://www.reddit.com/r/cline/">
r/cline
</VSCodeLink>
for more updates!
</p>
</div>
)
}
</announcement-component>
- 3.13
<changeset>
Minor Changes
2964388: Added copy button to MermaidBlock component
75143a7: Add the ability to fetch from global cline rules files
Patch Changes
a0252e7: convert inline style to tailwind css of file SettingsView.tsx
ab59bd9: Add stream options back to xai provider
7276f50: Icons to indicate an action is occuring outside of the users workspace
0b19ba6: update to NEW model
</changeset>
<changelog>
## [3.13.0]
- Add Cline rules popover under the chat field, allowing you to easily add, enable & disable workspace level or global rule files
- Add new slash command menu letting you type “/“ to do quick actions like creating new tasks
- Add ability to edit past messages, with options to restore your workspace back to that point
- Allow sending a message when selecting an option provided by the question or plan tool
- Add command to jump to Cline's chat input
- Add support for OpenAI o3 & 4o-mini (Thanks @PeterDaveHello and @arafatkatze!)
- Add baseURL option for Google Gemini provider (Thanks @owengo and @olivierhub!)
- Add support for Azure's DeepSeek model. (Thanks @yt3trees!)
- Add ability for models that support it to receive image responses from MCP servers (Thanks @rikaaa0928!)
- Improve search and replace diff editing by making it more flexible with models that fail to follow structured output instructions. (Thanks @chi-cat!)
- Add detection of Ctrl+C termination in terminal, improving output reading issues
- Fix issue where some commands with large output would cause UI to freeze
- Fix token usage tracking issues with vertex provider (Thanks @mzsima!)
- Fix issue with xAI reasoning content not being parsed (Thanks @mrubens!)
</changelog>
<announcement-component>
const Announcement = ({ version, hideAnnouncement }: AnnouncementProps) => {
const minorVersion = version.split(".").slice(0, 2).join(".") // 2.0.0 -> 2.0
return (
<div style={containerStyle}>
<VSCodeButton appearance="icon" onClick={hideAnnouncement} style={closeIconStyle}>
<span className="codicon codicon-close"></span>
</VSCodeButton>
<h3 style={h3TitleStyle}>
🎉{" "}New in v{minorVersion}
</h3>
<ul style={ulStyle}>
<li>
<b>Global Cline Rules:</b> store multiple rules files in Documents/Cline/Rules to share between projects.
</li>
<li>
<b>Cline Rules Popup:</b> New button in the chat area to view workspace and global cline rules files to plug
and play specific rules for the task
</li>
<li>
<b>Slash Commands:</b> Type <code>/</code> in chat to see the list of quick actions, like starting a new task
(more coming soon!)
</li>
<li>
<b>Edit Messages:</b> You can now edit a message you sent previously by clicking on it. Optionally restore
your project when the message was sent!
</li>
</ul>
<h4 style={{ margin: "5px 0 5px" }}>Previous Updates:</h4>
<ul style={ulStyle}>
<li>
<b>Model Favorites:</b> You can now mark your favorite models when using Cline & OpenRouter providers for
quick access!
</li>
<li>
<b>Faster Diff Editing:</b> Improved animation performance for large files, plus a new indicator in chat
showing the number of edits Cline makes.
</li>
<li>
<b>New Auto-Approve Options:</b> Turn off Cline's ability to read and edit files outside your workspace.
</li>
</ul>
{/*
// Leave this here for an example of how to structure the announcement
<ul style={{ margin: "0 0 8px", paddingLeft: "12px" }}>
<li>
OpenRouter now supports prompt caching! They also have much higher rate limits than other providers,
so I recommend trying them out.
<br />
{!apiConfiguration?.openRouterApiKey && (
<VSCodeButtonLink
href={getOpenRouterAuthUrl(vscodeUriScheme)}
style={{
transform: "scale(0.85)",
transformOrigin: "left center",
margin: "4px -30px 2px 0",
}}>
Get OpenRouter API Key
</VSCodeButtonLink>
)}
{apiConfiguration?.openRouterApiKey && apiConfiguration?.apiProvider !== "openrouter" && (
<VSCodeButton
onClick={() => {
vscode.postMessage({
type: "apiConfiguration",
apiConfiguration: { ...apiConfiguration, apiProvider: "openrouter" },
})
}}
style={{
transform: "scale(0.85)",
transformOrigin: "left center",
margin: "4px -30px 2px 0",
}}>
Switch to OpenRouter
</VSCodeButton>
)}
</li>
<li>
<b>Edit Cline's changes before accepting!</b> When he creates or edits a file, you can modify his
changes directly in the right side of the diff view (+ hover over the 'Revert Block' arrow button in
the center to undo "<code>{"// rest of code here"}</code>" shenanigans)
</li>
<li>
New <code>search_files</code> tool that lets Cline perform regex searches in your project, letting
him refactor code, address TODOs and FIXMEs, remove dead code, and more!
</li>
<li>
When Cline runs commands, you can now type directly in the terminal (+ support for Python
environments)
</li>
</ul>*/}
<div style={hrStyle} />
<p style={linkContainerStyle}>
Join us on{" "}
<VSCodeLink style={linkStyle} href="https://x.com/cline">
X,
</VSCodeLink>{" "}
<VSCodeLink style={linkStyle} href="https://discord.gg/cline">
discord,
</VSCodeLink>{" "}
or{" "}
<VSCodeLink style={linkStyle} href="https://www.reddit.com/r/cline/">
r/cline
</VSCodeLink>
for more updates!
</p>
</div>
)
}
</announcement-component>
We have a changeset PR that automatically generated as new unreleased PRs are merged into main, the PR is always called "Changeset version bump" and the author is github-actions.
The Changeset PR description looks something like this:
<changeset-pr-description>
This PR was opened by the [Changesets release](https://github.com/changesets/action) GitHub action. When you're ready to do a release, you can merge this and publish to npm yourself or [setup this action to publish automatically](https://github.com/changesets/action#with-publishing). If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated.
# Releases
## claude-dev@3.16.0
### Minor Changes
- c6e8b04: Recent task list is now collapsible, allowing users to hide their recent tasks (e.g. when sharing their screen).
- aabe4ae: Add detection for new users to display special components
- 6c18d51: adds global endpoint for vertex ai users
- 080ed7c: Add Tailwind CSS IntelliSense to the the recommended extensions list
- 5147e28: new workflow feature
### Patch Changes
- c0b3c69: fix eternal loading states when the last message is a checkpoint
- 570ece3: selectImages protos migration
- 8d8452e: askResponse protobus migration
- cd1ff2a: Finishing the migration of Vscode Advanced settings to Settings Webview
</changeset-pr-description>
The changeset pr is ALWAYS on the following branch: `changeset-release/main`.
I have the `gh` command line tool set up and authenticated, so you have everything you need.
The first step is to get the full diff from the changeset PR to look at the changes that were automatically made to the `CHANGELOG.md` file. By default it will automatically add a new section to the changelog.md file with the new version. The problem with the automatically generated section is that it just takes the text that the developers threw into their changeset files for each corresponding PR, and they can be pretty vague and bad. Additionally there's some stuff that is totally irrelevant for the end user, like minor refactoring changes. So I manually typically go in and update this section to be a proper changelog that will show up in our patchnotes. You can look at how the rest of the file is done because those are all good examples of us updating this to use good language for the end user. We usually put new features up top (and the most exciting flagship features at the very top), and then bug fixes/improvements at the bottom. Having some basic organization to the ordering of the bullet points by content is nice. But use common sense.
To handle this process effectively, do the following:
For each of the automatically generated bullet points in the Changelog.md, you should
1. Take the commit hash at the start of the bullet point, and use the `gh` command line tool find the PR that it was associated with.
2. Use the `gh` command to get the PR title/description/discussion to understand the context surrounding the PR.
3. Use the `gh` command line tool to get the full PR diff to fully understand the changes made in the code.
4. Synthesize that knowledge to determine (a) whether or not this change is relevant to end users and (b) what the text & ordering of the line should be.
5. Update the `CHANGELOG.md` accordingly
Do this for every single item in the list from the autogenerated bullet points. We want to be diligent and have a full understanding of every feature so we can make the best changelog ever!
Here are some principles for good changelogs from keepchangelog.com, a handy guide:
<keepachangelog-pinciples-for-good-changelogs>
### Guiding Principles
- Changelogs are for humans, not machines.
- There should be an entry for every single version.
- The same types of changes should be grouped.
- The latest version comes first.
### Bullet points in the changelog should follow these principles:
- Types of changes
- Added for new features.
- Changed for changes in existing functionality.
- Deprecated for soon-to-be removed features.
- Removed for now removed features.
- Fixed for any bug fixes.
- Security in case of vulnerabilities.
</keepachangelog-pinciples-for-good-changelogs>
Lastly, when developers make a PR, they typically make a changeset. And they have 3 options when making the changeset:
1. Patch
2. Minor
3. Major
Sometimes they label something as minor when really it should just be a patch. Or vice versa. Because of this, the automatic version bump may be incorrect. So when starting out this workflow, you should use the <ask_followup_question> tool to confirm with me whether or not this should be a patch bump (show the old version number and what the proposed new version number would be) or a minor bump. Part of the release process is making sure the version in package.json that is automatically changed actually corresponds with what we decided the bump should actually be based on the features. ALL these modifications happen in the `changeset-release/main` branch btw.
<important_note>
Before doing any of this, make sure you check out the `changeset-release/main` and pull the most recent up to date changes. Then perform all this work in that branch.
New announcement banners should ONLY be made for minor version bumps or higher. That's another reason why double checking if the changelog warrants the bump is important.
Also, SUPER important: For any external contributors that aren't part of the cline github organization, we always want to add a (Thanks @username!) at the end of the changelog to attribute them properly. We're an open source project and it's ethical to do this.
</important_note>
Once the changelog looks good, and the version number looks good, we gotta double check that the version number in the changelog has the brackets around it. And as a final step, double check the package.json version number matches the latest number in the changelog. And as the ultimate final step we run `npm run install:all` to make sure the package version number permiates through the lock file.
<detailed_sequence_of_steps>
# Cline Release Process - Detailed Sequence of Steps
## Before Starting
1. First, examine the changeset PR without checking it out:
```bash
gh pr view changeset-release/main
```
2. View the PR diff to see the auto-generated CHANGELOG.md changes:
```bash
gh pr diff changeset-release/main > changeset-diff.txt
cat changeset-diff.txt | grep -A 50 "CHANGELOG.md"
```
## Initial Setup
3. Once you're ready to start, checkout and update the changeset release branch:
```bash
git checkout changeset-release/main
git pull origin changeset-release/main
```
## Analyzing Each Change
4. For each commit hash in the auto-generated changelog entries:
a. Find the PR number associated with a commit hash:
```bash
gh pr list --search "<commit-hash>" --state merged
```
b. Get PR details for better context:
```bash
gh pr view <PR-number>
```
c. Check if the contributor is external to determine if attribution is needed:
```bash
# Extract username from PR
USERNAME=$(gh pr view <PR-number> --json author --jq .author.login)
# Check if user is a member of the Cline organization
# this command is a bit finnicky, but it 100% works.
# if you see a `Error executing command: The command ran successfully, but we couldn't capture its output. Please proceed accordingly.` error, just retry it until you actually get the output
# don't make any assumptions, just retry the command to actually get the output and determine if they're external or not.
# no output means they are an external contributor, otherwise if there is output they are an internal contributor (part of our github org)
gh api "orgs/cline/members" --jq "map(.login)" | grep -i "pashpashpash"
```
d. View the full PR diff to understand code changes:
```bash
gh pr diff <PR-number> > pr-diff-<PR-number>.txt
cat pr-diff-<PR-number>.txt
```
## Updating the Changelog
5. Based on PR analysis, update the CHANGELOG.md with user-friendly descriptions:
- Use the `<replace_in_file>` tool to edit the CHANGELOG.md file
- Group by feature type (Added, Changed, Fixed)
- Put most exciting features at the top
- Move bug fixes and small improvements to the bottom
- Use clear, end-user focused language
- For external contributors, add attribution at the end of the relevant entry: `(Thanks @username!)`
## Version Number Verification
6. Confirm the version bump is appropriate:
- Check package.json to verify the auto-generated version number:
```bash
cat package.json | grep "\"version\""
```
- If the feature set doesn't warrant a minor bump, use the `<replace_in_file>` tool to modify package.json
7. Ensure the version in CHANGELOG.md has brackets around it:
```
## [3.16.0]
```
## Creating the Announcement (for minor/major versions only)
8. If this is a minor version bump, create/update the announcement component:
- Use the `<replace_in_file>` tool to edit the src/views/components/announcement.tsx file
- Update the highlights based on key features
- Move previous version highlights to the "Previous Updates" section
- Use the previous announcement components as reference for structure
## Finalizing the Release
9. Update dependencies with the new version number:
```bash
npm run install:all
```
10. Commit your changes:
```bash
git add CHANGELOG.md package.json package-lock.json src/views/components/announcement.tsx
git commit -m "Update CHANGELOG.md and announcement for version 3.16.0"
```
11. Push your changes to the changeset branch:
```bash
git push origin changeset-release/main
```
12. Check that your changes pushed successfully:
```bash
git status
```
</detailed_sequence_of_steps>
+3 -10
View File
@@ -89,16 +89,9 @@ On the main branch, create a commit that updates:
2. **package.json** - Update the version field to the new version
3. **Delete changesets** for the commits being included in the hotfix. This prevents the changeset bot from including duplicate entries in the next regular release.
3. No changelog-entry file cleanup is needed. Contributors do not create changelog-entry files in this repo.
Find and delete the changeset files associated with the selected commits:
```bash
ls .changeset/
```
Each changeset file in `.changeset/` corresponds to a PR. Read them to identify which ones belong to the commits you're hotfixing, then delete those files.
**Skip running `npm run install:all`** - the automation handles outdated lockfiles.
**Skip running `npm run install:all`** - release automation handles lockfile consistency as needed.
Commit with message format: `v{VERSION} Release Notes (hotfix)`
@@ -107,7 +100,7 @@ In the commit body, mention:
- List the cherry-picked commits that will be included
```bash
git add CHANGELOG.md package.json .changeset/
git add CHANGELOG.md package.json
git commit -m "v3.40.1 Release Notes (hotfix)
Hotfix release including:
-2
View File
@@ -347,8 +347,6 @@ A few notes:
So until the settings page is update, and this is added to settings in a way that's clean and doesn't confuse new users, I don't think we can merge this. Please bear with us.
</request_changes_comment>
<request_changes_comment>
Also, don't forget to add a changeset since this fixes a user-facing bug.
The architectural change is solid - moving the focus logic to the command handlers makes sense. Just don't want to introduce subtle timing issues by removing those timeouts.
</request_changes_comment>
</example_comments_that_i_have_written_before>
+40 -208
View File
@@ -1,232 +1,64 @@
# Release
Prepare and publish a release from the open changeset PR.
Prepare and publish a release directly from `main`.
## Overview
This workflow helps you:
1. Find and checkout the open changeset PR
2. Clean up the changelog (fix version format, wordsmith entries)
3. Push changes back to the PR branch
4. Merge with proper commit message format
5. Tag and push the release (after verifying the commit)
6. Trigger the publish workflow
7. Update GitHub release notes
8. Provide final summary with Slack announcement
1. Select/confirm the target version
2. Curate `CHANGELOG.md` entries manually for end users
3. Ensure `package.json` version matches the changelog
4. Create and push a release commit + tag
5. Trigger publish workflow
6. Update GitHub release notes and share a summary
## Step 1: Find the Changeset PR
## Process
Look for the open changeset PR:
```bash
gh pr list --search "Changeset version bump" --state open --json number,title,headRefName,url
```
If no PR is found, inform the user there's no changeset PR ready. They may need to:
- Merge PRs with changesets to main first
- Manually trigger the Changeset Converter workflow at: https://github.com/cline/cline/actions/workflows/changeset-converter.yml
## Step 2: Gather PR Information
Get the PR details:
```bash
PR_NUMBER=<number from step 1>
gh pr view $PR_NUMBER --json body,files,headRefName
```
Checkout the PR branch:
```bash
git fetch origin changeset-release/main
git checkout changeset-release/main
```
If the branch has diverged from remote, reset to the remote version:
```bash
git reset --hard origin/changeset-release/main
```
## Step 3: Analyze the Changes
Read the current CHANGELOG.md to see what the automation generated:
```bash
head -50 CHANGELOG.md
```
Get the version from package.json:
```bash
cat package.json | grep '"version"'
```
**Present to the user:**
- The version number that will be released
- The raw changelog entries from the changeset PR
- Whether this is a patch, minor, or major release
## Step 4: Clean Up the Changelog
The changelog needs these fixes:
1. **Add brackets to version number**: Change `## 3.44.1` to `## [3.44.1]`
2. **No category headers**: Don't use `### Added`, `### Fixed`, etc. Just a flat list of bullet points.
3. **Order entries from most important to least important**:
- Lead with major new features or significant fixes users care about
- End with minor fixes or internal changes
4. **Write user-friendly descriptions**:
- This is for end users, not developers—explain what changed in plain language
- Remove commit hashes from the beginning of lines (the automation adds these)
- Look at the actual commit diffs (`git show <hash>`) and PRs to understand what changed
- Write colorful descriptions that explain the value and impact, not just technical details
- Consolidate related changes into single entries when appropriate
**Ask the user** to review the proposed changelog changes before applying them. Show them:
- Current (raw) changelog section
- Proposed (cleaned) changelog section
Once approved, apply the changes to CHANGELOG.md.
## Step 5: Commit and Push Changes
After making changelog edits:
```bash
git add CHANGELOG.md
git commit -m "Clean up changelog formatting"
git push origin changeset-release/main
```
## Step 6: Merge the PR
**Ask the user to confirm** they're ready to merge.
Merge the PR with the proper commit message format:
```bash
VERSION=<version from package.json>
gh pr merge $PR_NUMBER --squash --subject "v${VERSION} Release Notes" --body ""
```
**If merge is blocked by branch protection:**
- Users with admin privileges can add the `--admin` flag to bypass
- Users without admin privileges need to get the PR approved through normal review first before merging
## Step 7: Tag the Release
After the merge completes, checkout main and pull:
### 1) Sync and determine version
```bash
git checkout main
git pull origin main
cat package.json | grep '"version"'
```
**IMPORTANT: Verify the latest commit is the release commit before tagging:**
Confirm the release version with the maintainer (patch/minor/major).
### 2) Curate changelog and version
- Edit `CHANGELOG.md` for the target version using human-friendly release notes.
- Ensure version headers use bracket format, e.g. `## [3.66.1]`.
- Update `package.json` version to the same value.
### 3) Commit and tag
```bash
git log -1 --oneline
git add CHANGELOG.md package.json package-lock.json
git commit -m "v<version> Release Notes"
git push origin main
git tag v<version>
git push origin v<version>
```
Confirm the commit message matches `v{VERSION} Release Notes` (e.g., `v3.44.1 Release Notes`). Do NOT blindly tag HEAD without verification.
### 4) Trigger publish workflow
Once verified, tag and push:
Tell the maintainer to run:
https://github.com/cline/cline/actions/workflows/publish.yml
Use `v<version>` as the release tag.
### 5) Update GitHub release notes
After publish completes:
```bash
VERSION=<version>
git tag v${VERSION}
git push origin v${VERSION}
gh release view v<version> --json body --jq '.body'
gh release edit v<version> --notes "<final curated release notes>"
```
## Step 8: Trigger Publish Workflow
### 6) Final summary
**Copy the tag to clipboard** so the user can easily paste it into the GitHub Actions workflow:
```bash
echo -n "v{VERSION}" | pbcopy
```
**Tell the user to trigger the publish workflow:**
1. Go to: https://github.com/cline/cline/actions/workflows/publish.yml
2. Select **"release"** for release-type
3. Paste **`v{VERSION}`** as the tag (already in clipboard)
**Wait for the user** to confirm the publish workflow has completed before proceeding.
## Step 9: Update GitHub Release Notes
Once the user confirms the publish workflow is done, fetch the auto-generated release content:
```bash
VERSION=<version>
gh release view v${VERSION} --json body --jq '.body'
```
The auto-generated release has:
- `## What's Changed` - PR list (we'll replace this with our changelog)
- `## New Contributors` - First-time contributors (keep this if present)
- `**Full Changelog**` - Comparison link (keep this)
Build the new release body:
1. Start with `## What's Changed` header
2. Add our changelog content (from CHANGELOG.md for this version)
3. Keep the `## New Contributors` section if it exists
4. Keep the `**Full Changelog**` link
Update the release:
```bash
gh release edit v${VERSION} --notes "<new body content>"
```
Verify the release was updated:
```bash
gh release view v${VERSION}
```
## Step 10: Final Summary
**Copy a Slack announcement message to clipboard** (include the full changelog, not just highlights):
```bash
echo "VS Code v{VERSION} Released
- Changelog entry 1
- Changelog entry 2
- Changelog entry 3" | pbcopy
```
**Present a final summary:**
- Version released: v{VERSION}
- PR merged: #{PR_NUMBER}
- Tag pushed: v{VERSION}
- Release: https://github.com/cline/cline/releases/tag/v{VERSION}
- Slack message copied to clipboard
**Final reminder:**
Post the Slack message to announce the release
## Handling Edge Cases
### No changesets found
If the changeset PR body shows no changes, inform the user they need to merge PRs with changesets first.
### Merge conflicts
If there are conflicts on the changeset branch, help the user resolve them:
```bash
git fetch origin main
git rebase origin/main
# resolve conflicts
git push origin changeset-release/main --force-with-lease
```
### User wants to add more changes
If the user wants to include additional PRs before releasing:
1. Ask them to merge those PRs to main first
2. The changeset automation will update the PR automatically
3. Re-run this workflow after the PR is updated
Provide:
- Released version/tag
- Link to release page
- Summary of top end-user changes
+58
View File
@@ -0,0 +1,58 @@
# Copilot Instructions for Cline
This is a VS Code extension. Read `.clinerules/general.md` for tribal knowledge and nuanced patterns.
## Architecture
- **Core** (`src/`): `extension.ts``WebviewProvider``Controller` (single source of truth) → `Task` (agent loop).
- **Webview** (`webview-ui/`): React/Vite app. State via `ExtensionStateContext.tsx`, synced through message passing.
- **CLI** (`cli/`): React Ink terminal UI sharing core logic. Update CLI when changing webview features.
- **Communication**: Protobuf-defined gRPC-like protocol over VS Code message passing. Schemas in `proto/`.
- **MCP**: `src/services/mcp/McpHub.ts`.
## Build & Test (Critical — non-obvious commands)
- **Build**: `npm run compile` — NOT `npm run build`.
- **Watch**: `npm run watch` (extension + webview).
- **Protos**: `npm run protos` — run **immediately** after any `.proto` change. Generates into `src/shared/proto/`, `src/generated/`.
- **Tests**: `npm run test:unit`. After prompt/tool changes: `UPDATE_SNAPSHOTS=true npm run test:unit`.
## Protobuf RPC Workflow (4 steps)
1. **Define** in `proto/cline/*.proto`. Naming: `PascalCaseService`, `camelCase` RPCs, `PascalCase` Messages. Use `common.proto` shared types for simple data.
2. **Generate**: `npm run protos`.
3. **Backend handler**: `src/core/controller/<domain>/`.
4. **Frontend call**: `UiServiceClient.myMethod(Request.create({...}))`.
- Adding enums (e.g. `ClineSay`) → also update `src/shared/proto-conversions/cline-message.ts`.
## Adding API Providers (silent failure risk)
Three proto conversion updates are **required** or the provider silently resets to Anthropic:
1. `proto/cline/models.proto` — add to `ApiProvider` enum.
2. `convertApiProviderToProto()` in `src/shared/proto-conversions/models/api-configuration-conversion.ts`.
3. `convertProtoToApiProvider()` in the same file.
Also update: `src/shared/api.ts`, `src/shared/providers/providers.json`, `src/core/api/index.ts`, `webview-ui/.../providerUtils.ts`, `webview-ui/.../validate.ts`, `webview-ui/.../ApiOptions.tsx`, and `cli/src/components/ModelPicker.tsx`.
For Responses API providers: add to `isNextGenModelProvider()` in `src/utils/model-utils.ts` and set `apiFormat: ApiFormat.OPENAI_RESPONSES` on models.
## Adding Tools to System Prompt (5+ file chain)
1. Add enum to `ClineDefaultTool` in `src/shared/tools.ts`.
2. Create definition in `src/core/prompts/system-prompt/tools/` (export `[GENERIC]` minimum).
3. Register in `src/core/prompts/system-prompt/tools/init.ts`.
4. Whitelist in `src/core/prompts/system-prompt/variants/*/config.ts` for each model family.
5. Handler in `src/core/task/tools/handlers/`, wire in `ToolExecutor.ts`.
6. If tool has UI: add `ClineSay` enum in proto → `ExtensionMessage.ts``cline-message.ts``ChatRow.tsx`.
7. Regenerate snapshots: `UPDATE_SNAPSHOTS=true npm run test:unit`.
## Modifying System Prompt
Modular: `components/` (shared) + `variants/` (model-specific) + `templates/` (`{{PLACEHOLDER}}`). Variants override components via `componentOverrides` in `config.ts` or custom `template.ts`. XS variant is heavily condensed inline. Always regenerate snapshots after changes.
## Global State Keys (silent failure risk)
Adding a key requires: type in `src/shared/storage/state-keys.ts`, read via `context.globalState.get()` in `src/core/storage/utils/state-helpers.ts` `readGlobalStateFromDisk()`, and add to return object. Missing the `.get()` call compiles fine but value is always `undefined`.
## Slash Commands (3 places)
- `src/core/slash-commands/index.ts` — definitions.
- `src/core/prompts/commands.ts` — system prompt integration.
- `webview-ui/src/utils/slash-commands.ts` — webview autocomplete.
## Conventions
- **Paths**: Always use `src/utils/path` helpers (`toPosixString`) for cross-platform compatibility.
- **Logging**: `src/shared/services/Logger.ts`.
- **Feature flags**: See PR #7566 as reference pattern.
-1
View File
@@ -60,7 +60,6 @@ We're not looking for exhaustive documentation - just evidence that you've thoug
- [ ] Changes are limited to a single feature, bugfix or chore (split larger changes into separate PRs)
- [ ] Tests are passing (`npm test`) and code is formatted and linted (`npm run format && npm run lint`)
- [ ] I have created a changeset using `npm run changeset` (required for user-facing changes)
- [ ] I have reviewed [contributor guidelines](https://github.com/cline/cline/blob/main/CONTRIBUTING.md)
### Screenshots
@@ -1,79 +0,0 @@
"""
This script updates a specific version's release notes section in CHANGELOG.md with new content
or reformats existing content.
The script:
1. Takes a version number, changelog path, and optionally new content as input from environment variables
2. Finds the section in the changelog for the specified version
3. Either:
a) Replaces the content with new content if provided, or
b) Reformats existing content by:
- Removing the first two lines of the changeset format
- Ensuring version numbers are wrapped in square brackets
4. Writes the updated changelog back to the file
Environment Variables:
CHANGELOG_PATH: Path to the changelog file (defaults to 'CHANGELOG.md')
VERSION: The version number to update/format
PREV_VERSION: The previous version number (used to locate section boundaries)
NEW_CONTENT: Optional new content to insert for this version
"""
#!/usr/bin/env python3
import os
CHANGELOG_PATH = os.environ.get("CHANGELOG_PATH", "CHANGELOG.md")
VERSION = os.environ['VERSION']
PREV_VERSION = os.environ.get("PREV_VERSION", "")
NEW_CONTENT = os.environ.get("NEW_CONTENT", "")
def overwrite_changelog_section(changelog_text: str, new_content: str):
# Find the section for the specified version
version_pattern = f"## {VERSION}\n"
unformmatted_prev_version_pattern = f"## {PREV_VERSION}\n"
prev_version_pattern = f"## [{PREV_VERSION}]\n"
print(f"latest version: {VERSION}")
print(f"prev_version: {PREV_VERSION}")
notes_start_index = changelog_text.find(version_pattern) + len(version_pattern)
notes_end_index = changelog_text.find(prev_version_pattern, notes_start_index) if PREV_VERSION and (prev_version_pattern in changelog_text or unformmatted_prev_version_pattern in changelog_text) else len(changelog_text)
if new_content:
return changelog_text[:notes_start_index] + f"{new_content}\n" + changelog_text[notes_end_index:]
else:
changeset_lines = changelog_text[notes_start_index:notes_end_index].split("\n")
filtered_lines = []
for line in changeset_lines:
# If the previous line is a changeset format
if len(filtered_lines) > 1 and filtered_lines[-1].startswith("### "):
# Remove the last two lines from the filted_lines
filtered_lines.pop()
filtered_lines.pop()
else:
filtered_lines.append(line.strip())
# Prepend a new line to the first line of filtered_lines
if filtered_lines:
filtered_lines[0] = "\n" + filtered_lines[0]
# Print filted_lines wiht a "\n" at the end of each line
for line in filtered_lines:
print(line.strip())
parsed_lines = "\n".join(line for line in filtered_lines)
updated_changelog = changelog_text[:notes_start_index] + parsed_lines + changelog_text[notes_end_index:]
return updated_changelog
with open(CHANGELOG_PATH, 'r') as f:
changelog_content = f.read()
new_changelog = overwrite_changelog_section(changelog_content, NEW_CONTENT)
# print("----------------------------------------------------------------------------------")
# print(new_changelog)
# print("----------------------------------------------------------------------------------")
# Write back to CHANGELOG.md
with open(CHANGELOG_PATH, 'w') as f:
f.write(new_changelog)
print(f"{CHANGELOG_PATH} updated successfully!")
-113
View File
@@ -1,113 +0,0 @@
name: Changeset Converter
run-name: Changeset Conversion
on:
workflow_dispatch:
pull_request:
types: [closed]
env:
REPO_PATH: ${{ github.repository }}
GIT_REF: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || 'main' }}
NODE_VERSION: 20.18.1
jobs:
# Job 1: Create version bump PR when changesets are merged to main
changeset-pr-version-bump:
if: |
github.event_name == 'workflow_dispatch' ||
(
github.event_name == 'pull_request' &&
github.event.pull_request.merged == true &&
github.event.pull_request.base.ref == 'main' &&
github.actor != 'github-actions'
)
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Check user for team affiliation
id: team_check
if: github.event_name == 'workflow_dispatch'
uses: morfien101/actions-authorized-user@4a3cfbf0bcb3cafe4a71710a278920c5d94bb38b
with:
username: ${{ github.actor }}
org: ${{ github.repository_owner }}
team: "deployer"
github_token: ${{ secrets.GITHUB_TOKEN }}
- name: Check if user is authorized
if: github.event_name == 'workflow_dispatch'
run: |
if [ "${{ steps.team_check.outputs.authorized }}" != "true" ]; then
echo "User is not authorized to run this workflow."
exit 1
fi
- name: Git Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ env.GIT_REF }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: "npm"
- name: Install Dependencies
run: npm ci
# Check if there are any new changesets to process
- name: Check for changesets
id: check-changesets
run: |
NEW_CHANGESETS=$(find .changeset -name "*.md" ! -name "README.md" | wc -l | tr -d ' ')
echo "Changesets diff with previous version: $NEW_CHANGESETS"
echo "new_changesets=$NEW_CHANGESETS" >> $GITHUB_OUTPUT
# Create version bump PR using changesets/action if there are new changesets
- name: Create Changeset Pull Request
if: steps.check-changesets.outputs.new_changesets != '0'
uses: changesets/action@v1
with:
commit: "changeset version bump"
title: "Changeset version bump"
version: npm run version-packages # This performs the changeset version bump
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Get current and previous versions to edit changelog entry
- name: Get version
id: get_version
run: |
VERSION=$(git show HEAD:package.json | jq -r '.version')
echo "version=$VERSION" >> $GITHUB_OUTPUT
PREV_VERSION=$(git show origin/main:package.json | jq -r '.version')
echo "prev_version=$PREV_VERSION" >> $GITHUB_OUTPUT
echo "version=$VERSION"
echo "prev_version=$PREV_VERSION"
# Update CHANGELOG.md with proper format
- name: Update Changelog Format
env:
VERSION: ${{ steps.get_version.outputs.version }}
PREV_VERSION: ${{ steps.get_version.outputs.prev_version }}
run: python .github/scripts/overwrite_changeset_changelog.py
# Commit and push changelog updates
- name: Push Changelog updates to Pull Request
run: |
git config user.name "github-actions"
git config user.email github-actions@github.com
echo "Running git add and commit..."
git add CHANGELOG.md
git commit -m "Updating CHANGELOG.md format"
git status
echo "--------------------------------------------------------------------------------"
echo "Pushing to remote..."
echo "--------------------------------------------------------------------------------"
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
git push origin $CURRENT_BRANCH
+16 -1
View File
@@ -55,7 +55,22 @@ jobs:
CLINE_API_KEY: ${{ secrets.CLINE_API_KEY }}
run: |
cline auth -p cline -k "$CLINE_API_KEY" -m "anthropic/claude-sonnet-4.5"
npx tsx evals/smoke-tests/run-smoke-tests.ts --trials 1 --parallel
max_attempts=3
for attempt in $(seq 1 $max_attempts); do
echo "::group::Attempt $attempt of $max_attempts"
if npx tsx evals/smoke-tests/run-smoke-tests.ts --trials 1 --parallel; then
echo "::endgroup::"
echo "Smoke tests passed on attempt $attempt"
exit 0
fi
echo "::endgroup::"
if [ $attempt -lt $max_attempts ]; then
echo "::warning::Smoke tests failed on attempt $attempt, retrying..."
sleep 10
fi
done
echo "::error::Smoke tests failed after $max_attempts attempts"
exit 1
- name: Generate summary
if: always()
+75 -16
View File
@@ -11,8 +11,13 @@ on:
options:
- pre-release
- release
auto_create_tag_from_main:
description: "Auto-create and push the provided tag from the tested main commit (recommended)"
required: true
default: true
type: boolean
tag:
description: "Enter existing tag to publish (e.g., v3.1.2)"
description: "Tag to publish (required in both modes, e.g., v3.1.2)"
required: true
type: string
@@ -35,10 +40,69 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.tag }}
ref: main
fetch-depth: 0
fetch-tags: true
- name: Resolve Release Tag
id: resolve_tag
run: |
TAG="${{ github.event.inputs.tag }}"
AUTO_CREATE="${{ github.event.inputs.auto_create_tag_from_main }}"
TESTED_SHA="${{ github.sha }}"
WORKFLOW_REF="${{ github.ref }}"
if [[ -z "$TAG" ]]; then
echo "Error: tag input is required"
exit 1
fi
if [[ ! "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+([-.][0-9A-Za-z.]+)?$ ]]; then
echo "Error: tag must match vX.Y.Z (optionally with -suffix or .suffix)"
exit 1
fi
TAG_REF="refs/tags/$TAG"
git fetch origin main --tags
if [[ "$AUTO_CREATE" == "true" ]]; then
if [[ "$WORKFLOW_REF" != "refs/heads/main" ]]; then
echo "Error: auto-create mode requires dispatching from main (current ref: $WORKFLOW_REF)"
exit 1
fi
echo "Auto-create enabled. Using tested workflow SHA: $TESTED_SHA"
if ! git merge-base --is-ancestor "$TESTED_SHA" origin/main; then
echo "Error: tested SHA $TESTED_SHA is not on origin/main"
exit 1
fi
if git show-ref --verify --quiet "$TAG_REF"; then
TAG_SHA=$(git rev-list -n 1 "$TAG_REF^{commit}")
if [[ "$TAG_SHA" != "$TESTED_SHA" ]]; then
echo "Error: tag '$TAG' already exists at $TAG_SHA, not at tested SHA ($TESTED_SHA)"
exit 1
fi
echo "Tag '$TAG' already exists at tested SHA. Continuing."
else
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git tag "$TAG" "$TESTED_SHA"
git push origin "$TAG_REF"
echo "Created and pushed tag '$TAG' from tested SHA $TESTED_SHA."
fi
else
if ! git show-ref --verify --quiet "$TAG_REF"; then
echo "Error: tag '$TAG' does not exist in the repository"
exit 1
fi
echo "Using existing tag '$TAG'."
fi
git checkout --detach "$TAG_REF^{commit}"
echo "tag=$TAG" >> $GITHUB_OUTPUT
echo "resolved_sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
- name: Setup Node.js
uses: actions/setup-node@v4
with:
@@ -59,20 +123,15 @@ jobs:
VERSION=$(node -p "require('./package.json').version")
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: Validate Tag
id: validate_tag
- name: Verify Tag Matches Package Version
run: |
TAG="${{ github.event.inputs.tag }}"
echo "tag=$TAG" >> $GITHUB_OUTPUT
echo "Using existing tag: $TAG"
# Verify the tag exists
if ! git rev-parse "$TAG" >/dev/null 2>&1; then
echo "Error: Tag '$TAG' does not exist in the repository"
TAG="${{ steps.resolve_tag.outputs.tag }}"
VERSION="v${{ steps.get_version.outputs.version }}"
if [[ "$TAG" != "$VERSION" ]]; then
echo "Error: tag '$TAG' does not match package version '$VERSION'"
exit 1
fi
echo "Tag '$TAG' validated successfully"
echo "Tag and package version match: $TAG"
- name: Package and Publish Extension
env:
@@ -103,7 +162,7 @@ jobs:
- name: Get Previous Tag
id: prev_tag
run: |
CURRENT_TAG="${{ steps.validate_tag.outputs.tag }}"
CURRENT_TAG="${{ steps.resolve_tag.outputs.tag }}"
PREV_TAG=$(git describe --tags --abbrev=0 "$CURRENT_TAG^" 2>/dev/null || echo "")
echo "prev_tag=$PREV_TAG" >> $GITHUB_OUTPUT
@@ -119,12 +178,12 @@ jobs:
- name: Create GitHub Release
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ steps.validate_tag.outputs.tag }}
tag_name: ${{ steps.resolve_tag.outputs.tag }}
files: "*.vsix"
body: |
${{ steps.changelog.outputs.content }}
**Full Changelog**: https://github.com/${{ github.repository }}/compare/${{ steps.prev_tag.outputs.prev_tag }}...${{ steps.validate_tag.outputs.tag }}
**Full Changelog**: https://github.com/${{ github.repository }}/compare/${{ steps.prev_tag.outputs.prev_tag }}...${{ steps.resolve_tag.outputs.tag }}
prerelease: ${{ github.event.inputs.release-type == 'pre-release' }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -20,7 +20,8 @@ jobs:
github.event_name == 'pull_request_target' ||
(github.event_name == 'issue_comment' &&
github.event.issue.pull_request &&
contains(github.event.comment.body, '/test-jetbrains'))
contains(github.event.comment.body, '/test-jetbrains') &&
contains(fromJSON('["MEMBER","OWNER","COLLABORATOR"]'), github.event.comment.author_association))
steps:
- name: Generate GitHub App Token
id: app-token
-2
View File
@@ -35,11 +35,9 @@ cli/**
eslint-rules/
old_docs/
evals/
.changie.yaml
.codespellrc
.mocharc.json
buf.yaml
.changeset/
.clinerules/
# Ignore all webview-ui files except the build directory (https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/frameworks/hello-world-react-cra/.vscodeignore)
+138
View File
@@ -1,5 +1,143 @@
# Changelog
## [3.72.0]
### Added
- Added Anthropic Opus 4.6 fast mode variants
### Fixed
- Resolved native tool placeholder interpolation in prompts
- Gemini: capped Flash output tokens to 8192 across providers
- Fixed Windows unit test path normalization
- Fixed flaky hooks tests on Windows
- Bedrock: handle thinking and redacted_thinking blocks correctly in message conversion and streaming
- Prevent crash when `list_files` or `list_code_definition_names` receives a file path
### Changed
- Updated Jupyter Notebook GIFs
- Markdown image loading now requires user consent
- Added `.github/copilot-instructions.md` for coding agents
- Hooks: reintroduced feature toggle
## [3.71.0]
### Added
- Added GPT-5.4 models for ChatGPT subscription users
- Hooks: Added a `Notification` hook for attention and completion boundaries
### Fixed
- Handle streamable HTTP MCP reconnects more reliably after disconnects
## [3.70.0]
### Added
- New Cline API docs: Getting Started, Auth, Chat Completions, Models, Errors, and SDK Examples
- Hook payloads now include `model.provider` and `model.slug`
- Token/cost updates now happen immediately as usage chunks arrive, not after tool execution
### Fixed
- Improve subagent context compaction logic
- Subagent stream retry delay increased to reduce noise from transient failures
- State serialization errors are now caught and logged instead of crashing
- Removed incorrect `max_tokens` from OpenRouter requests
### Changed
- Windows test cleanup now retries on locked files and applies per-test timeouts
- Updated hooks docs
## [3.69.0]
### Added
- Add `User-Agent` header to requests sent to the Cline backend
- Add default auto-tag workflow for publish release flow
- Show Cline SDK docs on the Cline page
### Fixed
- Retry nested git restore and prevent silent `.git_disabled` leftovers in checkpoints
- Prevent Chinese filename escaping in diff view
- Trigger auto-compaction on OpenRouter context overflow errors
- Restore GPT-OSS native file editing on OpenAI-compatible models
### Changed
- Update Cline SDK docs
- Improve hooks support for Windows PowerShell
## [3.68.0]
### Added
- Add dynamic Cline provider model fetching from Cline endpoint
- Add additional Markdown formatting in CLI
- Add focus indicator on action buttons in extension
### Fixed
- Clear all OCA secrets on auth refresh failure to prevent re-auth loops
- Resolve "Could not find the file context" error in Explain Changes
- Use `JSON_SCHEMA` for `yaml.load` to prevent unsafe deserialization
- Fetch model info from API in CLI headless auth for Cline and Vercel providers
- Generate commit message from staged changes only when staging exists
- Update stale `maxTokens` values for Claude 3.7+ models across Anthropic, Bedrock, Vertex, and SAP AI Core
- Use `model.info.maxTokens` for OpenRouter instead of hardcoded `8192`
### Changed
- Increase timeout for a flaky test to reduce short-term test instability
## [3.67.1]
### Added
- Added Cline SDK API interface for programmatic access to Cline features and tools, enabling integration into custom applications.
- Added Codex 5.3 model support
### Fixed
- Fix OpenAI Codex by setting `store` to `false`
- Use `isLocatedInPath()` instead of string matching for path containment checks
## [3.67.0]
### Added
- Add support for skills and optional modelId in subagent configuration
- Add AgentConfigLoader for file-based agent configs
- Add Responses API support for OpenAI native provider
- Preconnect websocket to reduce response latency
- Fetch featured models from backend with local fallback
- Add /q command to quit CLI
- Add MCP enterprise configuration details
- Pull Cline's recommended models from internal endpoint
- Add dynamic flag to adjust banner cache duration
### Fixed
- Fix reasoning delta crash on usage-only stream chunks
- Fix OpenAI tool ID transformation restricted to native provider only
- Fix auth check for ACP mode
- Fix CLI yolo mode to not persist yolo setting to disk
- Fix inline focus-chain slider within its feature row
- Fix Gemini 3.1 Pro compatibility
- Fix Cline auth with ACP flag
### Changed
- Move PR skill to .agents/skills
- SambaNova provider: update models list
- Remove changeset-converter GitHub Action and npm run changeset
## [3.66.0]
### Added
+6 -25
View File
@@ -57,25 +57,11 @@ We also welcome contributions to our [documentation](https://github.com/cline/cl
### Creating a Pull Request
1. Before creating a PR, generate a changeset entry:
```bash
npm run changeset
```
This will prompt you for:
- Type of change (major, minor, patch)
- `major` → breaking changes (1.0.0 → 2.0.0)
- `minor` → new features (1.0.0 → 1.1.0)
- `patch` → bug fixes (1.0.0 → 1.0.1)
- Description of your changes
1. Commit your changes.
2. Commit your changes and the generated `.changeset` file
3. Push your branch and create a PR on GitHub. Our CI will:
2. Push your branch and create a PR on GitHub. Our CI will:
- Run tests and checks
- Changesetbot will create a comment showing the version impact
- When merged to main, changesetbot will create a Version Packages PR
- When the Version Packages PR is merged, a new release will be published
4. Testing
3. Testing
- Run `npm run test` to run tests locally.
- Before submitting PR, run `npm run format:fix` to format your code
@@ -192,15 +178,10 @@ Anyone can contribute code to Cline, but we ask that you follow these guidelines
- Temporary workspaces with test fixtures
- Video recording for failed tests
4. **Version Management with Changesets**
4. **Versioning & Changelog Notes**
- Create a changeset for any user-facing changes using `npm run changeset`
- Choose the appropriate version bump:
- `major` for breaking changes (1.0.0 → 2.0.0)
- `minor` for new features (1.0.0 → 1.1.0)
- `patch` for bug fixes (1.0.0 → 1.0.1)
- Write clear, descriptive changeset messages that explain the impact
- Documentation-only changes don't require changesets
- Contributors do not need to create changelog-entry files as part of PRs.
- Maintainers handle release versioning and changelog curation during the release process.
5. **Commit Guidelines**
+98 -1
View File
@@ -1,6 +1,103 @@
# cline
## 2.4.2
## [2.7.0]
### Added
- Added MCP add shortcuts for stdio and HTTP servers
- Added `--continue` for the current directory
- Added `--auto-condense` flag for AI-powered context compaction
- Added `--hooks-dir` flag for runtime hook injection
- Enabled error autocapture
- Prompt rules now include test verification guidance and make `CLI_RULES` language-agnostic
### Fixed
- Fixed remount behavior so TUI remounts only on width resize
- Fixed startup prompt replay on resize remount
- Fixed task flags so they are applied before the welcome TUI mounts
### Changed
- Hooks: reintroduced feature toggle
## [2.6.1]
### Added
- Added GPT-5.4 models for ChatGPT subscription users
- Hooks: Added a `Notification` hook for attention and completion boundaries
- Added `--hooks-dir` CLI flag for runtime hook injection
- Added `--auto-approve-all` CLI flag for interactive mode
### Fixed
- Handle streamable HTTP MCP reconnects more reliably
## [2.6.0]
### Added
- Hook payloads now include `model.provider` and `model.slug`
- Token/cost updates now happen immediately as usage chunks arrive, not after tool execution
### Fixed
- Improve subagent context compaction logic
- Subagent stream retry delay increased to reduce noise from transient failures
- State serialization errors are now caught and logged instead of crashing
- Removed incorrect `max_tokens` from OpenRouter requests
## [2.5.2]
### Added
- Added Windows PowerShell support for hooks (execution, resolution, and management), improving hook behavior on Windows for CLI and shared core workflows.
### Fixed
- Restored GPT-OSS native file editing for OpenAI-compatible models used through shared core tooling.
- Improved OpenRouter context overflow error handling so auto-compaction triggers correctly for wrapped 400 errors.
- Hardened checkpoint recovery by retrying nested git restore and preventing silent `.git_disabled` leftovers.
- Added a User-Agent header for requests to the Cline back-end to improve request handling consistency.
## [2.5.1]
### Added
- Expanded CLI markdown rendering support (headings, lists, blockquotes, fenced code blocks, links, and nested lists).
### Fixed
- Fixed CLI headless auth provider model metadata loading for Cline and Vercel AI Gateway by fetching model info from API with cache fallback.
- Increased flaky CLI import test timeout on Windows CI to reduce intermittent test failures.
## [2.5.0]
### Added
- Added Cline SDK API interface for programmatic access to Cline features and tools, enabling integration into custom applications.
- Added Codex 5.3 model support
### Fixed
- Fix OpenAI Codex by setting `store` to `false`
- Use `isLocatedInPath()` instead of string matching for path containment checks
## [2.4.3]
### Added
- Add /q command to quit CLI
- Fetch featured models from backend with local fallback
### Fixed
- Fix auth check for ACP mode
- Fix Cline auth with ACP flag
- Fix yolo mode to not persist yolo setting to disk
## [2.4.2]
### Added
+42 -10
View File
@@ -186,6 +186,7 @@ const buildEnvVars: Record<string, string> = {
const buildTimeEnvs = [
"TELEMETRY_SERVICE_API_KEY",
"ERROR_SERVICE_API_KEY",
"ENABLE_ERROR_AUTOCAPTURE",
"POSTHOG_TELEMETRY_ENABLED",
"OTEL_TELEMETRY_ENABLED",
"OTEL_LOGS_EXPORTER",
@@ -208,8 +209,8 @@ if (production) {
buildEnvVars["process.env.IS_DEV"] = "false"
}
const config: esbuild.BuildOptions = {
entryPoints: [path.join(__dirname, "src", "index.ts")],
// Shared build options
const sharedOptions: Partial<esbuild.BuildOptions> = {
bundle: true,
minify: production,
sourcemap: !production,
@@ -221,7 +222,6 @@ const config: esbuild.BuildOptions = {
sourcesContent: false,
platform: "node",
target: "node20",
outfile: path.join(__dirname, "dist", "cli.mjs"),
// These modules need to load files from the module directory at runtime
external: [
"@grpc/reflection",
@@ -237,6 +237,13 @@ const config: esbuild.BuildOptions = {
"@vscode/ripgrep", // Uses __dirname to locate the binary
],
supported: { "top-level-await": true },
}
// CLI executable configuration
const cliConfig: esbuild.BuildOptions = {
...sharedOptions,
entryPoints: [path.join(__dirname, "src", "index.ts")],
outfile: path.join(__dirname, "dist", "cli.mjs"),
banner: {
js: `#!/usr/bin/env node
// Suppress all Node.js warnings (deprecation, experimental, etc.)
@@ -250,19 +257,44 @@ const __dirname = _dirname(__filename);`,
},
}
// Library configuration for programmatic use
const libConfig: esbuild.BuildOptions = {
...sharedOptions,
entryPoints: [path.join(__dirname, "src", "exports.ts")],
outfile: path.join(__dirname, "dist", "lib.mjs"),
banner: {
js: `// Cline Library - Programmatic API
import { createRequire as _createRequire } from 'module';
import { fileURLToPath as _fileURLToPath } from 'url';
import { dirname as _dirname } from 'path';
const require = _createRequire(import.meta.url);
const __filename = _fileURLToPath(import.meta.url);
const __dirname = _dirname(__filename);`,
},
}
async function main() {
const ctx = await esbuild.context(config)
if (watch) {
// In watch mode, only watch the CLI (primary use case for development)
const ctx = await esbuild.context(cliConfig)
await ctx.watch()
console.log("[cli] Watching for changes...")
} else {
await ctx.rebuild()
await ctx.dispose()
// Build both CLI and library
console.log("[cli esbuild] Building CLI executable...")
const cliCtx = await esbuild.context(cliConfig)
await cliCtx.rebuild()
await cliCtx.dispose()
// Make the output executable
const outfile = path.join(__dirname, "dist", "cli.mjs")
if (fs.existsSync(outfile)) {
fs.chmodSync(outfile, "755")
console.log("[cli esbuild] Building library bundle...")
const libCtx = await esbuild.context(libConfig)
await libCtx.rebuild()
await libCtx.dispose()
// Make the CLI output executable
const cliOutfile = path.join(__dirname, "dist", "cli.mjs")
if (fs.existsSync(cliOutfile)) {
fs.chmodSync(cliOutfile, "755")
}
}
}
+5
View File
@@ -162,6 +162,8 @@ When running **cline** with just a prompt (no subcommand), these options are ava
**-T**, **\--taskId** *id* : Resume an existing task by ID instead of starting a new one. The prompt becomes an optional follow-up message.
**\--continue** : Resume the most recent task from the current working directory instead of starting a new one.
# JSON OUTPUT FORMAT
When using **\--json**, each message is output as a JSON object with these fields:
@@ -268,6 +270,9 @@ cline -T abc123def
# Resume a task with a follow-up message
cline -T abc123def "Now add unit tests for the changes"
# Resume the most recent task from the current directory
cline --continue
# Resume in plan mode to review before continuing
cline -T abc123def -p "What's left to do?"
+15 -5
View File
@@ -1,11 +1,18 @@
{
"name": "cline",
"version": "2.4.2",
"version": "2.7.0",
"description": "Autonomous coding agent CLI - capable of creating/editing files, running commands, using the browser, and more",
"main": "dist/cli.mjs",
"main": "dist/lib.mjs",
"types": "dist/lib.d.ts",
"bin": {
"cline": "./dist/cli.mjs"
},
"exports": {
".": {
"import": "./dist/lib.mjs",
"types": "./dist/lib.d.ts"
}
},
"os": [
"darwin",
"linux",
@@ -23,8 +30,9 @@
"scripts": {
"package:brew": "npx tsx ./scripts/update-brew-formula.mts",
"package": "npm pack --pack-destination ./dist",
"build": "npm run typecheck && npx tsx esbuild.mts",
"build:production": "npm run typecheck && npx tsx esbuild.mts --production",
"build": "npm run typecheck && npx tsx esbuild.mts && npm run build:types",
"build:production": "npm run typecheck && npx tsx esbuild.mts --production && npm run build:types",
"build:types": "(npx tsc -p tsconfig.lib.json || true) && cp dist/types/cli/src/exports.d.ts dist/lib.d.ts && mkdir -p dist/agent && cp dist/types/cli/src/agent/ClineAgent.d.ts dist/types/cli/src/agent/ClineSessionEmitter.d.ts dist/types/cli/src/agent/public-types.d.ts dist/agent/ && rm -rf dist/types",
"watch": "npx tsx esbuild.mts --watch",
"dev": "IS_DEV=true && npm run link && npm run watch ; npm run unlink",
"clean": "rimraf dist",
@@ -62,6 +70,7 @@
"url": "https://github.com/cline/cline/issues"
},
"devDependencies": {
"@types/marked": "^5.0.2",
"@types/node": "20.x",
"@types/prompts": "^2.4.9",
"@types/react": "^19.2.9",
@@ -81,8 +90,9 @@
"ink": "npm:@jrichman/ink@6.4.7",
"ink-picture": "^1.3.3",
"ink-spinner": "^5.0.0",
"ora": "^8.0.1",
"marked": "^17.0.3",
"nanoid": "^5.1.6",
"ora": "^8.0.1",
"pino": "^10.0.0",
"pino-roll": "^4.0.0",
"prompts": "^2.4.2",
+2 -6
View File
@@ -108,11 +108,7 @@ class ACPDiffServiceClient implements DiffServiceClientInterface {
class ACPEnvServiceClient implements EnvServiceClientInterface {
private readonly version: string
constructor(
_clientCapabilities: acp.ClientCapabilities | undefined,
_sessionIdResolver: SessionIdResolver,
version: string = "1.0.0",
) {
constructor(_clientCapabilities: acp.ClientCapabilities | undefined, _sessionIdResolver: SessionIdResolver, version: string) {
this.version = version
}
@@ -402,7 +398,7 @@ export class ACPHostBridgeClientProvider implements HostBridgeClientProvider {
clientCapabilities: acp.ClientCapabilities | undefined,
sessionIdResolver: SessionIdResolver,
cwdResolver: CwdResolver,
version: string = "1.0.0",
version: string,
) {
this.workspaceClient = new ACPWorkspaceServiceClient(clientCapabilities, sessionIdResolver, cwdResolver)
this.envClient = new ACPEnvServiceClient(clientCapabilities, sessionIdResolver, version)
+5 -21
View File
@@ -15,7 +15,7 @@
import type * as acp from "@agentclientprotocol/sdk"
import { Logger } from "@/shared/services/Logger.js"
import { ClineAgent } from "../agent/ClineAgent.js"
import type { AcpAgentOptions, SessionUpdateType } from "../agent/types.js"
import { type AcpAgentOptions, type SessionUpdateType } from "../agent/types.js"
/**
* ACP Agent wrapper that bridges stdio connection to ClineAgent.
@@ -39,37 +39,21 @@ export class AcpAgent implements acp.Agent {
this.clineAgent = new ClineAgent(options)
// Wire up the permission handler to use the connection
this.clineAgent.setPermissionHandler(async (request, resolve) => {
this.clineAgent.setPermissionHandler(async (request) => {
try {
Logger.debug("[AcpAgent] Forwarding permission request to connection")
const response = await this.connection.requestPermission({
sessionId: this.getCurrentSessionId() ?? "",
return await this.connection.requestPermission({
sessionId: request.sessionId,
toolCall: request.toolCall,
options: request.options,
})
resolve(response)
} catch (error) {
Logger.debug("[AcpAgent] Error requesting permission:", error)
resolve({ outcome: "rejected" as unknown as acp.RequestPermissionOutcome })
return { outcome: { outcome: "cancelled" } }
}
})
}
/**
* Get the current active session ID from the ClineAgent.
*/
private getCurrentSessionId(): string | undefined {
// Find the session that's currently processing
for (const [sessionId, session] of this.clineAgent.sessions) {
if (session.controller?.task) {
return sessionId
}
}
// Fall back to the first session if none is actively processing
const firstSession = this.clineAgent.sessions.keys().next()
return firstSession.done ? undefined : firstSession.value
}
/**
* Subscribe to session events and forward them to the connection.
*/
+3 -5
View File
@@ -15,22 +15,18 @@
import { AgentSideConnection, ndJsonStream } from "@agentclientprotocol/sdk"
import { Logger } from "@/shared/services/Logger"
import { version as CLI_VERSION } from "../../../package.json"
import { AcpAgent } from "./AcpAgent.js"
import { nodeToWebReadable, nodeToWebWritable } from "./streamUtils.js"
// Re-export classes for programmatic use
export { ClineAgent } from "../agent/ClineAgent.js"
export { ClineSessionEmitter } from "../agent/ClineSessionEmitter.js"
// Re-export types
export type {
AcpAgentOptions,
AcpSessionState,
ClineAcpSession,
ClineAgentOptions,
ClineSessionEvents,
PermissionHandler,
PermissionResolver,
} from "../agent/types.js"
export { AcpAgent } from "./AcpAgent.js"
@@ -73,6 +69,8 @@ export interface AcpModeOptions {
config?: string
/** Working directory (default: process.cwd()) */
cwd?: string
/** Additional runtime hooks directory */
hooksDir?: string
/** Enable verbose/debug logging to stderr */
verbose?: boolean
}
@@ -99,8 +97,8 @@ export async function runAcpMode(options: AcpModeOptions = {}): Promise<void> {
new AgentSideConnection((conn) => {
agent = new AcpAgent(conn, {
version: CLI_VERSION,
debug: Boolean(options.verbose),
hooksDir: options.hooksDir,
})
return agent
}, stream)
+48 -31
View File
@@ -42,6 +42,7 @@ import { getProviderModelIdKey } from "@shared/storage/provider-keys"
import { ClineEndpoint } from "@/config.js"
import { Controller } from "@/core/controller"
import { getAvailableSlashCommands } from "@/core/controller/slash/getAvailableSlashCommands"
import { setRuntimeHooksDir } from "@/core/storage/disk"
import { StateManager } from "@/core/storage/StateManager"
import { AuthHandler } from "@/hosts/external/AuthHandler.js"
import { ExternalCommentReviewController } from "@/hosts/external/ExternalCommentReviewController.js"
@@ -54,16 +55,19 @@ import { AuthService } from "@/services/auth/AuthService.js"
import { Logger } from "@/shared/services/Logger.js"
import type { Mode } from "@/shared/storage/types"
import { openExternal } from "@/utils/env"
import { version as AGENT_VERSION } from "../../package.json"
import { ACPDiffViewProvider } from "../acp/ACPDiffViewProvider.js"
import { ACPHostBridgeClientProvider } from "../acp/ACPHostBridgeClientProvider.js"
import { AcpTerminalManager } from "../acp/AcpTerminalManager.js"
import { isAuthConfigured } from "../index.js"
import { isAuthConfigured } from "../utils/auth"
import { fetchOpenRouterModels, usesOpenRouterModels } from "../utils/openrouter-models"
import { CliContextResult, initializeCliContext } from "../vscode-context.js"
import { ClineSessionEmitter } from "./ClineSessionEmitter.js"
import { translateMessage } from "./messageTranslator.js"
import { handlePermissionResponse } from "./permissionHandler.js"
import type { AcpSessionState, ClineAcpSession, ClineAgentOptions, PermissionHandler } from "./types.js"
import type { ClineAcpSession, ClineAgentOptions, PermissionHandler } from "./public-types.js"
import { AcpSessionStatus } from "./public-types.js"
import { type AcpSessionState } from "./types.js"
// Map providers to their static model lists and defaults (copied from ModelPicker.tsx)
const providerModels: Record<string, { models: Record<string, unknown>; defaultId: string }> = {
@@ -104,7 +108,12 @@ function getModelList(provider: string): string[] {
export class ClineAgent implements acp.Agent {
private readonly options: ClineAgentOptions
private readonly ctx: CliContextResult
readonly sessions: Map<string, ClineAcpSession> = new Map()
/** Map of active sessions by session ID */
public readonly sessions: Map<string, ClineAcpSession> = new Map()
/** WeakMap to associate ClineAcpSession with its Controller without exposing it to consumers */
readonly #sessionControllers = new WeakMap<ClineAcpSession, Controller>()
/** Runtime state for active sessions */
private readonly sessionStates: Map<string, AcpSessionState> = new Map()
@@ -132,7 +141,8 @@ export class ClineAgent implements acp.Agent {
constructor(options: ClineAgentOptions) {
this.options = options
this.ctx = initializeCliContext()
setRuntimeHooksDir(options.hooksDir)
this.ctx = initializeCliContext({ clineDir: options.clineDir })
}
/**
@@ -194,7 +204,7 @@ export class ClineAgent implements acp.Agent {
},
agentInfo: {
name: "cline",
version: this.options.version,
version: AGENT_VERSION,
},
authMethods: [
{
@@ -226,7 +236,7 @@ export class ClineAgent implements acp.Agent {
clientCapabilities,
() => this.currentActiveSessionId,
() => this.sessions.get(this.currentActiveSessionId ?? "")?.cwd ?? process.cwd(),
this.options.version,
AGENT_VERSION,
)
HostProvider.initialize(
@@ -289,16 +299,16 @@ export class ClineAgent implements acp.Agent {
mcpServers: params.mcpServers ?? [],
createdAt: Date.now(),
lastActivityAt: Date.now(),
controller,
}
this.#sessionControllers.set(session, controller)
this.sessions.set(sessionId, session)
// Initialize session state
const sessionState: AcpSessionState = {
sessionId,
isProcessing: false,
cancelled: false,
status: AcpSessionStatus.Idle,
pendingToolCalls: new Map(),
}
@@ -435,11 +445,11 @@ export class ClineAgent implements acp.Agent {
*
* The prompt flow:
* 1. Extract content from the ACP prompt (text, images, files)
* 2. Set up state broadcasting (subscribe to controller updates)
* 3. Initialize or continue task with Controller
* 2. Set up internal cline state subsription
* 3. Initialize or continue cline task
* 4. Translate ClineMessages to ACP SessionUpdates
* 5. Handle permission requests for tools/commands
* 6. Return when task completes, is cancelled, or needs user input
* 6. Return when cline task completes, is cancelled, or needs user input
*/
async prompt(params: acp.PromptRequest): Promise<acp.PromptResponse> {
const session = this.sessions.get(params.sessionId)
@@ -449,11 +459,11 @@ export class ClineAgent implements acp.Agent {
throw new Error(`Session not found: ${params.sessionId}`)
}
if (sessionState.isProcessing) {
if (sessionState.status === AcpSessionStatus.Processing) {
throw new Error(`Session ${params.sessionId} is already processing a prompt`)
}
const controller = session.controller
const controller = this.#sessionControllers.get(session)
if (!controller) {
throw new Error("Controller not initialized for session. This is a bug in the ACP agent setup.")
}
@@ -464,8 +474,7 @@ export class ClineAgent implements acp.Agent {
})
// Mark session as processing and set as current active session
sessionState.isProcessing = true
sessionState.cancelled = false
sessionState.status = AcpSessionStatus.Processing
session.lastActivityAt = Date.now()
this.currentActiveSessionId = params.sessionId
@@ -586,7 +595,7 @@ export class ClineAgent implements acp.Agent {
Logger.debug("[ClineAgent] Error during cleanup:", error)
}
}
sessionState.isProcessing = false
sessionState.status = AcpSessionStatus.Idle
}
}
@@ -648,7 +657,13 @@ export class ClineAgent implements acp.Agent {
permissionRequest: Omit<acp.RequestPermissionRequest, "sessionId">,
): Promise<void> {
const session = this.sessions.get(sessionId)
const controller = session?.controller
if (!session) {
Logger.debug("[ClineAgent] No session found for permission request")
return
}
const controller = this.#sessionControllers.get(session)
if (!controller?.task) {
Logger.debug("[ClineAgent] No active task for permission request")
@@ -829,7 +844,7 @@ export class ClineAgent implements acp.Agent {
await this.emitSessionUpdate(sessionId, {
sessionUpdate,
content: { type: "text", text: needsNewline ? "\n" + textDelta : textDelta },
content: { type: "text", text: needsNewline ? `\n${textDelta}` : textDelta },
})
}
@@ -882,18 +897,22 @@ export class ClineAgent implements acp.Agent {
*/
async cancel(params: acp.CancelNotification): Promise<void> {
const session = this.sessions.get(params.sessionId)
if (!session) {
Logger.debug("[ClineAgent] cancel called for non-existent session:", params.sessionId)
return
}
const sessionState = this.sessionStates.get(params.sessionId)
Logger.debug("[ClineAgent] cancel called:", {
sessionId: params.sessionId,
isProcessing: sessionState?.isProcessing,
status: sessionState?.status,
})
if (sessionState) {
sessionState.cancelled = true
sessionState.status = AcpSessionStatus.Cancelled
// If we have an active controller task, cancel it
const controller = session?.controller
const controller = this.#sessionControllers.get(session)
if (controller?.task) {
try {
await controller.cancelTask()
@@ -934,7 +953,7 @@ export class ClineAgent implements acp.Agent {
session.lastActivityAt = Date.now()
// Update Controller mode if active
const controller = session.controller
const controller = this.#sessionControllers.get(session)
if (controller) {
controller.stateManager.setGlobalState("mode", session.mode)
@@ -1065,7 +1084,7 @@ export class ClineAgent implements acp.Agent {
* @returns The permission response from the client
*/
protected async requestPermission(
_sessionId: string,
sessionId: string,
toolCall: acp.ToolCallUpdate,
options: acp.PermissionOption[],
): Promise<acp.RequestPermissionResponse> {
@@ -1080,17 +1099,15 @@ export class ClineAgent implements acp.Agent {
return { outcome: "rejected" as unknown as acp.RequestPermissionOutcome }
}
// Use the permission handler callback pattern
return new Promise<acp.RequestPermissionResponse>((resolve) => {
this.permissionHandler!({ toolCall, options }, resolve)
})
return await this.permissionHandler({ sessionId, toolCall, options })
}
async shutdown(): Promise<void> {
for (const [sessionId, session] of this.sessions) {
await session.controller?.task?.abortTask()
await session.controller?.stateManager.flushPendingState()
await session.controller?.dispose()
const controller = this.#sessionControllers.get(session)
await controller?.task?.abortTask()
await controller?.stateManager.flushPendingState()
await controller?.dispose()
this.sessions.delete(sessionId)
this.sessionStates.delete(sessionId)
}
+1 -1
View File
@@ -8,7 +8,7 @@
*/
import { EventEmitter } from "events"
import type { ClineSessionEvents } from "./types.js"
import type { ClineSessionEvents } from "./public-types.js"
/**
* Type-safe EventEmitter for ClineAgent session events.
+4 -4
View File
@@ -12,6 +12,7 @@ import type { ClineMessage } from "@shared/ExtensionMessage"
import { beforeEach, describe, expect, it } from "vitest"
import { createSessionState, translateMessage, translateMessages } from "./messageTranslator"
import type { AcpSessionState } from "./types"
import { AcpSessionStatus } from "./types"
// =============================================================================
// Test Helpers
@@ -175,8 +176,7 @@ describe("createSessionState", () => {
const state = createSessionState("my-session-123")
expect(state.sessionId).toBe("my-session-123")
expect(state.isProcessing).toBe(false)
expect(state.cancelled).toBe(false)
expect(state.status).toBe(AcpSessionStatus.Idle)
expect(state.pendingToolCalls).toBeInstanceOf(Map)
expect(state.pendingToolCalls.size).toBe(0)
expect(state.currentToolCallId).toBeUndefined()
@@ -187,11 +187,11 @@ describe("createSessionState", () => {
const state2 = createSessionState("session-2")
// Modify state1
state1.isProcessing = true
state1.status = AcpSessionStatus.Processing
state1.pendingToolCalls.set("tool-1", {} as acp.ToolCall)
// state2 should be unaffected
expect(state2.isProcessing).toBe(false)
expect(state2.status).toBe(AcpSessionStatus.Idle)
expect(state2.pendingToolCalls.size).toBe(0)
})
})
+2 -2
View File
@@ -11,6 +11,7 @@
import type * as acp from "@agentclientprotocol/sdk"
import type { ClineMessage, ClineSayBrowserAction, ClineSayTool } from "@shared/ExtensionMessage"
import type { AcpSessionState, TranslatedMessage } from "./types.js"
import { AcpSessionStatus } from "./types.js"
/**
* Maps Cline tool types to ACP ToolKind values.
@@ -1019,8 +1020,7 @@ export function translateMessages(messages: ClineMessage[], sessionState: AcpSes
export function createSessionState(sessionId: string): AcpSessionState {
return {
sessionId,
isProcessing: false,
cancelled: false,
status: AcpSessionStatus.Idle,
pendingToolCalls: new Map(),
}
}
+258
View File
@@ -0,0 +1,258 @@
/**
* Public types for the Cline library API.
*
* This file contains types that are safe to export to library consumers.
* It must NOT import any internal types (Controller, StateManager, etc.)
* to keep the generated declaration files clean.
*
* Internal-only extensions of these types live in ./types.ts.
*/
import type * as acp from "@agentclientprotocol/sdk"
// ============================================================
// Session Update Type Utilities
// ============================================================
/**
* Different types of updates that can be sent during session processing.
*
* These updates provide real-time feedback about the agent's progress.
*
* See protocol docs: [Agent Reports Output](https://agentclientprotocol.com/protocol/prompt-turn#3-agent-reports-output)
*/
export type SessionUpdateType = acp.SessionUpdate["sessionUpdate"]
/**
* Different types of update payloads that can be sent during session processing.
*
* Each update type has a corresponding payload structure defined in the ACP SessionUpdate union.
*/
export type SessionUpdatePayload<T extends SessionUpdateType> = Omit<
Extract<acp.SessionUpdate, { sessionUpdate: T }>,
"sessionUpdate"
>
// ============================================================
// Permission Handler Callback Types
// ============================================================
/**
* Handler function for permission requests.
* Called when the agent needs permission for a tool call.
* The handler should present the request to the user and call resolve() with their response.
*/
export type PermissionHandler = (request: acp.RequestPermissionRequest) => Promise<acp.RequestPermissionResponse>
// ============================================================
// Session Event Emitter Types
// ============================================================
/**
* Maps ACP SessionUpdate types to their event listener signatures.
* Uses the sessionUpdate discriminator to derive event names and payload types.
*/
export type ClineSessionEvents = {
[K in SessionUpdateType]: (payload: SessionUpdatePayload<K>) => void
} & {
/** Error event for session-level errors (not part of ACP SessionUpdate) */
error: (error: Error) => void
}
// ============================================================
// ClineAgent Options
// ============================================================
/**
* Options for creating a ClineAgent instance.
*/
export interface ClineAgentOptions {
/** Whether debug logging is enabled */
debug?: boolean
/** Cline Config Directory (defaults to ~/.cline) */
clineDir?: string
/** Additional runtime hooks directory */
hooksDir?: string
}
/**
* Options for creating an ACP agent instance.
*/
export interface AcpAgentOptions {
/** Whether debug logging is enabled */
debug?: boolean
/** Additional runtime hooks directory */
hooksDir?: string
}
// ============================================================
// Session Types
// ============================================================
export type SessionID = string
/**
* Extended session data stored by Cline for ACP sessions.
*/
export interface ClineAcpSession {
/** Unique session ID */
sessionId: SessionID
/** Working directory for the session */
cwd: string
/** Current mode (plan/act) */
mode: "plan" | "act"
/** MCP servers passed from the client */
mcpServers: acp.McpServer[]
/** Timestamp when session was created */
createdAt: number
/** Timestamp of last activity */
lastActivityAt: number
/** Whether this session was loaded from history (needs resume on first prompt) */
isLoadedFromHistory?: boolean
/** Model ID override for plan mode (format: "provider/modelId") */
planModeModelId?: string
/** Model ID override for act mode (format: "provider/modelId") */
actModeModelId?: string
}
/**
* Lifecycle status of an ACP session.
*
* Represents the state machine:
* Idle Processing Idle (normal completion)
* Idle Processing Cancelled (cancellation, then back to Idle on next prompt)
*/
export enum AcpSessionStatus {
/** Session is idle, waiting for a prompt */
Idle = "idle",
/** Session is actively processing a prompt */
Processing = "processing",
/** Session processing was cancelled */
Cancelled = "cancelled",
}
/**
* State tracking for an active ACP session within Cline.
*/
export interface AcpSessionState {
/** Session ID */
sessionId: SessionID
/** Current lifecycle status of the session */
status: AcpSessionStatus
/** Current tool call ID being executed (if any) */
currentToolCallId?: string
/** Accumulated tool calls for permission batching */
pendingToolCalls: Map<string, acp.ToolCall>
}
// ============================================================
// Agent Capabilities
// ============================================================
/**
* Cline-specific agent capabilities extending the ACP base capabilities.
*/
export interface ClineAgentCapabilities {
/** Support for loading sessions from disk */
loadSession: boolean
/** Prompt capabilities for the agent */
promptCapabilities: {
/** Support for image inputs */
image: boolean
/** Support for audio inputs */
audio: boolean
/** Support for embedded context (file resources) */
embeddedContext: boolean
}
/** MCP server passthrough capabilities */
mcpCapabilities: {
/** Support for HTTP MCP servers */
http: boolean
/** Support for SSE MCP servers */
sse: boolean
}
}
/**
* Cline agent info for ACP initialization response.
*/
export interface ClineAgentInfo {
name: "cline"
title: "Cline"
version: string
}
// ============================================================
// Permission Options
// ============================================================
/**
* Permission option as presented to the ACP client.
*/
export interface ClinePermissionOption {
kind: acp.PermissionOptionKind
name: string
optionId: string
}
// ============================================================
// Message Translation
// ============================================================
/**
* Result of translating a Cline message to ACP session update(s).
* A single Cline message may produce multiple ACP updates.
*/
export interface TranslatedMessage {
/** The session updates to send */
updates: acp.SessionUpdate[]
/** Whether this message requires a permission request */
requiresPermission?: boolean
/** Permission request details if required */
permissionRequest?: Omit<acp.RequestPermissionRequest, "sessionId">
/** The toolCallId that was created/used (for tracking across streaming updates) */
toolCallId?: string
}
// ============================================================
// Re-exported ACP Types
// ============================================================
export type {
Agent,
AgentSideConnection,
AudioContent,
CancelNotification,
ClientCapabilities,
ContentBlock,
ImageContent,
InitializeRequest,
InitializeResponse,
LoadSessionRequest,
LoadSessionResponse,
McpServer,
ModelInfo,
NewSessionRequest,
NewSessionResponse,
PermissionOption,
PermissionOptionKind,
PromptRequest,
PromptResponse,
RequestPermissionRequest,
RequestPermissionResponse,
SessionConfigOption,
SessionModelState,
SessionNotification,
SessionUpdate,
SetSessionConfigOptionRequest,
SetSessionConfigOptionResponse,
SetSessionModelRequest,
SetSessionModelResponse,
SetSessionModeRequest,
SetSessionModeResponse,
StopReason,
TextContent,
ToolCall,
ToolCallStatus,
ToolCallUpdate,
ToolKind,
} from "@agentclientprotocol/sdk"
+20 -199
View File
@@ -1,76 +1,13 @@
/**
* Custom types and extensions for ACP integration with Cline CLI.
* Internal types for ACP integration with Cline CLI.
*
* This file extends the base ACP types with Cline-specific functionality.
* This file re-exports all public types from ./public-types.ts and adds
* internal-only Types that reference core modules (Controller, etc.).
*
* Library consumers should never import from this file directly they
* get the public types via the library entrypoint (exports.ts).
*/
import type * as acp from "@agentclientprotocol/sdk"
import type { Controller } from "@/core/controller"
// ============================================================
// Session Update Type Utilities
// ============================================================
/**
* Extract the sessionUpdate discriminator value from a SessionUpdate variant.
*/
export type SessionUpdateType = acp.SessionUpdate["sessionUpdate"]
/**
* Extract the payload type for a given sessionUpdate discriminator value.
* This removes the `sessionUpdate` discriminator field from the type.
*/
export type SessionUpdatePayload<T extends SessionUpdateType> = Omit<
Extract<acp.SessionUpdate, { sessionUpdate: T }>,
"sessionUpdate"
>
// ============================================================
// Permission Handler Callback Types
// ============================================================
/**
* Callback to resolve a permission request with the user's response.
*/
export type PermissionResolver = (response: acp.RequestPermissionResponse) => void
/**
* Handler function for permission requests.
* Called when the agent needs permission for a tool call.
* The handler should present the request to the user and call resolve() with their response.
*/
export type PermissionHandler = (request: Omit<acp.RequestPermissionRequest, "sessionId">, resolve: PermissionResolver) => void
// ============================================================
// Session Event Emitter Types
// ============================================================
/**
* Maps ACP SessionUpdate types to their event listener signatures.
* Uses the sessionUpdate discriminator to derive event names and payload types.
*/
export type ClineSessionEvents = {
[K in SessionUpdateType]: (payload: SessionUpdatePayload<K>) => void
} & {
/** Error event for session-level errors (not part of ACP SessionUpdate) */
error: (error: Error) => void
}
// ============================================================
// ClineAgent Options (decoupled from connection)
// ============================================================
/**
* Options for creating a ClineAgent instance (decoupled from connection).
*/
export interface ClineAgentOptions {
/** CLI version string */
version: string
/** Whether debug logging is enabled */
debug?: boolean
}
// Re-export common ACP types for convenience
export type {
Agent,
AgentSideConnection,
@@ -114,134 +51,18 @@ export type {
WriteTextFileResponse,
} from "@agentclientprotocol/sdk"
/**
* Cline-specific agent capabilities extending the ACP base capabilities.
*/
export interface ClineAgentCapabilities {
/** Support for loading sessions from disk */
loadSession: boolean
/** Prompt capabilities for the agent */
promptCapabilities: {
/** Support for image inputs */
image: boolean
/** Support for audio inputs */
audio: boolean
/** Support for embedded context (file resources) */
embeddedContext: boolean
}
/** MCP server passthrough capabilities */
mcpCapabilities: {
/** Support for HTTP MCP servers */
http: boolean
/** Support for SSE MCP servers */
sse: boolean
}
}
export type {
AcpAgentOptions,
AcpSessionState,
ClineAgentCapabilities,
ClineAgentInfo,
ClineAgentOptions,
ClinePermissionOption,
ClineSessionEvents,
PermissionHandler,
SessionUpdatePayload,
SessionUpdateType,
TranslatedMessage,
} from "./public-types.js"
/**
* Cline agent info for ACP initialization response.
*/
export interface ClineAgentInfo {
name: "cline"
title: "Cline"
version: string
}
/**
* Extended session data stored by Cline for ACP sessions.
* Maps to Cline's task history structure.
*/
export interface ClineAcpSession {
/** Unique session/task ID */
sessionId: string
/** Working directory for the session */
cwd: string
/** Current mode (plan/act) */
mode: "plan" | "act"
/** MCP servers passed from the client */
mcpServers: acp.McpServer[]
/** Timestamp when session was created */
createdAt: number
/** Timestamp of last activity */
lastActivityAt: number
/** Whether this session was loaded from history (needs resume on first prompt) */
isLoadedFromHistory?: boolean
/** Controller instance for this session (manages task execution) */
controller?: Controller
/** Model ID override for plan mode (format: "provider/modelId") */
planModeModelId?: string
/** Model ID override for act mode (format: "provider/modelId") */
actModeModelId?: string
}
/**
* Permission option as presented to the ACP client.
*/
export interface ClinePermissionOption {
kind: acp.PermissionOptionKind
name: string
optionId: string
}
/**
* Mapping of Cline message types to their ACP session update equivalents.
*/
export type ClineToAcpUpdateMapping = {
/** Text messages from the agent */
text: "agent_message_chunk"
/** Reasoning/thinking from the agent */
reasoning: "agent_thought_chunk"
/** Markdown content from the agent */
markdown: "agent_message_chunk"
/** Tool execution */
tool: "tool_call"
/** Command execution */
command: "tool_call"
/** Command output */
command_output: "tool_call_update"
/** Task completion */
completion_result: "end_turn"
/** Error messages */
error: "tool_call_update" | "error"
}
/**
* Options for creating an ACP agent instance.
*/
export interface AcpAgentOptions {
/** CLI version string */
version: string
/** Whether debug logging is enabled */
debug?: boolean
}
/**
* Result of translating a Cline message to ACP session update(s).
* A single Cline message may produce multiple ACP updates.
*/
export interface TranslatedMessage {
/** The session updates to send */
updates: acp.SessionUpdate[]
/** Whether this message requires a permission request */
requiresPermission?: boolean
/** Permission request details if required */
permissionRequest?: Omit<acp.RequestPermissionRequest, "sessionId">
/** The toolCallId that was created/used (for tracking across streaming updates) */
toolCallId?: string
}
/**
* State tracking for an active ACP session within Cline.
*/
export interface AcpSessionState {
/** Session ID */
sessionId: string
/** Whether the session is currently processing a prompt */
isProcessing: boolean
/** Current tool call ID being executed (if any) */
currentToolCallId?: string
/** Whether the session has been cancelled */
cancelled: boolean
/** Accumulated tool calls for permission batching */
pendingToolCalls: Map<string, acp.ToolCall>
}
export { AcpSessionStatus } from "./public-types.js"
@@ -0,0 +1,136 @@
import { Text } from "ink"
import { render } from "ink-testing-library"
import React from "react"
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"
import { App } from "./App"
const CLEAR_SEQUENCE = "\x1b[2J\x1b[3J\x1b[H"
function setTerminalSize(columns: number, rows: number) {
Object.defineProperty(process.stdout, "columns", {
configurable: true,
writable: true,
value: columns,
})
Object.defineProperty(process.stdout, "rows", {
configurable: true,
writable: true,
value: rows,
})
}
function hasClearSequenceCall(calls: unknown[][]): boolean {
return calls.some((call) => call[0] === CLEAR_SEQUENCE)
}
vi.mock("./ChatView", () => ({
ChatView: ({ controller, initialPrompt, initialImages }: any) => {
React.useEffect(() => {
if (initialPrompt || (initialImages && initialImages.length > 0)) {
controller?.initTask(initialPrompt || "", initialImages)
}
}, [])
return React.createElement(Text, null, "ChatView")
},
}))
vi.mock("./TaskJsonView", () => ({
TaskJsonView: () => React.createElement(Text, null, "TaskJsonView"),
}))
vi.mock("./HistoryView", () => ({
HistoryView: () => React.createElement(Text, null, "HistoryView"),
}))
vi.mock("./ConfigView", () => ({
ConfigView: () => React.createElement(Text, null, "ConfigView"),
}))
vi.mock("./AuthView", () => ({
AuthView: () => React.createElement(Text, null, "AuthView"),
}))
vi.mock("../context/TaskContext", () => ({
TaskContextProvider: ({ children }: any) => children,
}))
vi.mock("../context/StdinContext", () => ({
StdinProvider: ({ children }: any) => children,
}))
describe("App startup prompt resize behavior", () => {
beforeEach(() => {
vi.useFakeTimers()
})
afterEach(() => {
vi.useRealTimers()
vi.restoreAllMocks()
delete (process.stdout as any).columns
delete (process.stdout as any).rows
})
it("does not replay initialPrompt after a width resize", async () => {
const initTask = vi.fn()
setTerminalSize(120, 40)
const writeSpy = vi.spyOn(process.stdout, "write").mockImplementation(((...args: any[]) => {
const callback = args.find((arg) => typeof arg === "function")
if (callback) {
callback()
}
return true
}) as any)
const { unmount } = render(
<App controller={{ initTask }} initialPrompt="hello" isRawModeSupported={true} view="welcome" />,
)
await vi.advanceTimersByTimeAsync(0)
expect(initTask).toHaveBeenCalledTimes(1)
writeSpy.mockClear()
setTerminalSize(121, 40)
process.stdout.emit("resize")
await vi.advanceTimersByTimeAsync(350)
await vi.advanceTimersByTimeAsync(0)
expect(initTask).toHaveBeenCalledTimes(1)
expect(hasClearSequenceCall(writeSpy.mock.calls as unknown[][])).toBe(true)
unmount()
})
it("does not remount on height-only resize", async () => {
const initTask = vi.fn()
setTerminalSize(120, 40)
const writeSpy = vi.spyOn(process.stdout, "write").mockImplementation(((...args: any[]) => {
const callback = args.find((arg) => typeof arg === "function")
if (callback) {
callback()
}
return true
}) as any)
const { unmount } = render(
<App controller={{ initTask }} initialPrompt="hello" isRawModeSupported={true} view="welcome" />,
)
await vi.advanceTimersByTimeAsync(0)
expect(initTask).toHaveBeenCalledTimes(1)
writeSpy.mockClear()
setTerminalSize(120, 45)
process.stdout.emit("resize")
await vi.advanceTimersByTimeAsync(350)
await vi.advanceTimersByTimeAsync(0)
expect(initTask).toHaveBeenCalledTimes(1)
expect(hasClearSequenceCall(writeSpy.mock.calls as unknown[][])).toBe(false)
unmount()
})
})
+27 -5
View File
@@ -3,14 +3,15 @@
* Routes between different views (task, history, config)
*/
import { Box } from "ink"
import React, { ReactNode, useCallback, useState } from "react"
import { Box, useApp } from "ink"
import React, { ReactNode, useCallback, useEffect, useState } from "react"
import { StdinProvider } from "../context/StdinContext"
import { TaskContextProvider } from "../context/TaskContext"
import { useTerminalSize } from "../hooks/useTerminalSize"
import { AuthView } from "./AuthView"
import { ChatView } from "./ChatView"
import { ConfigView } from "./ConfigView"
import { ErrorBoundary } from "./ErrorBoundary"
import { HistoryView } from "./HistoryView"
import { TaskJsonView } from "./TaskJsonView"
@@ -90,7 +91,17 @@ interface AppProps {
isRawModeSupported?: boolean
}
export const App: React.FC<AppProps> = ({
export const App: React.FC<AppProps> = (props) => {
const { exit } = useApp()
return (
<ErrorBoundary exit={exit}>
<InternalApp {...props} />
</ErrorBoundary>
)
}
const InternalApp: React.FC<AppProps> = ({
view: initialView,
taskId,
verbose = false,
@@ -135,6 +146,17 @@ export const App: React.FC<AppProps> = ({
const { resizeKey } = useTerminalSize()
const [currentView, setCurrentView] = useState<ViewType>(initialView)
const [selectedTaskId, setSelectedTaskId] = useState<string | undefined>(taskId)
const [pendingInitialPrompt, setPendingInitialPrompt] = useState<string | undefined>(initialPrompt)
const [pendingInitialImages, setPendingInitialImages] = useState<string[] | undefined>(initialImages)
useEffect(() => {
if (!pendingInitialPrompt && (!pendingInitialImages || pendingInitialImages.length === 0)) {
return
}
setPendingInitialPrompt(undefined)
setPendingInitialImages(undefined)
}, [pendingInitialPrompt, pendingInitialImages])
const handleSelectTask = useCallback((taskId: string) => {
setSelectedTaskId(taskId)
@@ -242,8 +264,8 @@ export const App: React.FC<AppProps> = ({
) : (
<ChatView
controller={controller}
initialImages={initialImages}
initialPrompt={initialPrompt}
initialImages={pendingInitialImages}
initialPrompt={pendingInitialPrompt}
onComplete={onComplete}
onError={onError}
onExit={onWelcomeExit}
+6 -4
View File
@@ -15,6 +15,7 @@ import { StringRequest } from "@/shared/proto/cline/common"
import { openExternal } from "@/utils/env"
import { COLORS } from "../constants/colors"
import { useStdinContext } from "../context/StdinContext"
import { useClineFeaturedModels } from "../hooks/useClineFeaturedModels"
import { useOcaAuth } from "../hooks/useOcaAuth"
import { useScrollableList } from "../hooks/useScrollableList"
import { type DetectedSources, detectImportSources, type ImportSource } from "../utils/import-configs"
@@ -172,6 +173,7 @@ export const AuthView: React.FC<AuthViewProps> = ({ controller, onComplete, onEr
const [providerSearch, setProviderSearch] = useState("")
const [providerIndex, setProviderIndex] = useState(0)
const [clineModelIndex, setClineModelIndex] = useState(0)
const featuredModels = useClineFeaturedModels()
const [importSources, setImportSources] = useState<DetectedSources>({ codex: false, opencode: false })
const [importSource, setImportSource] = useState<ImportSource | null>(null)
const [bedrockConfig, setBedrockConfig] = useState<BedrockConfig | null>(null)
@@ -767,7 +769,7 @@ export const AuthView: React.FC<AuthViewProps> = ({ controller, onComplete, onEr
<Box flexDirection="column">
<Text color="white">Choose a model</Text>
<Text> </Text>
<FeaturedModelPicker selectedIndex={clineModelIndex} />
<FeaturedModelPicker featuredModels={featuredModels} selectedIndex={clineModelIndex} />
</Box>
)
}
@@ -869,17 +871,17 @@ export const AuthView: React.FC<AuthViewProps> = ({ controller, onComplete, onEr
setProviderSearch((prev) => prev + input)
}
} else if (step === "cline_model") {
const maxIndex = getFeaturedModelMaxIndex()
const maxIndex = getFeaturedModelMaxIndex(featuredModels)
if (key.upArrow) {
setClineModelIndex((prev) => (prev > 0 ? prev - 1 : maxIndex))
} else if (key.downArrow) {
setClineModelIndex((prev) => (prev < maxIndex ? prev + 1 : 0))
} else if (key.return) {
if (isBrowseAllSelected(clineModelIndex)) {
if (isBrowseAllSelected(clineModelIndex, featuredModels)) {
setStep("modelid")
} else {
const selectedModel = getFeaturedModelAtIndex(clineModelIndex)
const selectedModel = getFeaturedModelAtIndex(clineModelIndex, featuredModels)
if (selectedModel) {
handleClineModelSelect(selectedModel.id)
}
@@ -0,0 +1,53 @@
import type { ClineMessage } from "@shared/ExtensionMessage"
import { render } from "ink-testing-library"
import React from "react"
import { describe, expect, it, vi } from "vitest"
import { ChatMessage } from "./ChatMessage"
vi.mock("../hooks/useTerminalSize", () => ({
useTerminalSize: () => ({
columns: 120,
rows: 40,
resizeKey: 0,
}),
}))
describe("ChatMessage markdown rendering", () => {
it("renders basic markdown elements correctly with appropriate styling", () => {
const message: ClineMessage = {
ts: Date.now(),
type: "say",
say: "text",
text: "# Heading 1\n\nThis is a **bold** and *italic* text with `inline code`.\n\n- List item 1\n- List item 2\n\n> Blockquote\n\n```javascript\nconst x = 1;\n```",
}
const { lastFrame } = render(React.createElement(ChatMessage, { message, mode: "act" }))
const frame = lastFrame() || ""
// Check for heading (bold)
// \x1B[1m is the ANSI escape code for bold
expect(frame).toMatch(/\x1B\[1mHeading 1\x1B\[22m/)
// Check for bold text
expect(frame).toMatch(/\x1B\[1mbold\x1B\[22m/)
// Check for italic text
// \x1B[3m is the ANSI escape code for italic
expect(frame).toMatch(/\x1B\[3mitalic\x1B\[23m/)
// Check for inline code (no special styling in the current implementation, just text)
expect(frame).toContain("inline code")
// Check for list items (gray bullet)
// \x1B[90m is the ANSI escape code for gray
expect(frame).toMatch(/\x1B\[90m• \x1B\[39mList item 1/)
expect(frame).toMatch(/\x1B\[90m• \x1B\[39mList item 2/)
// Check for blockquote (gray pipe)
expect(frame).toMatch(/\x1B\[90m│ \x1B\[39mBlockquote/)
// Check for code block (cyan text)
// \x1B[36m is the ANSI escape code for cyan
expect(frame).toMatch(/\x1B\[36mconst x = 1;\x1B\[39m/)
})
})
+133 -63
View File
@@ -11,6 +11,7 @@ import { COMMAND_OUTPUT_STRING } from "@shared/combineCommandSequences"
import type { ClineAskUseMcpServer, ClineMessage } from "@shared/ExtensionMessage"
import { Box, Text } from "ink"
import Spinner from "ink-spinner"
import { lexer, type Token, type Tokens } from "marked"
import React from "react"
import { COLORS } from "../constants/colors"
import { useTerminalSize } from "../hooks/useTerminalSize"
@@ -20,13 +21,10 @@ import { DiffView } from "./DiffView"
import { SubagentMessage } from "./SubagentMessage"
/**
* Add "(Tab)" hint after "Act mode" mentions.
* Add "(Tab)" hint after "Act mode" mentions in plain text.
* Case-insensitive, avoids double-adding if already present.
* Matches just "Act mode" without requiring "to " prefix because markdown
* processing may split "toggle to **Act mode**" into separate text chunks.
*/
function addActModeHint(text: string, keyPrefix: string): React.ReactNode[] {
// Match "Act mode" in various capitalizations, but not if already followed by (Tab)
const actModeRegex = /\bact\s+mode\b(?!\s*\(tab\))/gi
const parts = text.split(actModeRegex)
const matches = text.match(actModeRegex)
@@ -37,9 +35,7 @@ function addActModeHint(text: string, keyPrefix: string): React.ReactNode[] {
const nodes: React.ReactNode[] = []
parts.forEach((part, i) => {
if (part) {
nodes.push(part)
}
if (part) nodes.push(part)
if (matches[i]) {
nodes.push(
<React.Fragment key={`${keyPrefix}-act-mode-${i}`}>
@@ -49,72 +45,146 @@ function addActModeHint(text: string, keyPrefix: string): React.ReactNode[] {
)
}
})
return nodes
}
/**
* Render inline markdown: **bold**, *italic*, `code`
* Also adds "(Tab)" hints after "Act mode" mentions.
* Returns array of React nodes with appropriate styling
* Render an array of marked tokens as Ink React nodes.
* This is the entry point for recursive rendering each token may
* contain child tokens (e.g. a paragraph contains inline tokens,
* a list contains items, etc.).
*/
function renderInlineMarkdown(text: string): React.ReactNode[] {
const nodes: React.ReactNode[] = []
let hintCallIndex = 0
const addHintedText = (value: string) => addActModeHint(value, `hint-${hintCallIndex++}`)
// Match **bold**, *italic*, or `code` - order matters (** before *)
const regex = /(\*\*[^*]+\*\*|\*[^*]+\*|`[^`]+`)/g
let lastIndex = 0
let match
while ((match = regex.exec(text)) !== null) {
// Add text before match (with Act Mode hint processing)
if (match.index > lastIndex) {
const beforeText = text.slice(lastIndex, match.index)
nodes.push(...addHintedText(beforeText))
}
const fullMatch = match[0]
const key = `md-${match.index}`
if (fullMatch.startsWith("**") && fullMatch.endsWith("**")) {
// Bold - also process for Act Mode hints inside bold text
const boldContent = fullMatch.slice(2, -2)
const hintedContent = addHintedText(boldContent)
nodes.push(
<Text bold key={key}>
{hintedContent}
</Text>,
)
} else if (fullMatch.startsWith("*") && fullMatch.endsWith("*")) {
// Italic
nodes.push(
<Text italic key={key}>
{fullMatch.slice(1, -1)}
</Text>,
)
} else if (fullMatch.startsWith("`") && fullMatch.endsWith("`")) {
// Inline code
nodes.push(<Text key={key}>{fullMatch.slice(1, -1)}</Text>)
}
lastIndex = regex.lastIndex
}
// Add remaining text (with Act Mode hint processing)
if (lastIndex < text.length) {
nodes.push(...addHintedText(text.slice(lastIndex)))
}
return nodes.length > 0 ? nodes : addHintedText(text)
function renderTokens(tokens: Token[], color?: string): React.ReactNode[] {
return tokens.map((token, i) => renderToken(token, i, color))
}
/**
* Render text with inline markdown support
* Render a single marked token (block or inline) as an Ink React node.
* Handles both block-level tokens (heading, paragraph, list, code, etc.)
* and inline tokens (strong, em, codespan, link, text).
*/
function renderToken(token: Token, key: number, color?: string): React.ReactNode {
switch (token.type) {
// --- Block tokens ---
case "heading": {
const { depth, tokens } = token as Tokens.Heading
return (
<Box key={key} marginY={depth === 1 ? 1 : 0}>
<Text bold color={color}>
{renderTokens(tokens, color)}
</Text>
</Box>
)
}
case "paragraph":
return (
<Text color={color} key={key}>
{renderTokens((token as Tokens.Paragraph).tokens, color)}
</Text>
)
case "code":
return (
<Box flexDirection="column" key={key} marginY={1}>
{(token as Tokens.Code).text.split("\n").map((line, i) => (
<Text color="cyan" key={i}>
{line || " "}
</Text>
))}
</Box>
)
case "list": {
const { ordered, start, items } = token as Tokens.List
return (
<Box flexDirection="column" key={key}>
{items.map((item, i) => (
<Box flexDirection="row" key={i}>
<Text color="gray">{ordered ? `${Number(start ?? 1) + i}. ` : "• "}</Text>
<Box flexDirection="column" flexGrow={1}>
{renderTokens(item.tokens, color)}
</Box>
</Box>
))}
</Box>
)
}
case "blockquote":
return (
<Box flexDirection="row" key={key}>
<Text color="gray"> </Text>
<Box flexDirection="column">{renderTokens((token as Tokens.Blockquote).tokens, color)}</Box>
</Box>
)
case "space":
return <Text key={key}> </Text>
// --- Inline tokens ---
case "strong":
return (
<Text bold color={color} key={key}>
{renderTokens((token as Tokens.Strong).tokens, color)}
</Text>
)
case "em":
return (
<Text color={color} italic key={key}>
{renderTokens((token as Tokens.Em).tokens, color)}
</Text>
)
case "codespan":
return <Text key={key}>{(token as Tokens.Codespan).text}</Text>
case "link": {
const { text, href } = token as Tokens.Link
return (
<Text color={color} key={key}>
{text && text !== href ? `${text} (${href})` : href}
</Text>
)
}
case "text": {
const { text, tokens } = token as Tokens.Text
if (tokens?.length) {
return (
<Text color={color} key={key}>
{renderTokens(tokens, color)}
</Text>
)
}
return (
<Text color={color} key={key}>
{addActModeHint(text, `${key}`)}
</Text>
)
}
// Fallback for any unhandled token type
default:
return "raw" in token ? (
<Text color={color} key={key}>
{(token as { raw: string }).raw}
</Text>
) : null
}
}
/**
* Render a markdown string as Ink components.
* Uses marked's lexer to parse markdown into tokens, then renders
* each token to the appropriate Ink component.
*/
const MarkdownText: React.FC<{ children: string; color?: string }> = ({ children, color }) => {
const nodes = renderInlineMarkdown(children)
return <Text color={color}>{nodes}</Text>
const tokens = lexer(children)
return <Box flexDirection="column">{renderTokens(tokens, color)}</Box>
}
interface ChatMessageProps {
+10
View File
@@ -84,6 +84,16 @@ describe("ConfigView", () => {
)
expect(lastFrame()).toContain("Global Settings")
})
it("hides Hooks tab when hooks are disabled", () => {
const { lastFrame } = render(<ConfigView {...defaultProps} hooksEnabled={false} skillsEnabled={true} />)
expect(lastFrame()).not.toContain("Hooks")
})
it("shows Hooks tab when hooks are enabled", () => {
const { lastFrame } = render(<ConfigView {...defaultProps} hooksEnabled={true} skillsEnabled={true} />)
expect(lastFrame()).toContain("Hooks")
})
})
describe("value formatting", () => {
+51
View File
@@ -0,0 +1,51 @@
import { Box, Text } from "ink"
import React from "react"
import { ErrorService } from "@/services/error"
import { StaticRobotFrame } from "./AsciiMotionCli"
type Props = React.PropsWithChildren<{ exit: (error?: Error) => void }>
async function onReactError(props: Props, error: Error, errorInfo: React.ErrorInfo) {
try {
await ErrorService.get().captureException(error, { context: "ErrorBoundary", errorInfo })
await ErrorService.get().dispose()
} catch {
// Ignore errors
} finally {
props.exit(error)
}
}
export class ErrorBoundary extends React.Component<Props, { hasError: boolean }> {
override state = { hasError: false }
constructor(props: Props) {
super(props)
}
override componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void {
onReactError(this.props, error, errorInfo)
}
static getDerivedStateFromError() {
return { hasError: true }
}
override render() {
if (this.state.hasError) {
return (
<Box flexDirection="column" height="100%" key="header" width="100%">
<StaticRobotFrame />
<Text> </Text>
<Text bold color="white">
Something went wrong. We're sorry.
</Text>
<Text color="white">Please check the logs for more details.</Text>
<Text> </Text>
</Box>
)
}
return this.props.children
}
}
+10 -11
View File
@@ -7,13 +7,14 @@
import { Box, Text } from "ink"
import React from "react"
import { COLORS } from "../constants/colors"
import { type FeaturedModel, getAllFeaturedModels } from "../constants/featured-models"
import type { FeaturedModel } from "../constants/featured-models"
interface FeaturedModelPickerProps {
selectedIndex: number
title?: string
showBrowseAll?: boolean
helpText?: string
featuredModels: FeaturedModel[]
}
export const FeaturedModelPicker: React.FC<FeaturedModelPickerProps> = ({
@@ -21,8 +22,9 @@ export const FeaturedModelPicker: React.FC<FeaturedModelPickerProps> = ({
title,
showBrowseAll = true,
helpText = "Arrows to navigate, Enter to select",
featuredModels,
}) => {
const featuredModels = getAllFeaturedModels()
const models = featuredModels
return (
<Box flexDirection="column">
@@ -35,7 +37,7 @@ export const FeaturedModelPicker: React.FC<FeaturedModelPickerProps> = ({
</Text>
)}
{featuredModels.map((model, i) => {
{models.map((model, i) => {
const isSelected = i === selectedIndex
return (
@@ -64,8 +66,8 @@ export const FeaturedModelPicker: React.FC<FeaturedModelPickerProps> = ({
{showBrowseAll && (
<Box>
<Text color={selectedIndex === featuredModels.length ? COLORS.primaryBlue : "white"}>
{selectedIndex === featuredModels.length ? " " : " "}
<Text color={selectedIndex === models.length ? COLORS.primaryBlue : "white"}>
{selectedIndex === models.length ? " " : " "}
Browse all models...
</Text>
</Box>
@@ -81,24 +83,21 @@ export const FeaturedModelPicker: React.FC<FeaturedModelPickerProps> = ({
* Get the maximum valid index for the featured model picker
* (includes "Browse all" option if showBrowseAll is true)
*/
export function getFeaturedModelMaxIndex(showBrowseAll = true): number {
const featuredModels = getAllFeaturedModels()
export function getFeaturedModelMaxIndex(featuredModels: FeaturedModel[], showBrowseAll = true): number {
return showBrowseAll ? featuredModels.length : featuredModels.length - 1
}
/**
* Check if the selected index is the "Browse all" option
*/
export function isBrowseAllSelected(selectedIndex: number): boolean {
const featuredModels = getAllFeaturedModels()
export function isBrowseAllSelected(selectedIndex: number, featuredModels: FeaturedModel[]): boolean {
return selectedIndex === featuredModels.length
}
/**
* Get the featured model at the given index, or null if "Browse all" is selected
*/
export function getFeaturedModelAtIndex(index: number): FeaturedModel | null {
const featuredModels = getAllFeaturedModels()
export function getFeaturedModelAtIndex(index: number, featuredModels: FeaturedModel[]): FeaturedModel | null {
if (index >= 0 && index < featuredModels.length) {
return featuredModels[index]
}
+6 -3
View File
@@ -25,6 +25,7 @@ import { supportsReasoningEffortForModel } from "@/utils/model-utils"
import { version as CLI_VERSION } from "../../package.json"
import { COLORS } from "../constants/colors"
import { useStdinContext } from "../context/StdinContext"
import { useClineFeaturedModels } from "../hooks/useClineFeaturedModels"
import { useOcaAuth } from "../hooks/useOcaAuth"
import { isMouseEscapeSequence } from "../utils/input"
import { applyBedrockConfig, applyProviderConfig } from "../utils/provider-config"
@@ -161,6 +162,7 @@ export const SettingsPanelContent: React.FC<SettingsPanelContentProps> = ({
)
const [isPickingFeaturedModel, setIsPickingFeaturedModel] = useState(initialMode === "featured-models")
const [featuredModelIndex, setFeaturedModelIndex] = useState(0)
const featuredModels = useClineFeaturedModels()
const [isPickingProvider, setIsPickingProvider] = useState(false)
const [isPickingLanguage, setIsPickingLanguage] = useState(false)
const [isEnteringApiKey, setIsEnteringApiKey] = useState(false)
@@ -1292,7 +1294,7 @@ export const SettingsPanelContent: React.FC<SettingsPanelContentProps> = ({
// Featured model picker mode (Cline provider)
if (isPickingFeaturedModel) {
const maxIndex = getFeaturedModelMaxIndex()
const maxIndex = getFeaturedModelMaxIndex(featuredModels)
if (key.escape) {
setIsPickingFeaturedModel(false)
@@ -1306,12 +1308,12 @@ export const SettingsPanelContent: React.FC<SettingsPanelContentProps> = ({
} else if (key.downArrow) {
setFeaturedModelIndex((prev) => (prev < maxIndex ? prev + 1 : 0))
} else if (key.return) {
if (isBrowseAllSelected(featuredModelIndex)) {
if (isBrowseAllSelected(featuredModelIndex, featuredModels)) {
// Switch to full ModelPicker
setIsPickingFeaturedModel(false)
setIsPickingModel(true)
} else {
const selectedModel = getFeaturedModelAtIndex(featuredModelIndex)
const selectedModel = getFeaturedModelAtIndex(featuredModelIndex, featuredModels)
if (selectedModel && pickingModelKey) {
handleModelSelect(selectedModel.id)
setIsPickingFeaturedModel(false)
@@ -1522,6 +1524,7 @@ export const SettingsPanelContent: React.FC<SettingsPanelContentProps> = ({
const label = pickingModelKey === "actModelId" ? "Model ID (Act)" : "Model ID (Plan)"
return (
<FeaturedModelPicker
featuredModels={featuredModels}
helpText="Arrows to navigate, Enter to select, Esc to cancel"
selectedIndex={featuredModelIndex}
title={`Select: ${label}`}
+12 -1
View File
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest"
import { getAllFeaturedModels } from "./featured-models"
import { getAllFeaturedModels, mapRecommendedModelsToFeaturedModels } from "./featured-models"
describe("featured models", () => {
it("includes display names for all featured models", () => {
@@ -9,4 +9,15 @@ describe("featured models", () => {
expect(model.name).toBeTruthy()
}
})
it("fills free model metadata from fallback when upstream payload is sparse", () => {
const models = mapRecommendedModelsToFeaturedModels({
recommended: [],
free: [{ id: "trinity-large-preview:free", name: "trinity-large-preview:free", description: "", tags: [] }],
})
expect(models.free[0]?.name).toBe("Arcee AI Trinity Large Preview")
expect(models.free[0]?.description).toBe("Arcee AI's advanced large preview model in the Trinity series")
expect(models.free[0]?.labels).toContain("FREE")
})
})
+76 -55
View File
@@ -2,6 +2,7 @@
* Featured models shown in the Cline model picker during onboarding
* These are curated models that work well with Cline
*/
import { CLINE_RECOMMENDED_MODELS_FALLBACK } from "@shared/cline/recommended-models"
export interface FeaturedModel {
id: string
@@ -10,61 +11,81 @@ export interface FeaturedModel {
labels: string[]
}
export const FEATURED_MODELS: { recommended: FeaturedModel[]; free: FeaturedModel[] } = {
recommended: [
{
id: "google/gemini-3.1-pro-preview",
name: "Gemini 3.1 Pro Preview",
description: "Latest Gemini release with 1m ctx window and strong coding performance",
labels: ["NEW"],
},
{
id: "anthropic/claude-sonnet-4.6",
name: "Claude Sonnet 4.6",
description: "Latest Sonnet release with strong coding and agent performance",
labels: ["NEW"],
},
{
id: "anthropic/claude-opus-4.6",
name: "Claude Opus 4.6",
description: "Most intelligent model for agents and coding",
labels: ["BEST"],
},
{
id: "openai/gpt-5.2-codex",
name: "GPT 5.2 Codex",
description: "OpenAI's latest with strong coding abilities",
labels: ["HOT"],
},
],
free: [
{
id: "minimax/minimax-m2.5",
name: "MiniMax M2.5",
description: "MiniMax-M2.5 is a lightweight, state-of-the-art LLM optimized for coding and agentic workflows",
labels: ["FREE"],
},
{
id: "z-ai/glm-5",
name: "Z-AI GLM5",
description: "Z.AI's latest GLM 5 model with strong coding and agent performance",
labels: ["FREE"],
},
{
id: "kwaipilot/kat-coder-pro",
name: "KAT Coder Pro",
description: "KwaiKAT's most advanced agentic coding model in the KAT-Coder series",
labels: ["FREE"],
},
{
id: "arcee-ai/trinity-large-preview:free",
name: "Trinity Large Preview",
description: "Arcee AI's advanced large preview model in the Trinity series",
labels: ["FREE"],
},
],
type RecommendedModelLike = {
id: string
name: string
description: string
tags: string[]
}
export function getAllFeaturedModels(): FeaturedModel[] {
return [...FEATURED_MODELS.recommended, ...FEATURED_MODELS.free]
export interface FeaturedModelsByTier {
recommended: FeaturedModel[]
free: FeaturedModel[]
}
interface RecommendedModelsByTier {
recommended: RecommendedModelLike[]
free: RecommendedModelLike[]
}
function toFeaturedModel(model: RecommendedModelLike): FeaturedModel {
return {
id: model.id,
name: model.name,
description: model.description,
labels: model.tags,
}
}
function getModelIdSuffix(id: string): string {
const lastSlashIndex = id.lastIndexOf("/")
return lastSlashIndex >= 0 ? id.slice(lastSlashIndex + 1) : id
}
function findFallbackFeaturedModelById(models: FeaturedModel[], id: string): FeaturedModel | undefined {
const idSuffix = getModelIdSuffix(id)
return models.find((model) => model.id === id || getModelIdSuffix(model.id) === idSuffix)
}
function mapRecommendedModelToFeaturedModelWithFallback(
model: RecommendedModelLike,
fallbackModels: FeaturedModel[],
defaultLabels: string[] = [],
): FeaturedModel {
const fallbackModel = findFallbackFeaturedModelById(fallbackModels, model.id)
const upstreamNameLooksLikeFallback = model.name === model.id || model.name.trim().length === 0
const name = upstreamNameLooksLikeFallback ? (fallbackModel?.name ?? model.name) : model.name
const description = model.description.trim().length > 0 ? model.description : (fallbackModel?.description ?? "")
const labels = model.tags.length > 0 ? model.tags : (fallbackModel?.labels ?? defaultLabels)
return {
id: model.id,
name,
description,
labels,
}
}
export const FEATURED_MODELS: FeaturedModelsByTier = {
recommended: CLINE_RECOMMENDED_MODELS_FALLBACK.recommended.map(toFeaturedModel),
free: CLINE_RECOMMENDED_MODELS_FALLBACK.free.map(toFeaturedModel),
}
export function getAllFeaturedModels(modelsByTier: FeaturedModelsByTier = FEATURED_MODELS): FeaturedModel[] {
return [...modelsByTier.recommended, ...modelsByTier.free]
}
export function mapRecommendedModelsToFeaturedModels(data: RecommendedModelsByTier): FeaturedModelsByTier {
return {
recommended: data.recommended.map((model) =>
mapRecommendedModelToFeaturedModelWithFallback(model, FEATURED_MODELS.recommended),
),
free: data.free.map((model) => mapRecommendedModelToFeaturedModelWithFallback(model, FEATURED_MODELS.free, ["FREE"])),
}
}
export function withFeaturedModelFallback(modelsByTier: FeaturedModelsByTier): FeaturedModelsByTier {
const recommended = modelsByTier.recommended.length > 0 ? modelsByTier.recommended : FEATURED_MODELS.recommended
const free = modelsByTier.free.length > 0 ? modelsByTier.free : FEATURED_MODELS.free
return { recommended, free }
}
+71
View File
@@ -0,0 +1,71 @@
/**
* Cline Library Exports
*
* This file exports the public API for programmatic use of Cline.
* Use these classes and types to embed Cline into your applications.
*
* @example
* ```typescript
* import { ClineAgent } from "cline"
*
* const agent = new ClineAgent()
* await agent.initialize({ clientCapabilities: {} })
* const session = await agent.newSession({ cwd: process.cwd() })
* ```
* @module cline
*/
export { ClineAgent } from "./agent/ClineAgent.js"
export { ClineSessionEmitter } from "./agent/ClineSessionEmitter.js"
export type {
AcpAgentOptions,
AcpSessionState,
AcpSessionStatus,
Agent,
AgentSideConnection,
AudioContent,
CancelNotification,
ClientCapabilities,
ClineAcpSession,
ClineAgentCapabilities,
ClineAgentInfo,
ClineAgentOptions,
ClinePermissionOption,
ClineSessionEvents,
ContentBlock,
ImageContent,
InitializeRequest,
InitializeResponse,
LoadSessionRequest,
LoadSessionResponse,
McpServer,
ModelInfo,
NewSessionRequest,
NewSessionResponse,
PermissionHandler,
PermissionOption,
PermissionOptionKind,
PromptRequest,
PromptResponse,
RequestPermissionRequest,
RequestPermissionResponse,
SessionConfigOption,
SessionModelState,
SessionNotification,
SessionUpdate,
SessionUpdatePayload,
SessionUpdateType,
SetSessionConfigOptionRequest,
SetSessionConfigOptionResponse,
SetSessionModelRequest,
SetSessionModelResponse,
SetSessionModeRequest,
SetSessionModeResponse,
StopReason,
TextContent,
ToolCall,
ToolCallStatus,
ToolCallUpdate,
ToolKind,
TranslatedMessage,
} from "./agent/public-types.js"
+34
View File
@@ -0,0 +1,34 @@
import { useEffect, useState } from "react"
import { refreshClineRecommendedModels } from "@/core/controller/models/refreshClineRecommendedModels"
import {
type FeaturedModel,
getAllFeaturedModels,
mapRecommendedModelsToFeaturedModels,
withFeaturedModelFallback,
} from "../constants/featured-models"
export function useClineFeaturedModels(): FeaturedModel[] {
const [featuredModels, setFeaturedModels] = useState<FeaturedModel[]>(() => getAllFeaturedModels())
useEffect(() => {
let cancelled = false
void (async () => {
try {
const recommendedModels = await refreshClineRecommendedModels()
const mappedModels = mapRecommendedModelsToFeaturedModels(recommendedModels)
const modelsWithFallback = withFeaturedModelFallback(mappedModels)
if (!cancelled) {
setFeaturedModels(getAllFeaturedModels(modelsWithFallback))
}
} catch {
// Keep local fallback models on error.
}
})()
return () => {
cancelled = true
}
}, [])
return featuredModels
}
+25 -3
View File
@@ -26,6 +26,9 @@ import { useCallback, useEffect, useRef, useState } from "react"
* to unmount and remount everything from scratch. This resets Ink's internal tracking
* AND re-renders Static content since the components are brand new instances.
*
* We only run this full recovery when terminal width changes. Height-only resizes do not
* affect wrapping in the same way and should not restart the task view.
*
* Gemini CLI does the same thing in AppContainer.tsx: debounce 300ms, then
* stdout.write(ansiEscapes.clearTerminal) + setHistoryRemountKey(prev => prev + 1).
*
@@ -41,6 +44,8 @@ export function useTerminalSize() {
})
const [resizeKey, setResizeKey] = useState(0)
const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null)
const previousColumnsRef = useRef(process.stdout.columns || 80)
const pendingWidthRefreshRef = useRef(false)
const refreshAfterResize = useCallback(() => {
// Clear terminal + scrollback to wipe stale content from old width
@@ -56,17 +61,33 @@ export function useTerminalSize() {
useEffect(() => {
function updateSize() {
const nextColumns = process.stdout.columns || 80
const nextRows = process.stdout.rows || 24
const didWidthChange = nextColumns !== previousColumnsRef.current
previousColumnsRef.current = nextColumns
setSize({
columns: process.stdout.columns || 80,
rows: process.stdout.rows || 24,
columns: nextColumns,
rows: nextRows,
})
if (didWidthChange) {
pendingWidthRefreshRef.current = true
}
if (!pendingWidthRefreshRef.current) {
return
}
// Debounce: wait 300ms after last resize event to do full recovery
if (debounceRef.current) {
clearTimeout(debounceRef.current)
}
debounceRef.current = setTimeout(() => {
refreshAfterResize()
if (pendingWidthRefreshRef.current) {
refreshAfterResize()
pendingWidthRefreshRef.current = false
}
debounceRef.current = null
}, 300)
}
@@ -76,6 +97,7 @@ export function useTerminalSize() {
if (debounceRef.current) {
clearTimeout(debounceRef.current)
}
pendingWidthRefreshRef.current = false
}
}, [refreshAfterResize])
+144 -1
View File
@@ -1,5 +1,6 @@
import { Command } from "commander"
import { beforeEach, describe, expect, it } from "vitest"
import { beforeEach, describe, expect, it, vi } from "vitest"
import { captureUnhandledException } from "."
/**
* Tests for CLI command parsing and structure
@@ -25,6 +26,7 @@ describe("CLI Commands", () => {
.option("-a, --act", "Run in act mode")
.option("-p, --plan", "Run in plan mode")
.option("-y, --yolo", "Enable yolo mode")
.option("--auto-approve-all", "Enable auto-approve all")
.option("-m, --model <model>", "Model to use")
.option("-i, --images <paths...>", "Image file paths")
.option("-v, --verbose", "Show verbose output")
@@ -33,6 +35,9 @@ describe("CLI Commands", () => {
.option("--thinking [tokens]", "Enable extended thinking")
.option("--reasoning-effort <effort>", "Reasoning effort")
.option("--max-consecutive-mistakes <count>", "Maximum consecutive mistakes")
.option("--double-check-completion", "Reject first completion attempt to force re-verification")
.option("--auto-condense", "Enable AI-powered context compaction instead of mechanical truncation")
.option("--hooks-dir <path>", "Additional hooks directory")
.action(() => {})
program
@@ -62,6 +67,22 @@ describe("CLI Commands", () => {
.option("--config <path>", "Configuration directory")
.action(() => {})
const mcpCommand = program.command("mcp").description("Manage MCP servers")
mcpCommand
.command("add")
.description("Add an MCP server shortcut")
.argument("<name>", "MCP server name")
.argument("[targetOrCommand...]", "Command args for stdio, or URL for remote")
.option("--type <type>", "Transport type", "stdio")
.option("-c, --cwd <path>", "Working directory")
.option("--config <path>", "Configuration directory")
.action(() => {})
program
.command("kanban")
.description("Run npx kanban --agent cline")
.action(() => {})
// Default command for interactive mode
program
.argument("[prompt]", "Task prompt")
@@ -72,6 +93,11 @@ describe("CLI Commands", () => {
.option("--thinking [tokens]", "Enable extended thinking")
.option("--reasoning-effort <effort>", "Reasoning effort")
.option("--max-consecutive-mistakes <count>", "Maximum consecutive mistakes")
.option("--double-check-completion", "Reject first completion attempt to force re-verification")
.option("--auto-condense", "Enable AI-powered context compaction instead of mechanical truncation")
.option("--hooks-dir <path>", "Additional hooks directory")
.option("--auto-approve-all", "Enable auto-approve all")
.option("--kanban", "Run npx kanban --agent cline")
.action(() => {})
})
@@ -108,6 +134,13 @@ describe("CLI Commands", () => {
expect(taskCmd.opts().yolo).toBe(true)
})
it("should parse --auto-approve-all flag", () => {
const taskCmd = program.commands.find((c) => c.name() === "task")!
const args = ["test prompt", "--auto-approve-all"]
taskCmd.parse(args, { from: "user" })
expect(taskCmd.opts().autoApproveAll).toBe(true)
})
it("should parse --model option", () => {
const taskCmd = program.commands.find((c) => c.name() === "task")!
const args = ["test prompt", "--model", "claude-sonnet-4-20250514"]
@@ -171,6 +204,27 @@ describe("CLI Commands", () => {
expect(taskCmd.opts().maxConsecutiveMistakes).toBe("999")
})
it("should parse --hooks-dir option", () => {
const taskCmd = program.commands.find((c) => c.name() === "task")!
const args = ["test prompt", "--hooks-dir", "/tmp/hooks"]
taskCmd.parse(args, { from: "user" })
expect(taskCmd.opts().hooksDir).toBe("/tmp/hooks")
})
it("should parse --double-check-completion flag", () => {
const taskCmd = program.commands.find((c) => c.name() === "task")!
const args = ["test prompt", "--double-check-completion"]
taskCmd.parse(args, { from: "user" })
expect(taskCmd.opts().doubleCheckCompletion).toBe(true)
})
it("should parse --auto-condense flag", () => {
const taskCmd = program.commands.find((c) => c.name() === "task")!
const args = ["test prompt", "--auto-condense"]
taskCmd.parse(args, { from: "user" })
expect(taskCmd.opts().autoCondense).toBe(true)
})
it("should parse short flags", () => {
const taskCmd = program.commands.find((c) => c.name() === "task")!
const args = ["test prompt", "-a", "-v", "-m", "gpt-4"]
@@ -237,6 +291,13 @@ describe("CLI Commands", () => {
})
})
describe("kanban command", () => {
it("should parse kanban command", () => {
const args = ["node", "cli", "kanban"]
program.parse(args)
})
})
describe("auth command", () => {
it("should parse auth command", () => {
const args = ["node", "cli", "auth"]
@@ -281,6 +342,32 @@ describe("CLI Commands", () => {
})
})
describe("mcp command", () => {
it("should parse mcp add stdio syntax", () => {
const args = ["node", "cli", "mcp", "add", "kanban", "--", "kanban", "mcp"]
program.parse(args)
})
it("should parse mcp add remote http syntax", () => {
const args = ["node", "cli", "mcp", "add", "linear", "https://mcp.linear.app/mcp", "--type", "http"]
program.parse(args)
})
it("should default mcp add type to stdio", () => {
const mcpCmd = program.commands.find((c) => c.name() === "mcp")!
const addCmd = mcpCmd.commands.find((c) => c.name() === "add")!
addCmd.parse(["kanban", "--", "kanban", "mcp"], { from: "user" })
expect(addCmd.opts().type).toBe("stdio")
})
it("should parse mcp add type option", () => {
const mcpCmd = program.commands.find((c) => c.name() === "mcp")!
const addCmd = mcpCmd.commands.find((c) => c.name() === "add")!
addCmd.parse(["linear", "https://mcp.linear.app/mcp", "--type", "http"], { from: "user" })
expect(addCmd.opts().type).toBe("http")
})
})
describe("default command (interactive mode)", () => {
it("should parse optional prompt argument", () => {
const args = ["node", "cli", "do something"]
@@ -321,6 +408,21 @@ describe("CLI Commands", () => {
program.parse(["node", "cli", "--max-consecutive-mistakes", "7"])
expect(program.opts().maxConsecutiveMistakes).toBe("7")
})
it("should parse --hooks-dir option", () => {
program.parse(["node", "cli", "--hooks-dir", "/tmp/hooks"])
expect(program.opts().hooksDir).toBe("/tmp/hooks")
})
it("should parse --auto-approve-all flag", () => {
program.parse(["node", "cli", "--auto-approve-all"])
expect(program.opts().autoApproveAll).toBe(true)
})
it("should parse --kanban flag", () => {
program.parse(["node", "cli", "--kanban"])
expect(program.opts().kanban).toBe(true)
})
})
describe("command structure", () => {
@@ -330,6 +432,8 @@ describe("CLI Commands", () => {
expect(commandNames).toContain("history")
expect(commandNames).toContain("config")
expect(commandNames).toContain("auth")
expect(commandNames).toContain("mcp")
expect(commandNames).toContain("kanban")
})
it("should have correct aliases", () => {
@@ -410,3 +514,42 @@ describe("getProviderModelIdKey", () => {
expect(getProviderModelIdKey("unknown-provider", "act")).toBeNull()
})
})
const mockCaptureException = vi.fn().mockResolvedValue(undefined)
const mockDispose = vi.fn().mockResolvedValue(undefined)
vi.mock("@/services/error/ErrorService", () => {
return {
ErrorService: {
get: () => ({
captureException: mockCaptureException,
dispose: mockDispose,
}),
},
}
})
describe("captureUnhandledException", () => {
beforeEach(() => {
vi.resetAllMocks()
})
it("captures unhandled exceptions", async () => {
const testError = new Error("Test unhandled exception")
await captureUnhandledException(testError, "unhandledRejection")
expect(mockCaptureException).toHaveBeenCalledWith(testError, { context: "unhandledRejection" })
expect(mockDispose).toHaveBeenCalled()
})
it("does not throw if captureException fails", async () => {
mockCaptureException.mockRejectedValueOnce(new Error("Capture failed"))
const testError = new Error("Test unhandled exception")
await expect(captureUnhandledException(testError, "unhandledRejection")).resolves.not.toThrow()
expect(mockCaptureException).toHaveBeenCalledWith(testError, { context: "unhandledRejection" })
expect(mockDispose).not.toHaveBeenCalled()
})
})
+212 -93
View File
@@ -2,6 +2,7 @@
* Cline CLI - TypeScript implementation with React Ink
*/
import { spawn } from "node:child_process"
import { exit } from "node:process"
import type { ApiProvider } from "@shared/api"
import { Command } from "commander"
@@ -9,6 +10,8 @@ import { render } from "ink"
import React from "react"
import { ClineEndpoint } from "@/config"
import type { Controller } from "@/core/controller"
import { getHooksEnabledSafe } from "@/core/hooks/hooks-utils"
import { setRuntimeHooksDir } from "@/core/storage/disk"
import { StateManager } from "@/core/storage/StateManager"
import { AuthHandler } from "@/hosts/external/AuthHandler"
import { HostProvider } from "@/hosts/host-provider"
@@ -20,7 +23,7 @@ import { PostHogClientProvider } from "@/services/telemetry/providers/posthog/Po
import { HistoryItem } from "@/shared/HistoryItem"
import { Logger } from "@/shared/services/Logger"
import { Session } from "@/shared/services/Session"
import { getProviderModelIdKey, ProviderToApiKeyMap } from "@/shared/storage"
import { getProviderModelIdKey } from "@/shared/storage"
import { isOpenaiReasoningEffort, OPENAI_REASONING_EFFORT_OPTIONS, type OpenaiReasoningEffort } from "@/shared/storage/types"
import { version as CLI_VERSION } from "../package.json"
import { runAcpMode } from "./acp/index.js"
@@ -29,8 +32,10 @@ import { checkRawModeSupport } from "./context/StdinContext"
import { createCliHostBridgeProvider } from "./controllers"
import { CliCommentReviewController } from "./controllers/CliCommentReviewController"
import { CliWebviewProvider } from "./controllers/CliWebviewProvider"
import { restoreConsole } from "./utils/console"
import { isAuthConfigured } from "./utils/auth"
import { restoreConsole, suppressConsoleUnlessVerbose } from "./utils/console"
import { printInfo, printWarning } from "./utils/display"
import { addMcpServerShortcut, type McpAddOptions } from "./utils/mcp"
import { selectOutputMode } from "./utils/mode-selection"
import { parseImagesFromInput, processImagePaths } from "./utils/parser"
import { CLINE_CLI_DIR, getCliBinaryPath } from "./utils/path"
@@ -38,28 +43,38 @@ import { readStdinIfPiped } from "./utils/piped"
import { runPlainTextTask } from "./utils/plain-text-task"
import { applyProviderConfig } from "./utils/provider-config"
import { getValidCliProviders, isValidCliProvider } from "./utils/providers"
import { findMostRecentTaskForWorkspace } from "./utils/task-history"
import { autoUpdateOnStartup, checkForUpdates } from "./utils/update"
import { initializeCliContext } from "./vscode-context"
import { CLI_LOG_FILE, shutdownEvent, window } from "./vscode-shim"
// CLI-only behavior: suppress console output unless verbose mode is enabled.
// Kept explicit here so importing the library bundle does not mutate global console methods.
suppressConsoleUnlessVerbose()
/**
* Common options shared between runTask and resumeTask
*/
interface TaskOptions {
act?: boolean
plan?: boolean
kanban?: boolean
model?: string
verbose?: boolean
cwd?: string
continue?: boolean
config?: string
thinking?: boolean | string
reasoningEffort?: string
maxConsecutiveMistakes?: string
yolo?: boolean
autoApproveAll?: boolean
doubleCheckCompletion?: boolean
autoCondense?: boolean
timeout?: string
json?: boolean
stdinWasPiped?: boolean
hooksDir?: string
}
let telemetryDisposed = false
@@ -128,46 +143,43 @@ function normalizeMaxConsecutiveMistakes(value?: string): number | undefined {
function applyTaskOptions(options: TaskOptions): void {
// Apply mode flag
if (options.plan) {
StateManager.get().setGlobalState("mode", "plan")
StateManager.get().setSessionOverride("mode", "plan")
telemetryService.captureHostEvent("mode_flag", "plan")
} else if (options.act) {
StateManager.get().setGlobalState("mode", "act")
StateManager.get().setSessionOverride("mode", "act")
telemetryService.captureHostEvent("mode_flag", "act")
}
// Apply model override if specified
if (options.model) {
const selectedMode = (StateManager.get().getGlobalSettingsKey("mode") || "act") as "act" | "plan"
const selectedMode = (StateManager.get().getGlobalSettingsKey("mode") ?? "act") as "act" | "plan"
const providerKey = selectedMode === "act" ? "actModeApiProvider" : "planModeApiProvider"
const currentProvider = StateManager.get().getGlobalSettingsKey(providerKey) as ApiProvider
const modelKey = getProviderModelIdKey(currentProvider, selectedMode)
if (modelKey) {
StateManager.get().setGlobalState(modelKey, options.model)
StateManager.get().setSessionOverride(modelKey, options.model)
}
telemetryService.captureHostEvent("model_flag", options.model)
}
const currentMode = (StateManager.get().getGlobalSettingsKey("mode") || "act") as "act" | "plan"
// Set thinking budget based on --thinking flag (boolean or number)
let thinkingBudget = 0
if (options.thinking) {
if (options.thinking !== undefined) {
let thinkingBudget = 1024
if (typeof options.thinking === "string") {
const parsed = Number.parseInt(options.thinking, 10)
if (Number.isNaN(parsed) || parsed < 0) {
printWarning(`Invalid --thinking value '${options.thinking}'. Using default 1024.`)
thinkingBudget = 1024
} else {
thinkingBudget = parsed
}
} else {
thinkingBudget = 1024
}
}
const currentMode = (StateManager.get().getGlobalSettingsKey("mode") || "act") as "act" | "plan"
setModeScopedState(currentMode, (mode) => {
const thinkingKey = mode === "act" ? "actModeThinkingBudgetTokens" : "planModeThinkingBudgetTokens"
StateManager.get().setGlobalState(thinkingKey, thinkingBudget)
})
if (options.thinking) {
setModeScopedState(currentMode, (mode) => {
const thinkingKey = mode === "act" ? "actModeThinkingBudgetTokens" : "planModeThinkingBudgetTokens"
StateManager.get().setSessionOverride(thinkingKey, thinkingBudget)
})
telemetryService.captureHostEvent("thinking_flag", "true")
}
@@ -175,14 +187,14 @@ function applyTaskOptions(options: TaskOptions): void {
if (reasoningEffort !== undefined) {
setModeScopedState(currentMode, (mode) => {
const reasoningKey = mode === "act" ? "actModeReasoningEffort" : "planModeReasoningEffort"
StateManager.get().setGlobalState(reasoningKey, reasoningEffort)
StateManager.get().setSessionOverride(reasoningKey, reasoningEffort)
})
telemetryService.captureHostEvent("reasoning_effort_flag", reasoningEffort)
}
const maxConsecutiveMistakes = normalizeMaxConsecutiveMistakes(options.maxConsecutiveMistakes)
if (maxConsecutiveMistakes !== undefined) {
StateManager.get().setGlobalState("maxConsecutiveMistakes", maxConsecutiveMistakes)
StateManager.get().setSessionOverride("maxConsecutiveMistakes", maxConsecutiveMistakes)
telemetryService.captureHostEvent("max_consecutive_mistakes_flag", String(maxConsecutiveMistakes))
}
@@ -193,11 +205,22 @@ function applyTaskOptions(options: TaskOptions): void {
telemetryService.captureHostEvent("yolo_flag", "true")
}
// Set auto-approve-all as a session-scoped override so CLI flag does not
// persist user settings to disk.
if (options.autoApproveAll) {
StateManager.get().setSessionOverride("autoApproveAllToggled", true)
telemetryService.captureHostEvent("auto_approve_all_flag", "true")
}
// Set double-check completion based on flag
if (options.doubleCheckCompletion) {
StateManager.get().setGlobalState("doubleCheckCompletionEnabled", true)
StateManager.get().setSessionOverride("doubleCheckCompletionEnabled", true)
telemetryService.captureHostEvent("double_check_completion_flag", "true")
}
if (options.autoCondense) {
StateManager.get().setSessionOverride("useAutoCondense", true)
}
}
/**
@@ -228,6 +251,36 @@ function getPlainTextModeReason(options: TaskOptions): string {
return getModeSelection(options).reason
}
function getNpxCommand(): string {
return process.platform === "win32" ? "npx.cmd" : "npx"
}
function runKanbanAlias(): void {
const child = spawn(getNpxCommand(), ["kanban", "--agent", "cline"], {
stdio: "inherit",
})
child.on("error", () => {
printWarning("Failed to run 'npx kanban --agent cline'. Make sure npx is installed and available in PATH.")
exit(1)
})
child.on("close", (code) => {
exit(code ?? 1)
})
}
async function addMcpServer(name: string, targetOrCommand: string[] = [], options: McpAddOptions): Promise<void> {
try {
const result = await addMcpServerShortcut(name, targetOrCommand, options)
const transportLabel = result.transportType === "streamableHttp" ? "http" : result.transportType
printInfo(`Added MCP server '${result.serverName}' (${transportLabel}) to ${result.settingsPath}`)
} catch (error) {
printWarning(error instanceof Error ? error.message : "Failed to add MCP server.")
exit(1)
}
}
/**
* Run a task in plain text mode (no Ink UI).
* Handles auth check, task execution, cleanup, and exit.
@@ -311,6 +364,42 @@ async function drainStdout(): Promise<void> {
})
}
export async function captureUnhandledException(reason: Error, context: string) {
try {
// ErrorService may not be initialized yet (e.g., error occurred before initializeCli())
// so we guard with a try/get pattern rather than letting ErrorService.get() throw
let errorService: ErrorService | null = null
try {
errorService = ErrorService.get()
} catch {
// ErrorService not yet initialized; skip capture
}
if (errorService) {
await errorService.captureException(reason, { context })
// dispose flushes any pending error captures to ensure they're sent before the process exits
return errorService.dispose()
}
} catch {
// Ignore errors during shutdown to avoid an infinite loop
Logger.info("Error capturing unhandled exception. Proceeding with shutdown.")
}
}
const EXIT_TIMEOUT_MS = 3000
function onUnhandledException(reason: unknown, context: string) {
Logger.error("Unhandled exception:", reason)
const finalError = reason instanceof Error ? reason : new Error(String(reason))
restoreConsole()
console.error(finalError)
setTimeout(() => process.exit(1), EXIT_TIMEOUT_MS)
captureUnhandledException(finalError, context).finally(() => {
process.exit(1)
})
}
function setupSignalHandlers() {
const shutdown = async (signal: string) => {
if (isShuttingDown) {
@@ -348,7 +437,11 @@ function setupSignalHandlers() {
} catch {
// StateManager may not be initialized yet
}
await ErrorService.get().dispose()
try {
await ErrorService.get().dispose()
} catch {
// ErrorService may not be initialized yet
}
await disposeTelemetryServices()
}
} catch {
@@ -370,9 +463,14 @@ function setupSignalHandlers() {
Logger.info("Suppressed unhandled rejection due to abort:", message)
return
}
// For other unhandled rejections, log to file via Logger (if available)
// For other unhandled rejections, capture the exception and log to file via Logger (if available)
// This won't show in terminal but will be in log files for debugging
Logger.error("Unhandled rejection:", reason)
onUnhandledException(reason, "unhandledRejection")
})
process.on("uncaughtException", (reason: unknown) => {
onUnhandledException(reason, "uncaughtException")
})
}
@@ -389,6 +487,7 @@ interface CliContext {
interface InitOptions {
config?: string
cwd?: string
hooksDir?: string
verbose?: boolean
enableAuth?: boolean
}
@@ -398,6 +497,7 @@ interface InitOptions {
*/
async function initializeCli(options: InitOptions): Promise<CliContext> {
const workspacePath = options.cwd || process.cwd()
setRuntimeHooksDir(options.hooksDir)
const { extensionContext, storageContext, DATA_DIR, EXTENSION_DIR } = initializeCliContext({
clineDir: options.config,
workspaceDir: workspacePath,
@@ -499,6 +599,11 @@ async function runTask(prompt: string, options: TaskOptions & { images?: string[
// Task without prompt starts in interactive mode
telemetryService.captureHostEvent("task_command", prompt ? "task" : "interactive")
// Capture piped stdin telemetry now that HostProvider is initialized
if (options.stdinWasPiped) {
telemetryService.captureHostEvent("piped", "detached")
}
// Apply shared task options (mode, model, thinking, yolo)
applyTaskOptions(options)
await StateManager.get().flushPendingState()
@@ -591,7 +696,7 @@ async function showConfig(options: { config?: string }) {
dataDir: ctx.dataDir,
globalState: stateManager.getAllGlobalStateEntries(),
workspaceState: stateManager.getAllWorkspaceStateEntries(),
hooksEnabled: true,
hooksEnabled: getHooksEnabledSafe(stateManager.getGlobalSettingsKey("hooksEnabled")),
skillsEnabled: true,
isRawModeSupported: checkRawModeSupport(),
}),
@@ -723,6 +828,7 @@ program
.option("-a, --act", "Run in act mode")
.option("-p, --plan", "Run in plan mode")
.option("-y, --yolo", "Enable yes/yolo mode (auto-approve actions)")
.option("--auto-approve-all", "Enable auto-approve all actions while keeping interactive mode")
.option("-t, --timeout <seconds>", "Optional timeout in seconds (applies only when provided)")
.option("-m, --model <model>", "Model to use for the task")
.option("-v, --verbose", "Show verbose output")
@@ -733,6 +839,8 @@ program
.option("--max-consecutive-mistakes <count>", "Maximum consecutive mistakes before halting in yolo mode")
.option("--json", "Output messages as JSON instead of styled text")
.option("--double-check-completion", "Reject first completion attempt to force re-verification")
.option("--auto-condense", "Enable AI-powered context compaction instead of mechanical truncation")
.option("--hooks-dir <path>", "Path to additional hooks directory for runtime hook injection")
.option("-T, --taskId <id>", "Resume an existing task by ID")
.action((prompt, options) => {
if (options.taskId) {
@@ -768,6 +876,18 @@ program
.option("--config <path>", "Path to Cline configuration directory")
.action(runAuth)
const mcpCommand = program.command("mcp").description("Manage MCP servers")
mcpCommand
.command("add")
.description("Add an MCP server shortcut to cline_mcp_settings.json")
.argument("<name>", "MCP server name")
.argument("[targetOrCommand...]", "For stdio: use -- <command> [args]. For http/sse: provide <url>.")
.option("--type <type>", "Transport type: stdio (default), http, or sse", "stdio")
.option("-c, --cwd <path>", "Working directory for config resolution")
.option("--config <path>", "Path to Cline configuration directory")
.action(addMcpServer)
program
.command("version")
.description("Show Cline CLI version number")
@@ -779,6 +899,8 @@ program
.option("-v, --verbose", "Show verbose output")
.action(() => checkForUpdates(CLI_VERSION))
program.command("kanban").description("Run npx kanban --agent cline").action(runKanbanAlias)
// Dev command with subcommands
const devCommand = program.command("dev").description("Developer tools and utilities")
@@ -790,68 +912,6 @@ devCommand
await openExternal(CLI_LOG_FILE)
})
/**
* Check if the user has completed onboarding (has any provider configured).
*
* Uses `welcomeViewCompleted` as the single source of truth, matching the VS Code extension's approach.
* If `welcomeViewCompleted` is undefined (first run), checks if ANY provider has credentials
* and sets the flag accordingly.
*/
export async function isAuthConfigured(): Promise<boolean> {
const stateManager = StateManager.get()
// Check welcomeViewCompleted first - this is the single source of truth
const welcomeViewCompleted = stateManager.getGlobalStateKey("welcomeViewCompleted")
if (welcomeViewCompleted !== undefined) {
return welcomeViewCompleted
}
// welcomeViewCompleted is undefined - run migration logic to check if ANY provider has credentials
// This mirrors the extension's migrateWelcomeViewCompleted behavior
const hasAnyAuth = await checkAnyProviderConfigured()
// Set welcomeViewCompleted based on what we found
stateManager.setGlobalState("welcomeViewCompleted", hasAnyAuth)
await stateManager.flushPendingState()
return hasAnyAuth
}
/**
* Check if ANY provider has valid credentials configured.
* Used for migration when welcomeViewCompleted is undefined.
*/
async function checkAnyProviderConfigured(): Promise<boolean> {
const stateManager = StateManager.get()
const config = stateManager.getApiConfiguration() as Record<string, unknown>
// Check Cline account (stored as "cline:clineAccountId" in secrets, loaded into config)
if (config["clineApiKey"] || config["cline:clineAccountId"]) return true
// Check OpenAI Codex OAuth (stored in SECRETS_KEYS, loaded into config)
if (config["openai-codex-oauth-credentials"]) return true
// Check all BYO provider API keys (loaded into config from secrets)
for (const [provider, keyField] of Object.entries(ProviderToApiKeyMap)) {
// Skip cline - already checked above with the correct key
if (provider === "cline") continue
const fields = Array.isArray(keyField) ? keyField : [keyField]
for (const field of fields) {
if (config[field]) return true
}
}
// Check provider-specific settings that indicate configuration
// (for providers that don't require API keys like Bedrock with IAM, Ollama, LM Studio)
if (config.awsRegion) return true
if (config.vertexProjectId) return true
if (config.ollamaBaseUrl) return true
if (config.lmStudioBaseUrl) return true
return false
}
/**
* Validate that a task exists in history
* @returns The task history item if found, null otherwise
@@ -865,8 +925,8 @@ function findTaskInHistory(taskId: string): HistoryItem | null {
* Resume an existing task by ID
* Loads the task and optionally prefills the input with a prompt
*/
async function resumeTask(taskId: string, options: TaskOptions & { initialPrompt?: string }) {
const ctx = await initializeCli({ ...options, enableAuth: true })
async function resumeTask(taskId: string, options: TaskOptions & { initialPrompt?: string }, existingContext?: CliContext) {
const ctx = existingContext || (await initializeCli({ ...options, enableAuth: true }))
// Validate task exists
const historyItem = findTaskInHistory(taskId)
@@ -879,6 +939,11 @@ async function resumeTask(taskId: string, options: TaskOptions & { initialPrompt
telemetryService.captureHostEvent("resume_task_command", options.initialPrompt ? "with_prompt" : "interactive")
// Capture piped stdin telemetry now that HostProvider is initialized
if (options.stdinWasPiped) {
telemetryService.captureHostEvent("piped", "detached")
}
// Apply shared task options (mode, model, thinking, yolo)
applyTaskOptions(options)
await StateManager.get().flushPendingState()
@@ -913,16 +978,35 @@ async function resumeTask(taskId: string, options: TaskOptions & { initialPrompt
)
}
async function continueTask(options: TaskOptions) {
const ctx = await initializeCli({ ...options, enableAuth: true })
const historyItem = findMostRecentTaskForWorkspace(StateManager.get().getGlobalStateKey("taskHistory"), ctx.workspacePath)
if (!historyItem) {
printWarning(`No previous task found for ${ctx.workspacePath}`)
printInfo("Start a new task or use 'cline history' to browse previous tasks.")
await disposeCliContext(ctx)
exit(1)
}
return resumeTask(historyItem.id, options, ctx)
}
/**
* Show welcome prompt and wait for user input
* If auth is not configured, show auth flow first
*/
async function showWelcome(options: { verbose?: boolean; cwd?: string; config?: string; thinking?: boolean }) {
async function showWelcome(options: TaskOptions) {
const ctx = await initializeCli({ ...options, enableAuth: true })
// Check if auth is configured
const hasAuth = await isAuthConfigured()
// Apply CLI task options in interactive startup too, so flags like
// --auto-approve-all and --yolo affect the initial TUI state.
applyTaskOptions(options)
await StateManager.get().flushPendingState()
let hadError = false
await runInkApp(
@@ -952,6 +1036,7 @@ program
.option("-a, --act", "Run in act mode")
.option("-p, --plan", "Run in plan mode")
.option("-y, --yolo", "Enable yolo mode (auto-approve actions)")
.option("--auto-approve-all", "Enable auto-approve all actions while keeping interactive mode")
.option("-t, --timeout <seconds>", "Optional timeout in seconds (applies only when provided)")
.option("-m, --model <model>", "Model to use for the task")
.option("-v, --verbose", "Show verbose output")
@@ -962,14 +1047,29 @@ program
.option("--max-consecutive-mistakes <count>", "Maximum consecutive mistakes before halting in yolo mode")
.option("--json", "Output messages as JSON instead of styled text")
.option("--double-check-completion", "Reject first completion attempt to force re-verification")
.option("--auto-condense", "Enable AI-powered context compaction instead of mechanical truncation")
.option("--hooks-dir <path>", "Path to additional hooks directory for runtime hook injection")
.option("--acp", "Run in ACP (Agent Client Protocol) mode for editor integration")
.option("--kanban", "Run npx kanban --agent cline")
.option("-T, --taskId <id>", "Resume an existing task by ID")
.option("--continue", "Resume the most recent task from the current working directory")
.action(async (prompt, options) => {
if (options.kanban) {
if (prompt) {
printWarning("Use --kanban without a prompt.")
exit(1)
}
runKanbanAlias()
return
}
// Check for ACP mode first - this takes precedence over everything else
if (options.acp) {
await runAcpMode({
config: options.config,
cwd: options.cwd,
hooksDir: options.hooksDir,
verbose: options.verbose,
})
return
@@ -984,6 +1084,25 @@ program
// stdinInput has content means stdin was piped with data
const stdinWasPiped = stdinInput !== null
if (options.taskId && options.continue) {
printWarning("Use either --taskId or --continue, not both.")
exit(1)
}
if (options.continue) {
if (prompt) {
printWarning("Use --continue without a prompt.")
exit(1)
}
if (stdinWasPiped) {
printWarning("Use --continue without piped input.")
exit(1)
}
await continueTask(options)
return
}
// Error if stdin was piped but empty AND no prompt was provided
// This handles:
// - `echo "" | cline` -> error (empty stdin, no prompt)
@@ -1004,8 +1123,6 @@ program
effectivePrompt = stdinInput
}
telemetryService.captureHostEvent("piped", "detached")
// Debug: show that we received piped input
if (options.verbose) {
process.stderr.write(`[debug] Received ${stdinInput.length} bytes from stdin\n`)
@@ -1032,4 +1149,6 @@ program
})
// Parse and run
program.parse()
if (process.env.VITEST !== "true") {
program.parse()
}
+9
View File
@@ -0,0 +1,9 @@
import { describe, expect, it } from "vitest"
describe("library import side effects", () => {
it("importing library exports must not mutate console.log", async () => {
const originalConsoleLog = console.log
await import("./exports")
expect(console.log).toBe(originalConsoleLog)
}, 30000)
})
+64
View File
@@ -0,0 +1,64 @@
import { StateManager } from "@/core/storage/StateManager"
import { ProviderToApiKeyMap } from "@/shared/storage"
/**
* Check if the user has completed onboarding (has any provider configured).
*
* Uses `welcomeViewCompleted` as the single source of truth, matching the VS Code extension's approach.
* If `welcomeViewCompleted` is undefined (first run), checks if ANY provider has credentials
* and sets the flag accordingly.
*/
export async function isAuthConfigured(): Promise<boolean> {
const stateManager = StateManager.get()
// Check welcomeViewCompleted first - this is the single source of truth
const welcomeViewCompleted = stateManager.getGlobalStateKey("welcomeViewCompleted")
if (welcomeViewCompleted !== undefined) {
return welcomeViewCompleted
}
// welcomeViewCompleted is undefined - run migration logic to check if ANY provider has credentials
// This mirrors the extension's migrateWelcomeViewCompleted behavior
const hasAnyAuth = await checkAnyProviderConfigured()
// Set welcomeViewCompleted based on what we found
stateManager.setGlobalState("welcomeViewCompleted", hasAnyAuth)
await stateManager.flushPendingState()
return hasAnyAuth
}
/**
* Check if ANY provider has valid credentials configured.
* Used for migration when welcomeViewCompleted is undefined.
*/
export async function checkAnyProviderConfigured(): Promise<boolean> {
const stateManager = StateManager.get()
const config = stateManager.getApiConfiguration() as Record<string, unknown>
// Check Cline account (stored as "cline:clineAccountId" in secrets, loaded into config)
if (config["clineApiKey"] || config["cline:clineAccountId"]) return true
// Check OpenAI Codex OAuth (stored in SECRETS_KEYS, loaded into config)
if (config["openai-codex-oauth-credentials"]) return true
// Check all BYO provider API keys (loaded into config from secrets)
for (const [provider, keyField] of Object.entries(ProviderToApiKeyMap)) {
// Skip cline - already checked above with the correct key
if (provider === "cline") continue
const fields = Array.isArray(keyField) ? keyField : [keyField]
for (const field of fields) {
if (config[field]) return true
}
}
// Check provider-specific settings that indicate configuration
// (for providers that don't require API keys like Bedrock with IAM, Ollama, LM Studio)
if (config.awsRegion) return true
if (config.vertexProjectId) return true
if (config.ollamaBaseUrl) return true
if (config.lmStudioBaseUrl) return true
return false
}
+12 -4
View File
@@ -12,11 +12,19 @@ export const originalConsoleWarn = console.warn.bind(console)
export const originalConsoleInfo = console.info.bind(console)
export const originalConsoleDebug = console.debug.bind(console)
// Check for verbose flag early (before commander parses)
const isVerbose = process.argv.includes("-v") || process.argv.includes("--verbose")
/**
* Suppress console output unless verbose mode is enabled.
*
* This is intentionally opt-in and should only be called by the CLI entrypoint.
* Library consumers should not have their global console methods mutated as a
* side effect of importing the library bundle.
*/
export function suppressConsoleUnlessVerbose(argv: string[] = process.argv) {
const isVerbose = argv.includes("-v") || argv.includes("--verbose")
if (isVerbose) {
return
}
// Suppress console output unless verbose mode
if (!isVerbose) {
console.log = () => {}
console.warn = () => {}
console.error = () => {}
+63
View File
@@ -0,0 +1,63 @@
import * as fs from "node:fs/promises"
import os from "node:os"
import path from "node:path"
import { afterEach, describe, expect, it } from "vitest"
import { addMcpServerShortcut } from "./mcp"
const tempDirs: string[] = []
async function createTempConfigDir(): Promise<string> {
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "cline-mcp-test-"))
tempDirs.push(dir)
return dir
}
type McpSettingsFile = {
mcpServers: Record<string, Record<string, unknown>>
}
async function readMcpSettings(configDir: string): Promise<McpSettingsFile> {
const settingsPath = path.join(configDir, "data", "settings", "cline_mcp_settings.json")
return JSON.parse(await fs.readFile(settingsPath, "utf-8")) as McpSettingsFile
}
afterEach(async () => {
for (const dir of tempDirs.splice(0, tempDirs.length)) {
await fs.rm(dir, { recursive: true, force: true })
}
})
describe("addMcpServerShortcut", () => {
it("writes stdio servers with type=stdio", async () => {
const configDir = await createTempConfigDir()
await addMcpServerShortcut("kanban", ["kanban", "mcp"], { config: configDir })
const settings = await readMcpSettings(configDir)
expect(settings.mcpServers.kanban).toEqual({
command: "kanban",
args: ["mcp"],
type: "stdio",
})
})
it("maps --type http to streamableHttp", async () => {
const configDir = await createTempConfigDir()
await addMcpServerShortcut("linear", ["https://mcp.linear.app/mcp"], { config: configDir, type: "http" })
const settings = await readMcpSettings(configDir)
expect(settings.mcpServers.linear).toEqual({
url: "https://mcp.linear.app/mcp",
type: "streamableHttp",
})
})
it("errors when URL is provided without --type http", async () => {
const configDir = await createTempConfigDir()
await expect(addMcpServerShortcut("linear", ["https://mcp.linear.app/mcp"], { config: configDir })).rejects.toThrow(
"Use --type http",
)
})
})
+159
View File
@@ -0,0 +1,159 @@
import * as fs from "node:fs/promises"
import path from "node:path"
import { getMcpSettingsFilePath } from "@/core/storage/disk"
import { ServerConfigSchema } from "@/services/mcp/schemas"
import { initializeCliContext } from "../vscode-context"
export interface McpAddOptions {
type?: string
config?: string
cwd?: string
}
export type McpAddTransportType = "stdio" | "streamableHttp" | "sse"
export interface AddMcpServerResult {
serverName: string
transportType: McpAddTransportType
settingsPath: string
}
function normalizeMcpTransportType(value?: string): McpAddTransportType {
const normalized = (value || "stdio").trim().toLowerCase()
switch (normalized) {
case "stdio":
return "stdio"
case "http":
case "streamable-http":
case "streamablehttp":
return "streamableHttp"
case "sse":
return "sse"
default:
throw new Error(`Invalid MCP transport type '${value}'. Valid values: stdio, http, sse.`)
}
}
function parseMcpSettings(content: string, settingsPath: string): Record<string, unknown> {
const trimmedContent = content.trim()
if (!trimmedContent) {
return { mcpServers: {} }
}
let parsed: unknown
try {
parsed = JSON.parse(content)
} catch {
throw new Error(`Invalid JSON in ${settingsPath}. Please fix the file and try again.`)
}
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
throw new Error(`Invalid MCP settings file at ${settingsPath}. Expected a JSON object.`)
}
const settings = parsed as Record<string, unknown>
if (settings.mcpServers === undefined) {
settings.mcpServers = {}
}
if (!settings.mcpServers || typeof settings.mcpServers !== "object" || Array.isArray(settings.mcpServers)) {
throw new Error(`Invalid MCP settings file at ${settingsPath}. Expected 'mcpServers' to be an object.`)
}
return settings
}
function createMcpServerConfig(targetOrCommand: string[], transportType: McpAddTransportType): Record<string, unknown> {
if (transportType === "stdio") {
if (targetOrCommand.length < 1) {
throw new Error("Missing stdio command. Example: cline mcp add kanban -- kanban mcp")
}
// Guard against common mistake:
// `cline mcp add <name> <url>` without `--type http`
if (targetOrCommand.length === 1) {
const [value] = targetOrCommand
try {
const parsedUrl = new URL(value)
if (parsedUrl.protocol === "http:" || parsedUrl.protocol === "https:") {
throw new Error(
`Looks like you provided a URL for '${value}'. Use --type http, for example: cline mcp add <name> ${value} --type http`,
)
}
} catch (error) {
if (error instanceof Error && error.message.startsWith("Looks like you provided a URL")) {
throw error
}
}
}
const [command, ...args] = targetOrCommand
const config: Record<string, unknown> = {
command,
type: "stdio",
}
if (args.length > 0) {
config.args = args
}
ServerConfigSchema.parse(config)
return config
}
if (targetOrCommand.length !== 1) {
throw new Error(
"HTTP/SSE MCP servers require exactly one URL. Example: cline mcp add linear https://mcp.linear.app/mcp --type http",
)
}
const config = {
url: targetOrCommand[0],
type: transportType,
}
ServerConfigSchema.parse(config)
return config
}
export async function addMcpServerShortcut(
name: string,
targetOrCommand: string[] = [],
options: McpAddOptions,
): Promise<AddMcpServerResult> {
const trimmedName = name.trim()
if (!trimmedName) {
throw new Error("Server name is required.")
}
const transportType = normalizeMcpTransportType(options.type)
const { DATA_DIR } = initializeCliContext({
clineDir: options.config,
workspaceDir: options.cwd || process.cwd(),
})
const settingsDirectoryPath = path.join(DATA_DIR, "settings")
await fs.mkdir(settingsDirectoryPath, { recursive: true })
const settingsPath = await getMcpSettingsFilePath(settingsDirectoryPath)
const content = await fs.readFile(settingsPath, "utf-8")
const settings = parseMcpSettings(content, settingsPath)
const mcpServers = settings.mcpServers as Record<string, unknown>
if (mcpServers[trimmedName]) {
throw new Error(`An MCP server named '${trimmedName}' already exists.`)
}
const serverConfig = createMcpServerConfig(targetOrCommand, transportType)
mcpServers[trimmedName] = serverConfig
await fs.writeFile(settingsPath, `${JSON.stringify(settings, null, 2)}\n`, "utf-8")
return {
serverName: trimmedName,
transportType,
settingsPath,
}
}
+12 -2
View File
@@ -7,6 +7,8 @@ import type { ApiProvider } from "@shared/api"
import { getProviderModelIdKey, ProviderToApiKeyMap } from "@shared/storage"
import { buildApiHandler } from "@/core/api"
import type { Controller } from "@/core/controller"
import { refreshOpenRouterModels } from "@/core/controller/models/refreshOpenRouterModels"
import { refreshVercelAiGatewayModels } from "@/core/controller/models/refreshVercelAiGatewayModels"
import { StateManager } from "@/core/storage/StateManager"
import type { BedrockConfig } from "../components/BedrockSetup"
import { getDefaultModelId } from "../components/ModelPicker"
@@ -40,14 +42,22 @@ export async function applyProviderConfig(options: ApplyProviderConfigOptions):
if (actModelKey) config[actModelKey] = finalModelId
if (planModelKey) config[planModelKey] = finalModelId
// For cline/openrouter, also set model info (required for getModel() to return correct model)
// Fetch model info from the provider API (not just disk cache) so headless
// CLI auth gets correct maxTokens, thinkingConfig, etc.
if ((providerId === "cline" || providerId === "openrouter") && controller) {
const openRouterModels = await controller.readOpenRouterModels()
const openRouterModels = await refreshOpenRouterModels(controller)
const modelInfo = openRouterModels?.[finalModelId]
if (modelInfo) {
stateManager.setGlobalState("actModeOpenRouterModelInfo", modelInfo)
stateManager.setGlobalState("planModeOpenRouterModelInfo", modelInfo)
}
} else if (providerId === "vercel-ai-gateway" && controller) {
const vercelModels = await refreshVercelAiGatewayModels(controller)
const modelInfo = vercelModels?.[finalModelId]
if (modelInfo) {
stateManager.setGlobalState("actModeVercelAiGatewayModelInfo", modelInfo)
stateManager.setGlobalState("planModeVercelAiGatewayModelInfo", modelInfo)
}
}
}
+70
View File
@@ -0,0 +1,70 @@
import { describe, expect, it } from "vitest"
import { findMostRecentTaskForWorkspace } from "./task-history"
describe("findMostRecentTaskForWorkspace", () => {
it("returns the newest matching task for the workspace", () => {
const result = findMostRecentTaskForWorkspace(
[
{
id: "older",
ts: 100,
task: "Older task",
tokensIn: 0,
tokensOut: 0,
totalCost: 0,
cwdOnTaskInitialization: "/repo",
},
{
id: "newer",
ts: 200,
task: "Newer task",
tokensIn: 0,
tokensOut: 0,
totalCost: 0,
cwdOnTaskInitialization: "/repo",
},
],
"/repo",
)
expect(result?.id).toBe("newer")
})
it("falls back to shadowGitConfigWorkTree for older tasks", () => {
const result = findMostRecentTaskForWorkspace(
[
{
id: "legacy",
ts: 200,
task: "Legacy task",
tokensIn: 0,
tokensOut: 0,
totalCost: 0,
shadowGitConfigWorkTree: "/repo",
},
],
"/repo",
)
expect(result?.id).toBe("legacy")
})
it("returns null when there is no match", () => {
const result = findMostRecentTaskForWorkspace(
[
{
id: "other",
ts: 200,
task: "Other task",
tokensIn: 0,
tokensOut: 0,
totalCost: 0,
cwdOnTaskInitialization: "/other",
},
],
"/repo",
)
expect(result).toBeNull()
})
})
+27
View File
@@ -0,0 +1,27 @@
import { HistoryItem } from "@shared/HistoryItem"
import { arePathsEqual } from "@/utils/path"
export function findMostRecentTaskForWorkspace(
taskHistory: HistoryItem[] | undefined,
workspacePath: string,
): HistoryItem | null {
if (!taskHistory?.length) {
return null
}
return (
[...taskHistory]
.filter((item) => {
if (!item.ts || !item.task) {
return false
}
return Boolean(
(item.cwdOnTaskInitialization && arePathsEqual(item.cwdOnTaskInitialization, workspacePath)) ||
(item.shadowGitConfigWorkTree && arePathsEqual(item.shadowGitConfigWorkTree, workspacePath)),
)
})
.sort((a, b) => b.ts - a.ts)
.at(0) ?? null
)
}
+20
View File
@@ -0,0 +1,20 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"declaration": true,
"emitDeclarationOnly": true,
"declarationMap": false,
"noCheck": true,
"noResolve": true,
"outDir": "dist/types"
},
"include": [
"src/exports.ts",
"src/agent/public-types.ts",
"src/agent/ClineAgent.ts",
"src/agent/ClineSessionEmitter.ts",
"src/agent/types.ts",
"src/agent/messageTranslator.ts",
"src/agent/permissionHandler.ts"
]
}
+18 -1
View File
@@ -5,11 +5,28 @@ export default defineConfig({
test: {
globals: true,
environment: "node",
include: ["src/**/*.test.{ts,tsx}", "tests/**/*.test.{ts,tsx}"],
coverage: {
reporter: ["text", "json", "html"],
exclude: ["node_modules/", "dist/"],
},
projects: [
{
extends: true,
test: {
name: "unit",
include: ["src/**/*.test.{ts,tsx}", "tests/**/*.test.{ts,tsx}"],
exclude: ["src/**/*.markdown.test.tsx"],
},
},
{
extends: true,
test: {
name: "markdown",
include: ["src/**/*.markdown.test.tsx"],
env: { FORCE_COLOR: "3" },
},
},
],
},
resolve: {
alias: {
+137
View File
@@ -0,0 +1,137 @@
---
title: "Authentication"
sidebarTitle: "Authentication"
description: "How to authenticate with the Cline API using API keys or account tokens."
---
Every request to the Cline API requires authentication via a Bearer token in the `Authorization` header.
## Authentication Methods
There are two ways to authenticate:
| Method | Use case | How to get it |
|--------|----------|---------------|
| **API key** | Direct API calls, scripts, CI/CD | Create at [app.cline.bot](https://app.cline.bot) Settings > API Keys |
| **Account auth token** | Cline extension and CLI | Generated automatically when you sign in |
Both methods use the same header format:
```bash
Authorization: Bearer YOUR_TOKEN
```
## API Keys
API keys are the recommended authentication method for programmatic access.
### Creating a Key
<Steps>
<Step title="Sign in">
Go to [app.cline.bot](https://app.cline.bot) and sign in.
</Step>
<Step title="Open API Keys">
Navigate to **Settings** > **API Keys**.
</Step>
<Step title="Create and copy">
Create a new key. Copy it immediately as you will not be able to see it again.
</Step>
</Steps>
### Deleting a Key
You can revoke an API key at any time from the same Settings > API Keys page. Deleted keys stop working immediately.
You can also manage keys programmatically through the [Enterprise API](/enterprise-solutions/api-reference#api-keys):
```bash
# List your keys
curl https://api.cline.bot/api/v1/api-keys \
-H "Authorization: Bearer YOUR_TOKEN"
# Delete a key
curl -X DELETE https://api.cline.bot/api/v1/api-keys/KEY_ID \
-H "Authorization: Bearer YOUR_TOKEN"
```
## Account Auth Tokens
When you sign in to the Cline extension (VS Code, JetBrains) or CLI, an account auth token is generated and managed automatically. You do not need to handle these tokens manually.
The Cline CLI uses these tokens when you authenticate via:
```bash
# Interactive sign-in
cline auth
# Or quick setup with an API key
cline auth -p cline -k "YOUR_API_KEY" -m anthropic/claude-sonnet-4-6
```
See the [CLI Reference](/cline-cli/cli-reference#cline-auth) for all auth options.
## Security Best Practices
**Do:**
- Store API keys in environment variables or a secrets manager
- Use different keys for development and production
- Rotate keys periodically
- Delete keys you no longer use
**Do not:**
- Commit keys to version control
- Share keys in chat or email
- Embed keys in client-side code (browsers, mobile apps)
- Log keys in application output
### Using Environment Variables
```bash
# Set the key
export CLINE_API_KEY="your_api_key_here"
# Use it in requests
curl -X POST https://api.cline.bot/api/v1/chat/completions \
-H "Authorization: Bearer $CLINE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "anthropic/claude-sonnet-4-6", "messages": [{"role": "user", "content": "Hello"}]}'
```
### Using a .env File
```bash
# .env (add to .gitignore)
CLINE_API_KEY=your_api_key_here
```
```python
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.cline.bot/api/v1",
api_key=os.environ["CLINE_API_KEY"],
)
```
## Custom Headers
The Cline API accepts optional headers for tracking and identification:
| Header | Description |
|--------|-------------|
| `HTTP-Referer` | Your application's URL. Helps with usage tracking. |
| `X-Title` | Your application's name. Appears in usage logs. |
| `X-Task-ID` | A unique task identifier. Used internally by the Cline extension. |
## Related
<CardGroup cols={2}>
<Card title="Getting Started" icon="rocket" href="/api/getting-started">
Create your first API key and make a request.
</Card>
<Card title="Enterprise API Keys" icon="building" href="/enterprise-solutions/api-reference#api-keys">
Manage API keys programmatically.
</Card>
</CardGroup>
+258
View File
@@ -0,0 +1,258 @@
---
title: "Chat Completions"
sidebarTitle: "Chat Completions"
description: "Full reference for the POST /chat/completions endpoint including all parameters, streaming, and tool calling."
---
The Chat Completions endpoint generates model responses from a conversation. It follows the [OpenAI Chat Completions](https://platform.openai.com/docs/api-reference/chat/create) format.
## Endpoint
```
POST https://api.cline.bot/api/v1/chat/completions
```
## Request Headers
| Header | Required | Description |
|--------|----------|-------------|
| `Authorization` | Yes | `Bearer YOUR_API_KEY` |
| `Content-Type` | Yes | `application/json` |
| `HTTP-Referer` | No | Your application URL (for usage tracking) |
| `X-Title` | No | Your application name (for usage logs) |
## Request Body
| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `model` | string | Yes | | Model ID in `provider/model` format. See [Models](/api/models). |
| `messages` | array | Yes | | Conversation messages. Each has `role` (`system`, `user`, `assistant`) and `content`. |
| `stream` | boolean | No | `true` | Return the response as a stream of Server-Sent Events. |
| `tools` | array | No | | Tool/function definitions in OpenAI format. |
| `temperature` | number | No | Model default | Sampling temperature (0.0 to 2.0). Lower values are more deterministic. |
### Message Format
Each message in the `messages` array has this structure:
```json
{
"role": "user",
"content": "Your message here"
}
```
**Roles:**
| Role | Purpose |
|------|---------|
| `system` | Sets the model's behavior and persona. Place first in the array. |
| `user` | The human's input. |
| `assistant` | Previous model responses (for multi-turn conversations). |
### Multi-Turn Conversation
Include previous messages to maintain context:
```json
{
"model": "anthropic/claude-sonnet-4-6",
"messages": [
{"role": "system", "content": "You are a helpful coding assistant."},
{"role": "user", "content": "What is a closure in JavaScript?"},
{"role": "assistant", "content": "A closure is a function that..."},
{"role": "user", "content": "Can you show me an example?"}
]
}
```
## Streaming Response
When `stream: true` (the default), the response is a series of [Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-Sent_Events):
```
data: {"id":"gen-abc123","choices":[{"delta":{"role":"assistant"},"index":0}],"model":"anthropic/claude-sonnet-4-6"}
data: {"id":"gen-abc123","choices":[{"delta":{"content":"The capital"},"index":0}],"model":"anthropic/claude-sonnet-4-6"}
data: {"id":"gen-abc123","choices":[{"delta":{"content":" of France"},"index":0}],"model":"anthropic/claude-sonnet-4-6"}
data: {"id":"gen-abc123","choices":[{"delta":{"content":" is Paris."},"index":0,"finish_reason":"stop"}],"model":"anthropic/claude-sonnet-4-6","usage":{"prompt_tokens":14,"completion_tokens":8,"cost":0.000066}}
data: [DONE]
```
Each `data:` line contains a JSON chunk. Key fields:
| Field | Description |
|-------|-------------|
| `id` | Generation ID, consistent across all chunks |
| `choices[0].delta.content` | The new text in this chunk |
| `choices[0].delta.reasoning` | Reasoning/thinking content (for reasoning models) |
| `choices[0].finish_reason` | `stop` when complete, `error` on failure |
| `usage` | Token counts and cost (included in the final chunk) |
### Usage Object
The final chunk includes token usage and cost:
```json
{
"usage": {
"prompt_tokens": 25,
"completion_tokens": 42,
"prompt_tokens_details": {
"cached_tokens": 0
},
"cost": 0.000315
}
}
```
| Field | Description |
|-------|-------------|
| `prompt_tokens` | Total input tokens |
| `completion_tokens` | Total output tokens |
| `prompt_tokens_details.cached_tokens` | Tokens served from cache (reduces cost) |
| `cost` | Total cost in USD for this request |
## Non-Streaming Response
When `stream: false`, the response is a single JSON object:
```json
{
"id": "gen-abc123",
"model": "anthropic/claude-sonnet-4-6",
"choices": [
{
"message": {
"role": "assistant",
"content": "The capital of France is Paris."
},
"finish_reason": "stop",
"index": 0
}
],
"usage": {
"prompt_tokens": 14,
"completion_tokens": 8
}
}
```
## Tool Calling
You can define tools that the model can call using the OpenAI function calling format:
```json
{
"model": "anthropic/claude-sonnet-4-6",
"messages": [
{"role": "user", "content": "What's the weather in San Francisco?"}
],
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather for a location",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "City and state, e.g. San Francisco, CA"
}
},
"required": ["location"]
}
}
}
]
}
```
When the model decides to call a tool, the response includes a `tool_calls` array:
```json
{
"choices": [
{
"message": {
"role": "assistant",
"tool_calls": [
{
"id": "call_abc123",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"location\": \"San Francisco, CA\"}"
}
}
]
},
"finish_reason": "tool_calls"
}
]
}
```
To continue the conversation after a tool call, include the tool result:
```json
{
"messages": [
{"role": "user", "content": "What's the weather in San Francisco?"},
{"role": "assistant", "tool_calls": [{"id": "call_abc123", "type": "function", "function": {"name": "get_weather", "arguments": "{\"location\": \"San Francisco, CA\"}"}}]},
{"role": "tool", "tool_call_id": "call_abc123", "content": "{\"temperature\": 62, \"condition\": \"foggy\"}"},
]
}
```
## Reasoning Models
Some models support extended thinking (reasoning). When using these models, the response may include reasoning content in the streaming delta:
```json
{"choices":[{"delta":{"reasoning":"Let me think about this step by step..."}}]}
```
Reasoning tokens are separate from the main content and appear in the `delta.reasoning` field. Some providers return encrypted reasoning blocks via `delta.reasoning_details` that can be passed back in subsequent requests to preserve the reasoning trace.
<Note>
Not all models support reasoning. See [Models](/api/models) for which models have reasoning capabilities.
</Note>
## Complete Example
```bash
curl -X POST https://api.cline.bot/api/v1/chat/completions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "anthropic/claude-sonnet-4-6",
"messages": [
{"role": "system", "content": "You are a concise assistant. Answer in one sentence."},
{"role": "user", "content": "Explain what an API is."}
],
"stream": true
}'
```
## Related
<CardGroup cols={2}>
<Card title="Models" icon="brain" href="/api/models">
Browse available models and their capabilities.
</Card>
<Card title="Errors" icon="triangle-exclamation" href="/api/errors">
Handle errors and implement retry logic.
</Card>
<Card title="SDK Examples" icon="code" href="/api/sdk-examples">
Use this endpoint from Python, Node.js, and more.
</Card>
<Card title="Authentication" icon="key" href="/api/authentication">
API key management and security practices.
</Card>
</CardGroup>
+152
View File
@@ -0,0 +1,152 @@
---
title: "Errors"
sidebarTitle: "Errors"
description: "Error codes, error formats, mid-stream errors, and retry strategies for the Cline API."
---
The Cline API returns errors in a consistent JSON format. Understanding these errors helps you build reliable integrations.
## Error Format
All errors follow the OpenAI error format:
```json
{
"error": {
"code": 401,
"message": "Invalid API key",
"metadata": {}
}
}
```
| Field | Type | Description |
|-------|------|-------------|
| `code` | number/string | HTTP status code or error identifier |
| `message` | string | Human-readable description of the error |
| `metadata` | object | Additional context (provider details, request IDs) |
## Error Codes
### HTTP Errors
These are returned as the HTTP response status code and in the error body:
| Code | Name | Cause | What to do |
|------|------|-------|------------|
| `400` | Bad Request | Malformed request body, missing required fields | Check your JSON syntax and required parameters |
| `401` | Unauthorized | Invalid or missing API key | Verify your API key in the `Authorization` header |
| `402` | Payment Required | Insufficient credits | Add credits at [app.cline.bot](https://app.cline.bot) |
| `403` | Forbidden | Key does not have access to this resource | Check key permissions |
| `404` | Not Found | Invalid endpoint or model ID | Verify the URL and model ID format |
| `429` | Too Many Requests | Rate limit exceeded | Wait and retry with exponential backoff |
| `500` | Internal Server Error | Server-side issue | Retry after a short delay |
| `502` | Bad Gateway | Upstream provider error | Retry after a short delay |
| `503` | Service Unavailable | Service temporarily down | Retry after a short delay |
### Mid-Stream Errors
When streaming, errors can occur after the response has started. These appear as a chunk with `finish_reason: "error"`:
```json
{
"choices": [
{
"finish_reason": "error",
"error": {
"code": "context_length_exceeded",
"message": "The input exceeds the model's maximum context length."
}
}
]
}
```
Common mid-stream error codes:
| Code | Meaning |
|------|---------|
| `context_length_exceeded` | Input tokens exceed the model's context window |
| `content_filter` | Content was blocked by a safety filter |
| `rate_limit` | Rate limit hit during generation |
| `server_error` | Upstream provider failed during generation |
<Warning>
Mid-stream errors do not produce an HTTP error code (the connection was already 200 OK). Always check `finish_reason` in your streaming handler.
</Warning>
## Retry Strategies
### Exponential Backoff
For transient errors (429, 500, 502, 503), retry with exponential backoff:
```python
import time
import requests
def call_api_with_retry(payload, max_retries=3):
for attempt in range(max_retries):
response = requests.post(
"https://api.cline.bot/api/v1/chat/completions",
headers={
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json",
},
json=payload,
)
if response.status_code == 200:
return response.json()
if response.status_code in (429, 500, 502, 503):
delay = (2 ** attempt) + 1
print(f"Retrying in {delay}s (attempt {attempt + 1}/{max_retries})")
time.sleep(delay)
continue
# Non-retryable error
response.raise_for_status()
raise Exception("Max retries exceeded")
```
### When to Retry
| Error | Retry? | Strategy |
|-------|--------|----------|
| `401 Unauthorized` | No | Fix your API key |
| `402 Payment Required` | No | Add credits |
| `429 Too Many Requests` | Yes | Exponential backoff (start at 1s) |
| `500 Internal Server Error` | Yes | Retry once after 1s |
| `502 Bad Gateway` | Yes | Retry up to 3 times with backoff |
| `503 Service Unavailable` | Yes | Retry up to 3 times with backoff |
| Mid-stream `error` | Depends | Retry the full request for transient errors |
### Rate Limits
If you hit rate limits frequently:
- Add delays between requests
- Reduce the number of concurrent requests
- Contact support if you need higher limits
## Debugging
When reporting issues, include:
1. The **error code and message** from the response
2. The **model ID** you were using
3. The **request ID** (from the `x-request-id` response header, if available)
4. Whether the error was **immediate** (HTTP error) or **mid-stream** (finish_reason error)
## Related
<CardGroup cols={2}>
<Card title="Chat Completions" icon="message" href="/api/chat-completions">
Endpoint reference with request and response schemas.
</Card>
<Card title="Authentication" icon="key" href="/api/authentication">
Verify your API key is configured correctly.
</Card>
</CardGroup>
+136
View File
@@ -0,0 +1,136 @@
---
title: "Getting Started"
sidebarTitle: "Getting Started"
description: "Create an API key and make your first request to the Cline API in under a minute."
---
This guide walks you through creating an API key and making your first Chat Completions request.
## Prerequisites
- A Cline account at [app.cline.bot](https://app.cline.bot)
- `curl` or any HTTP client (Python, Node.js, etc.)
## Create an API Key
<Steps>
<Step title="Sign in to app.cline.bot">
Go to [app.cline.bot](https://app.cline.bot) and sign in with your account.
</Step>
<Step title="Navigate to API Keys">
Open **Settings** and select **API Keys**.
</Step>
<Step title="Create a new key">
Click **Create API Key**. Copy the key immediately. You will not be able to see it again after leaving this page.
</Step>
</Steps>
<Warning>
Treat your API key like a password. Do not commit it to version control or share it publicly.
</Warning>
## Make Your First Request
Replace `YOUR_API_KEY` with the key you just created:
```bash
curl -X POST https://api.cline.bot/api/v1/chat/completions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "anthropic/claude-sonnet-4-6",
"messages": [
{"role": "user", "content": "What is the capital of France?"}
],
"stream": false
}'
```
## Verify the Response
You should get a JSON response like this:
```json
{
"id": "gen-abc123",
"model": "anthropic/claude-sonnet-4-6",
"choices": [
{
"message": {
"role": "assistant",
"content": "The capital of France is Paris."
},
"finish_reason": "stop",
"index": 0
}
],
"usage": {
"prompt_tokens": 14,
"completion_tokens": 8
}
}
```
The `choices[0].message.content` field contains the model's reply. The `usage` field shows how many tokens were consumed.
## Try Streaming
For real-time output, set `stream: true`. The response arrives as Server-Sent Events:
```bash
curl -X POST https://api.cline.bot/api/v1/chat/completions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "anthropic/claude-sonnet-4-6",
"messages": [
{"role": "user", "content": "Write a haiku about programming."}
],
"stream": true
}'
```
Each chunk arrives as a `data:` line. The stream ends with `data: [DONE]`.
## Try a Free Model
To test without spending credits, use one of the [free models](/api/models#free-models):
```bash
curl -X POST https://api.cline.bot/api/v1/chat/completions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "minimax/minimax-m2.5",
"messages": [
{"role": "user", "content": "Hello! What can you help me with?"}
],
"stream": false
}'
```
## Troubleshooting
| Problem | Solution |
|---------|----------|
| `401 Unauthorized` | Check that your API key is correct and included in the `Authorization` header |
| `402 Payment Required` | Your account has insufficient credits. Add credits at [app.cline.bot](https://app.cline.bot) |
| Empty response | Make sure `messages` is a non-empty array with at least one user message |
| Connection timeout | Verify your network can reach `api.cline.bot`. Check proxy settings if on a corporate network |
## Next Steps
<CardGroup cols={2}>
<Card title="Authentication" icon="key" href="/api/authentication">
Learn about API keys, token scoping, and security practices.
</Card>
<Card title="Chat Completions" icon="message" href="/api/chat-completions">
Full endpoint reference with all parameters and options.
</Card>
<Card title="Models" icon="brain" href="/api/models">
Browse available models and find the right one for your use case.
</Card>
<Card title="SDK Examples" icon="code" href="/api/sdk-examples">
Use the API from Python, Node.js, or the Cline CLI.
</Card>
</CardGroup>
+114
View File
@@ -0,0 +1,114 @@
---
title: "Models"
sidebarTitle: "Models"
description: "Available models, pricing tiers, free models, and how model IDs work in the Cline API."
---
The Cline API gives you access to models from multiple providers through a single endpoint. Model IDs follow the `provider/model-name` format, the same convention used by [OpenRouter](https://openrouter.ai).
## Model ID Format
Every model is identified by a string in the format:
```
provider/model-name
```
For example:
- `anthropic/claude-sonnet-4-6` - Claude Sonnet 4.6 from Anthropic
- `openai/gpt-4o` - GPT-4o from OpenAI
- `google/gemini-2.5-pro` - Gemini 2.5 Pro from Google
Pass this string as the `model` parameter in your [Chat Completions](/api/chat-completions) request.
## Popular Models
| Model ID | Provider | Context Window | Reasoning | Best For |
|----------|----------|---------------|-----------|----------|
| `anthropic/claude-sonnet-4-6` | Anthropic | 200K | Yes | General coding, analysis, complex tasks |
| `anthropic/claude-sonnet-4-5` | Anthropic | 200K | Yes | Balanced performance and cost |
| `openai/gpt-4o` | OpenAI | 128K | No | Multimodal tasks, fast responses |
| `google/gemini-2.5-pro` | Google | 1M | Yes | Very long context, document analysis |
| `deepseek/deepseek-chat` | DeepSeek | 64K | No | Cost-effective coding tasks |
| `x-ai/grok-3` | xAI | 128K | Yes | Reasoning-heavy tasks |
<Note>
Model availability and pricing change over time. Check [app.cline.bot](https://app.cline.bot) for the latest catalog.
</Note>
## Free Models
These models are available at no cost. They are a good starting point for experimentation and lightweight tasks:
| Model ID | Provider | Context Window |
|----------|----------|---------------|
| `minimax/minimax-m2.5` | MiniMax | 1M |
| `kwaipilot/kat-coder-pro` | Kwaipilot | 32K |
| `z-ai/glm-5` | Z-AI | 128K |
Free models have the same API interface as paid models. Just use their model ID:
```bash
curl -X POST https://api.cline.bot/api/v1/chat/completions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "minimax/minimax-m2.5",
"messages": [{"role": "user", "content": "Hello!"}]
}'
```
## Reasoning Models
Some models support extended thinking, where the model reasons through a problem before responding. When using these models:
- Reasoning content appears in `delta.reasoning` during streaming
- Some providers return encrypted reasoning blocks in `delta.reasoning_details`
- Reasoning tokens are counted separately from output tokens
Models with reasoning support include most Claude, Gemini 2.5, and Grok 3 models. Check the model's `supportsReasoning` capability in the model catalog.
## Choosing a Model
| If you need... | Consider |
|----------------|----------|
| Best coding performance | `anthropic/claude-sonnet-4-6` |
| Long document analysis | `google/gemini-2.5-pro` (1M context) |
| Fast, cheap responses | `deepseek/deepseek-chat` |
| Free experimentation | `minimax/minimax-m2.5` |
| Multi-modal (text + images) | `openai/gpt-4o` or `anthropic/claude-sonnet-4-6` |
| Complex reasoning | Any model with reasoning support |
For a deeper comparison of model capabilities and pricing, see the [Model Selection Guide](/core-features/model-selection-guide).
## Image Support
Models that support images accept base64-encoded image content in the `messages` array:
```json
{
"model": "anthropic/claude-sonnet-4-6",
"messages": [
{
"role": "user",
"content": [
{"type": "text", "text": "What's in this image?"},
{"type": "image_url", "image_url": {"url": "data:image/png;base64,..."}}
]
}
]
}
```
Not all models support images. Check the model's `supportsImages` capability before sending image content.
## Related
<CardGroup cols={2}>
<Card title="Chat Completions" icon="message" href="/api/chat-completions">
Use these models in your API requests.
</Card>
<Card title="Model Selection Guide" icon="scale-balanced" href="/core-features/model-selection-guide">
In-depth comparison for choosing the right model.
</Card>
</CardGroup>
+58
View File
@@ -0,0 +1,58 @@
---
title: "Cline API"
sidebarTitle: "Overview"
description: "Programmatic access to AI models through an OpenAI-compatible Chat Completions API."
---
Welcome to the Cline API documentation. Use the same models that power the Cline extension and CLI from any language, framework, or tool that speaks the OpenAI format.
## What is the Cline API?
The Cline API is an OpenAI-compatible Chat Completions endpoint. You authenticate once with a Cline API key and get access to models from Anthropic, OpenAI, Google, and more through a single base URL. No need to manage separate keys for each provider.
```
Your App → Cline API (api.cline.bot) → Anthropic / OpenAI / Google / etc.
```
<CardGroup cols={2}>
<Card title="Getting Started" icon="rocket" href="/api/getting-started">
Create an API key and make your first request in under a minute.
</Card>
<Card title="Authentication" icon="key" href="/api/authentication">
API keys, account tokens, key rotation, and security best practices.
</Card>
<Card title="Chat Completions" icon="message" href="/api/chat-completions">
Full endpoint reference with request schemas, streaming, and tool calling.
</Card>
<Card title="SDK Examples" icon="code" href="/api/sdk-examples">
Ready-to-copy examples for Python, Node.js, curl, and the Cline CLI.
</Card>
</CardGroup>
## Explore the Reference
<CardGroup cols={3}>
<Card title="Models" icon="brain" href="/api/models">
Browse available models, free tier options, reasoning support, and selection guidance.
</Card>
<Card title="Errors" icon="triangle-exclamation" href="/api/errors">
Error codes, mid-stream errors, retry strategies, and debugging tips.
</Card>
<Card title="Enterprise API" icon="building" href="/enterprise-solutions/api-reference">
Admin endpoints for managing users, organizations, billing, and API keys.
</Card>
</CardGroup>
## Quick Start
```bash
curl -X POST https://api.cline.bot/api/v1/chat/completions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "anthropic/claude-sonnet-4-6",
"messages": [{"role": "user", "content": "Hello!"}]
}'
```
Get your API key at [app.cline.bot](https://app.cline.bot) (Settings > API Keys), then follow the [Getting Started](/api/getting-started) guide.
+257
View File
@@ -0,0 +1,257 @@
---
title: "Cline API Reference"
sidebarTitle: "API Reference"
description: "Reference for the Cline Chat Completions API, an OpenAI-compatible endpoint for programmatic access."
---
The Cline API provides an OpenAI-compatible Chat Completions endpoint. You can use it from the Cline extension, the CLI, or any HTTP client that speaks the OpenAI format.
## Base URL
```
https://api.cline.bot/api/v1
```
## Authentication
All requests require a Bearer token in the `Authorization` header. You can use either:
- **API key** created at [app.cline.bot](https://app.cline.bot) (Settings > API Keys)
- **Account auth token** (used automatically by the Cline extension and CLI when you sign in)
```bash
Authorization: Bearer YOUR_API_KEY
```
### Getting an API Key
<Steps>
<Step title="Go to app.cline.bot">
Open [app.cline.bot](https://app.cline.bot) and sign in.
</Step>
<Step title="Open Settings > API Keys">
Navigate to **Settings**, then **API Keys**.
</Step>
<Step title="Create and copy your key">
Create a new key and copy it. Store it securely. You will not be able to see it again.
</Step>
</Steps>
## Chat Completions
Create a chat completion with streaming support. This endpoint follows the [OpenAI Chat Completions](https://platform.openai.com/docs/api-reference/chat/create) format.
### Request
```
POST /chat/completions
```
**Headers:**
| Header | Required | Description |
|--------|----------|-------------|
| `Authorization` | Yes | `Bearer YOUR_API_KEY` |
| `Content-Type` | Yes | `application/json` |
| `HTTP-Referer` | No | Your application URL |
| `X-Title` | No | Your application name |
**Body parameters:**
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `model` | string | Yes | Model ID in `provider/model` format (e.g., `anthropic/claude-sonnet-4-6`) |
| `messages` | array | Yes | Array of message objects with `role` and `content` |
| `stream` | boolean | No | Enable SSE streaming (default: `true`) |
| `tools` | array | No | Tool definitions in OpenAI function calling format |
| `temperature` | number | No | Sampling temperature |
### Example Request
```bash
curl -X POST https://api.cline.bot/api/v1/chat/completions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "anthropic/claude-sonnet-4-6",
"messages": [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Explain what a context window is in 2 sentences."}
],
"stream": true
}'
```
### Response (Streaming)
When `stream: true`, the response is a series of [Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-Sent_Events). Each event contains a JSON chunk:
```json
data: {"id":"gen-abc123","choices":[{"delta":{"content":"A context"},"index":0}],"model":"anthropic/claude-sonnet-4-6"}
data: {"id":"gen-abc123","choices":[{"delta":{"content":" window is"},"index":0}],"model":"anthropic/claude-sonnet-4-6"}
data: [DONE]
```
The final chunk includes a `usage` object with token counts and cost:
```json
{
"usage": {
"prompt_tokens": 25,
"completion_tokens": 42,
"prompt_tokens_details": {
"cached_tokens": 0
},
"cost": 0.000315
}
}
```
### Response (Non-Streaming)
When `stream: false`, the response is a single JSON object:
```json
{
"id": "gen-abc123",
"model": "anthropic/claude-sonnet-4-6",
"choices": [
{
"message": {
"role": "assistant",
"content": "A context window is the maximum amount of text..."
},
"finish_reason": "stop",
"index": 0
}
],
"usage": {
"prompt_tokens": 25,
"completion_tokens": 42
}
}
```
## Models
Model IDs use the `provider/model-name` format, the same format used by [OpenRouter](https://openrouter.ai). Some examples:
| Model ID | Description |
|----------|-------------|
| `anthropic/claude-sonnet-4-6` | Claude Sonnet 4.6 |
| `anthropic/claude-sonnet-4-5` | Claude Sonnet 4.5 |
| `google/gemini-2.5-pro` | Gemini 2.5 Pro |
| `openai/gpt-4o` | GPT-4o |
### Free Models
The following models are available at no cost:
| Model ID | Provider |
|----------|----------|
| `minimax/minimax-m2.5` | MiniMax |
| `kwaipilot/kat-coder-pro` | Kwaipilot |
| `z-ai/glm-5` | Z-AI |
<Note>
Model availability and pricing may change. Check [app.cline.bot](https://app.cline.bot) for the latest list.
</Note>
## Error Handling
Errors follow the OpenAI error format:
```json
{
"error": {
"code": 401,
"message": "Invalid API key",
"metadata": {}
}
}
```
Common error codes:
| Code | Meaning |
|------|---------|
| `401` | Invalid or missing API key |
| `402` | Insufficient credits |
| `429` | Rate limit exceeded |
| `500` | Server error |
| `error` (finish_reason) | Mid-stream error from the upstream model provider |
## Using with Cline
The easiest way to use the Cline API is through the Cline extension or CLI, which handle authentication and streaming for you.
### VS Code / JetBrains
Select **Cline** as your provider in the model picker dropdown. Sign in with your Cline account and your API key is managed automatically.
### Cline CLI
Configure the CLI with your API key in one command:
```bash
cline auth -p cline -k "YOUR_API_KEY" -m anthropic/claude-sonnet-4-6
```
Then run tasks normally:
```bash
cline "Write a one-line hello world in Python."
```
See the [CLI Reference](/cline-cli/cli-reference) for all available commands and options.
## Using with Other Tools
Because the Cline API is OpenAI-compatible, you can use it with any library or tool that supports custom OpenAI endpoints.
### Python (OpenAI SDK)
```python
from openai import OpenAI
client = OpenAI(
base_url="https://api.cline.bot/api/v1",
api_key="YOUR_API_KEY",
)
response = client.chat.completions.create(
model="anthropic/claude-sonnet-4-6",
messages=[{"role": "user", "content": "Hello!"}],
)
print(response.choices[0].message.content)
```
### Node.js (OpenAI SDK)
```typescript
import OpenAI from "openai"
const client = new OpenAI({
baseURL: "https://api.cline.bot/api/v1",
apiKey: "YOUR_API_KEY",
})
const response = await client.chat.completions.create({
model: "anthropic/claude-sonnet-4-6",
messages: [{ role: "user", content: "Hello!" }],
})
console.log(response.choices[0].message.content)
```
## Related
<CardGroup cols={2}>
<Card title="CLI Reference" icon="terminal" href="/cline-cli/cli-reference">
Full command reference for the Cline CLI, including auth setup.
</Card>
<Card title="Enterprise API" icon="building" href="/enterprise-solutions/api-reference">
Admin endpoints for user management, organizations, billing, and API keys.
</Card>
</CardGroup>
+275
View File
@@ -0,0 +1,275 @@
---
title: "SDK Examples"
sidebarTitle: "SDK Examples"
description: "Use the Cline API from Python, Node.js, curl, the Cline CLI, and the VS Code extension."
---
The Cline API is OpenAI-compatible, so any library or tool that works with OpenAI also works with the Cline API. Just change the base URL and API key.
## curl
### Non-Streaming
```bash
curl -X POST https://api.cline.bot/api/v1/chat/completions \
-H "Authorization: Bearer $CLINE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "anthropic/claude-sonnet-4-6",
"messages": [{"role": "user", "content": "What is 2+2?"}],
"stream": false
}'
```
### Streaming
```bash
curl -X POST https://api.cline.bot/api/v1/chat/completions \
-H "Authorization: Bearer $CLINE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "anthropic/claude-sonnet-4-6",
"messages": [{"role": "user", "content": "Write a short poem about code."}],
"stream": true
}'
```
## Python
### OpenAI SDK
The [OpenAI Python SDK](https://github.com/openai/openai-python) works with the Cline API by setting `base_url`:
```python
from openai import OpenAI
client = OpenAI(
base_url="https://api.cline.bot/api/v1",
api_key="YOUR_API_KEY",
)
# Non-streaming
response = client.chat.completions.create(
model="anthropic/claude-sonnet-4-6",
messages=[{"role": "user", "content": "Explain recursion in one sentence."}],
)
print(response.choices[0].message.content)
```
### Streaming in Python
```python
from openai import OpenAI
client = OpenAI(
base_url="https://api.cline.bot/api/v1",
api_key="YOUR_API_KEY",
)
stream = client.chat.completions.create(
model="anthropic/claude-sonnet-4-6",
messages=[{"role": "user", "content": "Write a function to reverse a string in Python."}],
stream=True,
)
for chunk in stream:
content = chunk.choices[0].delta.content
if content:
print(content, end="", flush=True)
print()
```
### Tool Calling in Python
```python
from openai import OpenAI
import json
client = OpenAI(
base_url="https://api.cline.bot/api/v1",
api_key="YOUR_API_KEY",
)
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get weather for a location",
"parameters": {
"type": "object",
"properties": {
"location": {"type": "string", "description": "City name"}
},
"required": ["location"],
},
},
}
]
response = client.chat.completions.create(
model="anthropic/claude-sonnet-4-6",
messages=[{"role": "user", "content": "What's the weather in Tokyo?"}],
tools=tools,
)
# Check if the model wants to call a tool
choice = response.choices[0]
if choice.message.tool_calls:
tool_call = choice.message.tool_calls[0]
print(f"Tool: {tool_call.function.name}")
print(f"Args: {tool_call.function.arguments}")
```
### Using requests
If you prefer not to use the OpenAI SDK:
```python
import requests
response = requests.post(
"https://api.cline.bot/api/v1/chat/completions",
headers={
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json",
},
json={
"model": "anthropic/claude-sonnet-4-6",
"messages": [{"role": "user", "content": "Hello!"}],
"stream": False,
},
)
data = response.json()
print(data["choices"][0]["message"]["content"])
```
## Node.js / TypeScript
### OpenAI SDK
The [OpenAI Node.js SDK](https://github.com/openai/openai-node) works with the Cline API by setting `baseURL`:
```typescript
import OpenAI from "openai"
const client = new OpenAI({
baseURL: "https://api.cline.bot/api/v1",
apiKey: "YOUR_API_KEY",
})
// Non-streaming
const response = await client.chat.completions.create({
model: "anthropic/claude-sonnet-4-6",
messages: [{ role: "user", content: "Explain async/await in one sentence." }],
})
console.log(response.choices[0].message.content)
```
### Streaming in Node.js
```typescript
import OpenAI from "openai"
const client = new OpenAI({
baseURL: "https://api.cline.bot/api/v1",
apiKey: "YOUR_API_KEY",
})
const stream = await client.chat.completions.create({
model: "anthropic/claude-sonnet-4-6",
messages: [{ role: "user", content: "Write a haiku about TypeScript." }],
stream: true,
})
for await (const chunk of stream) {
const content = chunk.choices[0]?.delta?.content
if (content) {
process.stdout.write(content)
}
}
console.log()
```
### Using fetch
```typescript
const response = await fetch("https://api.cline.bot/api/v1/chat/completions", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_API_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "anthropic/claude-sonnet-4-6",
messages: [{ role: "user", content: "Hello!" }],
stream: false,
}),
})
const data = await response.json()
console.log(data.choices[0].message.content)
```
## Cline CLI
The [Cline CLI](/cline-cli/cli-reference) is the fastest way to use the Cline API from your terminal. It handles authentication, streaming, and tool execution for you.
### Setup
```bash
# Install
npm install -g @anthropic-ai/cline
# Authenticate with a Cline API key
cline auth -p cline -k "YOUR_API_KEY" -m anthropic/claude-sonnet-4-6
```
### Run Tasks
```bash
# Simple prompt
cline "Explain what a REST API is."
# Pipe input
cat README.md | cline "Summarize this document."
# Use a specific model
cline -m google/gemini-2.5-pro "Analyze this codebase."
# YOLO mode for automation
cline -y "Run tests and fix failures."
```
See the [CLI Reference](/cline-cli/cli-reference) for all commands and options.
## VS Code / JetBrains
The Cline extension handles the API integration for you:
1. Open the Cline panel in your editor
2. Select **Cline** as the provider in the model picker
3. Sign in with your Cline account
4. Start chatting or give Cline a task
Your API key is managed automatically. No manual configuration needed.
For setup instructions, see [Installing Cline](/getting-started/installing-cline) and [Authorizing with Cline](/getting-started/authorizing-with-cline).
## Related
<CardGroup cols={2}>
<Card title="Chat Completions" icon="message" href="/api/chat-completions">
Full endpoint reference with all parameters.
</Card>
<Card title="Authentication" icon="key" href="/api/authentication">
API key management and security practices.
</Card>
<Card title="Models" icon="brain" href="/api/models">
Browse available models.
</Card>
<Card title="CLI Reference" icon="terminal" href="/cline-cli/cli-reference">
Complete Cline CLI command reference.
</Card>
</CardGroup>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 MiB

After

Width:  |  Height:  |  Size: 8.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 MiB

After

Width:  |  Height:  |  Size: 6.1 MiB

+4
View File
@@ -63,6 +63,9 @@ cline
# Start a task directly
cline "your prompt here"
# Resume the latest task for the current directory
cline --continue
```
**Options:**
@@ -77,6 +80,7 @@ cline "your prompt here"
| `--thinking` | Enable extended thinking with a 1024 token budget. |
| `--json` | Output messages as JSON (one object per line). Forces plain text mode. |
| `--timeout <seconds>` | Maximum execution time before the task is stopped. |
| `--continue` | Resume the most recent task from the current working directory. |
**Mode Behavior:**
+13 -3
View File
@@ -180,13 +180,23 @@ Cline CLI supports [MCP (Model Context Protocol)](/mcp/mcp-overview) servers, gi
### Setting Up MCP Servers
To configure MCP servers for the CLI, create or edit the settings file at:
You can add MCP servers from the CLI:
```bash
# STDIO server
cline mcp add kanban -- kanban mcp
# Remote HTTP server
cline mcp add linear https://mcp.linear.app/mcp --type http
```
These commands update:
```
~/.cline/data/settings/cline_mcp_settings.json
```
The file uses the same JSON format as the VS Code extension:
You can still edit this file directly. It uses the same JSON format as the VS Code extension:
```json
{
@@ -207,7 +217,7 @@ The file uses the same JSON format as the VS Code extension:
For the full configuration reference including STDIO and SSE transport types, see [Adding and Configuring MCP Servers](/mcp/adding-and-configuring-servers).
<Note>
The CLI does not yet have a `/mcp` slash command for managing MCP servers interactively. For now, you'll need to edit the `cline_mcp_settings.json` file directly.
The CLI does not yet have a `/mcp` slash command for interactive management inside the terminal UI. Use `cline mcp add` or edit `cline_mcp_settings.json` directly.
</Note>
### Custom Config Directory
-535
View File
@@ -1,535 +0,0 @@
---
title: "CVE Vulnerability Scanner"
description: "Automatically scan dependencies for CVEs and get AI-powered security reports using Cline CLI in GitHub Actions."
---
Turn noisy dependency audit output into actionable, prioritized security intelligence. This sample uses Cline CLI in GitHub Actions to scan for CVEs automatically — on every PR, on a weekly schedule, or on-demand — and post clear, prioritized reports with exact fix commands.
<Note>
**New to Cline CLI?** This sample assumes you understand Cline CLI basics and have completed the [Installation Guide](https://docs.cline.bot/cline-cli/installation). Start with the [GitHub RCA sample](./github-issue-rca) if you're looking for something simpler.
</Note>
## What It Does
| Trigger | What happens |
|---------|-------------|
| **PR opened** (dependency files changed) | Scans for CVEs, posts analysis as a PR comment |
| **Weekly schedule** (Monday 9am UTC) | Scans for newly disclosed CVEs, creates a GitHub Issue |
| **Manual trigger** | Scan with custom severity filter and optional auto-fix |
For each vulnerability found, Cline provides:
- **Plain-English impact** — what an attacker could actually do
- **Exploitability assessment** — is this theoretical or actively exploited?
- **Exact fix commands** — copy-paste remediation
- **Auto-fix safety** — which fixes are safe to apply without breaking changes
## Quick Start — Local Usage
Before setting up CI/CD, try it locally:
```bash
# Download the script
curl -O https://raw.githubusercontent.com/cline/cline/main/src/samples/cli/cve-scan/scan-cves.sh
chmod +x scan-cves.sh
# Run it (auto-detects npm/yarn/pnpm/pip)
./scan-cves.sh
```
Or skip the script and pipe directly:
```bash
npm audit --json | cline --yolo "Analyze these CVEs. For each: explain impact, assess exploitability, give exact fix commands. Prioritize by severity."
```
<Tip>
The `--yolo` flag (or `-y` for short) runs Cline in fully autonomous mode — it executes commands without waiting for approval. This is what makes piping and CI/CD workflows possible.
</Tip>
## Prerequisites
- **Cline CLI** installed and authenticated ([Installation Guide](https://docs.cline.bot/cline-cli/installation))
- **GitHub repository** with Actions enabled
- **API provider account** (Anthropic, OpenRouter, etc.) with API key added as a repository secret
## Setup
### 1. Copy the Workflow File
```bash
mkdir -p .github/workflows
curl -o .github/workflows/cline-cve-scan.yml \
https://raw.githubusercontent.com/cline/cline/main/src/samples/cli/cve-scan/cline-cve-scan.yml
```
<Accordion title="Click to view the complete cline-cve-scan.yml workflow">
```yaml
name: Cline CVE Scanner
on:
# Weekly scheduled scan — catches new CVEs in existing dependencies
schedule:
- cron: "0 9 * * 1" # Every Monday at 9am UTC
# PR scan — catch vulnerable dependencies before they merge
pull_request:
types: [opened, synchronize, ready_for_review]
paths:
- "package.json"
- "package-lock.json"
- "yarn.lock"
- "pnpm-lock.yaml"
- "requirements.txt"
- "Pipfile.lock"
- "pyproject.toml"
# Manual trigger with options
workflow_dispatch:
inputs:
severity:
description: "Minimum severity to report"
required: false
default: "all"
type: choice
options:
- all
- low
- medium
- high
- critical
auto_fix:
description: "Attempt safe auto-fixes"
required: false
default: false
type: boolean
concurrency:
group: cve-scan-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
cve-scan:
if: |
(github.event_name == 'pull_request' && github.event.pull_request.draft == false) ||
github.event_name == 'schedule' ||
github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: read
pull-requests: write
issues: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "22"
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Install Cline CLI
run: npm install -g cline
- name: Configure Cline Authentication
run: |
cline auth --provider anthropic \
--apikey "${{ secrets.ANTHROPIC_API_KEY }}" \
--modelid claude-sonnet-4-5-20250929
- name: Determine scan parameters
id: params
run: |
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
echo "severity=${{ inputs.severity }}" >> $GITHUB_OUTPUT
echo "auto_fix=${{ inputs.auto_fix }}" >> $GITHUB_OUTPUT
elif [ "${{ github.event_name }}" == "pull_request" ]; then
echo "severity=high" >> $GITHUB_OUTPUT
echo "auto_fix=false" >> $GITHUB_OUTPUT
else
echo "severity=all" >> $GITHUB_OUTPUT
echo "auto_fix=false" >> $GITHUB_OUTPUT
fi
if [ "${{ github.event_name }}" == "pull_request" ]; then
echo "output=pr-comment" >> $GITHUB_OUTPUT
echo "pr_number=${{ github.event.pull_request.number }}" >> $GITHUB_OUTPUT
else
echo "output=github-issue" >> $GITHUB_OUTPUT
echo "pr_number=" >> $GITHUB_OUTPUT
fi
- name: Download CVE scan script
run: |
curl -sL https://raw.githubusercontent.com/${{ github.repository }}/main/scan-cves.sh -o scan-cves.sh \
|| cp src/samples/cli/cve-scan/scan-cves.sh scan-cves.sh 2>/dev/null \
|| true
chmod +x scan-cves.sh
- name: Run CVE scan with Cline
env:
GH_TOKEN: ${{ github.token }}
GITHUB_REPOSITORY: ${{ github.repository }}
CLINE_COMMAND_PERMISSIONS: |
{
"allow": [
"npm audit *",
"yarn audit *",
"pnpm audit *",
"pip-audit *",
"gh issue create *",
"gh issue list *",
"gh pr comment *",
"cat *",
"echo *"
],
"deny": [
"rm *",
"sudo *",
"npm install *",
"npm publish *"
]
}
run: |
PR_FLAG=""
if [ -n "${{ steps.params.outputs.pr_number }}" ]; then
PR_FLAG="--pr ${{ steps.params.outputs.pr_number }}"
fi
AUTO_FIX_FLAG=""
if [ "${{ steps.params.outputs.auto_fix }}" == "true" ]; then
AUTO_FIX_FLAG="--auto-fix"
fi
./scan-cves.sh \
--scanner npm \
--output ${{ steps.params.outputs.output }} \
--severity ${{ steps.params.outputs.severity }} \
$PR_FLAG \
$AUTO_FIX_FLAG
```
</Accordion>
### 2. Add the Scan Script
Add `scan-cves.sh` to your repository root (or wherever the workflow downloads it from):
```bash
curl -O https://raw.githubusercontent.com/cline/cline/main/src/samples/cli/cve-scan/scan-cves.sh
chmod +x scan-cves.sh
```
<Accordion title="Click to view scan-cves.sh (simplified — see source for full version)">
```bash
#!/bin/bash
# scan-cves.sh — CVE vulnerability scanner powered by Cline CLI
#
# Usage:
# ./scan-cves.sh # Auto-detect scanner, stdout
# ./scan-cves.sh --output github-issue # Post as GitHub Issue
# ./scan-cves.sh --output pr-comment --pr 42 # Post as PR comment
# ./scan-cves.sh --scanner npm --severity critical # Filter by severity
# cat audit.json | ./scan-cves.sh --scanner custom # Custom scanner input
set -euo pipefail
SCANNER=""
OUTPUT="stdout"
SEVERITY="all"
PR_NUMBER=""
REPO="${GITHUB_REPOSITORY:-}"
AUTO_FIX="false"
CLINE_EXTRA_FLAGS=""
while [[ $# -gt 0 ]]; do
case "$1" in
--scanner) SCANNER="$2"; shift 2 ;;
--output) OUTPUT="$2"; shift 2 ;;
--severity) SEVERITY="$2"; shift 2 ;;
--pr) PR_NUMBER="$2"; shift 2 ;;
--repo) REPO="$2"; shift 2 ;;
--auto-fix) AUTO_FIX="true"; shift ;;
--config) CLINE_EXTRA_FLAGS="$CLINE_EXTRA_FLAGS --config $2"; shift 2 ;;
--model) CLINE_EXTRA_FLAGS="$CLINE_EXTRA_FLAGS -m $2"; shift 2 ;;
-h|--help) echo "Usage: scan-cves.sh [--scanner npm|yarn|pnpm|pip|custom] [--output stdout|github-issue|pr-comment|file] [--severity all|critical|high|medium|low] [--pr N] [--repo owner/repo] [--auto-fix] [--config path] [--model id]"; exit 0 ;;
*) echo "Unknown option: $1"; exit 1 ;;
esac
done
# Auto-detect scanner from lockfiles
if [[ -z "$SCANNER" ]]; then
if [[ -f "package-lock.json" ]]; then SCANNER="npm"
elif [[ -f "yarn.lock" ]]; then SCANNER="yarn"
elif [[ -f "pnpm-lock.yaml" ]]; then SCANNER="pnpm"
elif [[ -f "requirements.txt" ]] || [[ -f "Pipfile.lock" ]]; then SCANNER="pip"
else echo "Error: Could not detect package manager." >&2; exit 1; fi
echo "Auto-detected scanner: $SCANNER" >&2
fi
# Run the scan
case "$SCANNER" in
npm) SCAN_OUTPUT=$(npm audit --json 2>/dev/null || true) ;;
yarn) SCAN_OUTPUT=$(yarn audit --json 2>/dev/null || true) ;;
pnpm) SCAN_OUTPUT=$(pnpm audit --json 2>/dev/null || true) ;;
pip) SCAN_OUTPUT=$(pip-audit --format json 2>/dev/null || true) ;;
custom) SCAN_OUTPUT=$(cat) ;;
*) echo "Unknown scanner: $SCANNER" >&2; exit 1 ;;
esac
if [[ -z "$SCAN_OUTPUT" ]]; then echo "✅ No vulnerabilities found!" >&2; exit 0; fi
# Build the security analyst prompt
PROMPT='You are a senior security analyst. Analyze these vulnerability scan results.
For EACH vulnerability: provide CVE ID, severity, affected package with versions,
plain-English impact, exploitability assessment, exact fix commands, and auto-fix safety.
Format as markdown with sections: 🔴 Critical, 🟠 High, 🟡 Medium, 🔵 Low,
Summary & Recommended Actions, Risk Assessment.
Omit empty severity sections. Flag actively exploited CVEs with ⚠️.'
if [[ "$SEVERITY" != "all" ]]; then
PROMPT="$PROMPT Focus ONLY on $SEVERITY severity or higher."
fi
# Run Cline analysis
echo "Analyzing vulnerabilities with Cline..." >&2
REPORT=$(echo "$SCAN_OUTPUT" | cline -y $CLINE_EXTRA_FLAGS "$PROMPT" 2>/dev/null)
# Output results
case "$OUTPUT" in
stdout) echo "$REPORT" ;;
github-issue) gh issue create --repo "$REPO" --title "🔒 CVE Report — $(date +%Y-%m-%d)" --body "$REPORT" --label "security,automated" ;;
pr-comment) gh pr comment "$PR_NUMBER" --repo "$REPO" --body "$REPORT" ;;
file) echo "$REPORT" > "cve-report-$(date +%Y%m%d-%H%M%S).md" ;;
esac
```
The [full source script](https://github.com/cline/cline/blob/main/src/samples/cli/cve-scan/scan-cves.sh) includes additional features: a `--help` usage guide, `detect_scanner()` and `run_scan()` helper functions, a detailed heredoc security prompt with auto-fix instructions, and JSON output extraction via `jq`.
</Accordion>
### 3. Configure Secrets
1. Go to your repository **Settings** → **Secrets and variables** → **Actions**
2. Add a **New repository secret**:
- **Name:** `ANTHROPIC_API_KEY` (or match the provider in your workflow)
- **Value:** Your API key
### 4. Commit and Push
```bash
git add .github/workflows/cline-cve-scan.yml scan-cves.sh
git commit -m "Add Cline CVE scanner workflow"
git push
```
## Usage
### Automatic Triggers
Once set up, the scanner runs automatically:
- **Weekly (Monday 9am UTC):** Creates a GitHub Issue with a full vulnerability report
- **On PR:** Posts a comment on PRs that modify dependency files (only high+ severity)
### Manual Trigger
Go to **Actions** → **Cline CVE Scanner** → **Run workflow** to trigger a scan with custom options:
- Choose minimum severity level
- Optionally enable auto-fix for safe updates
### Local Usage
```bash
# Basic scan (auto-detects package manager)
./scan-cves.sh
# Save to file
./scan-cves.sh --output file
# Only critical CVEs
./scan-cves.sh --severity critical
# Post as GitHub Issue
./scan-cves.sh --output github-issue --repo myorg/myrepo
# Use a specific model
./scan-cves.sh --model claude-opus-4-5-20251101
# Pipe from any scanner (Trivy, Snyk, Grype, etc.)
trivy fs --format json . | ./scan-cves.sh --scanner custom
```
## How It Works
### Architecture
The scanner follows a three-layer design that keeps each concern separate and extensible:
```
┌─────────────────────────────────────────────────┐
│ Layer 1: Scanner Adapter (pluggable) │
│ npm audit | yarn audit | pip-audit | custom │
└────────────────────┬────────────────────────────┘
│ JSON vulnerability data
┌────────────────────▼────────────────────────────┐
│ Layer 2: Cline Security Analyst (reusable) │
│ AI-powered analysis via cline --yolo │
└────────────────────┬────────────────────────────┘
│ Markdown report
┌────────────────────▼────────────────────────────┐
│ Layer 3: Output Adapter (pluggable) │
│ stdout | GitHub Issue | PR comment | file │
└─────────────────────────────────────────────────┘
```
**Layer 1 (Scanner)** runs the appropriate audit command and produces JSON. You can swap scanners without touching the analysis logic.
**Layer 2 (Cline)** receives the raw vulnerability JSON and produces a prioritized, human-readable report. The security analyst prompt is self-contained and could be extracted into a Prompts Library entry.
**Layer 3 (Output)** delivers the report to its destination. Adding a new output target (e.g., Slack webhook) requires only a few lines in the output case statement.
### The Security Analyst Prompt
The core prompt instructs Cline to act as a senior security analyst. For each CVE, it provides:
1. **CVE ID & Severity** with color-coded sections
2. **Impact Assessment** in plain English (not just "RCE" — the actual attack vector)
3. **Exploitability** — is this a real-world risk or theoretical?
4. **Exact Fix** — copy-paste commands specific to your package manager
5. **Auto-fix Safety** — whether a simple version bump is safe
This prompt is **reusable** — it works with any JSON vulnerability data, not just npm audit. It could be published to the Cline Prompts Library for broader use.
### Security: Command Permissions
The workflow uses `CLINE_COMMAND_PERMISSIONS` to restrict Cline to safe, read-only operations:
```json
{
"allow": ["npm audit *", "gh issue create *", "gh pr comment *"],
"deny": ["rm *", "sudo *", "npm install *", "npm publish *"]
}
```
This ensures Cline can scan and report, but cannot modify your codebase or install packages — even in YOLO mode.
## Customization
### Different Package Managers
The script auto-detects from lockfiles, or you can specify explicitly:
```bash
./scan-cves.sh --scanner yarn
./scan-cves.sh --scanner pnpm
./scan-cves.sh --scanner pip
```
### Model Orchestration
Combine with [Model Orchestration](./model-orchestration) patterns for cost optimization:
```bash
# Cheap model for weekly triage
./scan-cves.sh --config ~/.cline-haiku --severity all
# Expensive model only for critical CVEs
./scan-cves.sh --config ~/.cline-opus --severity critical
```
### Custom Scanners
Pipe output from any scanner that produces JSON:
```bash
# Trivy (container/filesystem scanner)
trivy fs --format json . | ./scan-cves.sh --scanner custom
# Snyk
snyk test --json | ./scan-cves.sh --scanner custom
# Grype
grype dir:. -o json | ./scan-cves.sh --scanner custom
```
### Slack Notifications
Extend the output adapter by piping stdout to a Slack webhook:
```bash
REPORT=$(./scan-cves.sh)
curl -X POST -H 'Content-type: application/json' \
--data "{\"text\": \"$REPORT\"}" \
"$SLACK_WEBHOOK_URL"
```
## Sample Output
Here's an example of a Cline-generated CVE report:
```markdown
# 🔒 CVE Vulnerability Report
**Scan Date:** 2026-02-11
**Scanner:** npm
**Total Vulnerabilities:** 4
## 🔴 Critical Vulnerabilities (1)
### CVE-2022-24999: qs
- **Severity:** Critical
- **Package:** `qs@6.7.0` → fix in `qs@6.11.0`
- **Impact:** Prototype pollution via crafted query strings. An attacker can inject
properties into Object.prototype, which in Express.js apps can lead to remote code
execution or denial of service.
- **Exploitability:** ⚠️ ACTIVELY EXPLOITED — public exploits available, any Express
app using query parsing is vulnerable.
- **Fix:** `npm install qs@6.11.0`
- **Auto-fix safe:** Yes
## 🟠 High Vulnerabilities (2)
### CVE-2023-28155: jsonwebtoken
- **Severity:** High
- **Package:** `jsonwebtoken@8.5.1` → fix in `jsonwebtoken@9.0.0`
- **Impact:** Insecure default algorithm allows an attacker to forge tokens if the
server doesn't explicitly set the algorithm. Could lead to authentication bypass.
- **Exploitability:** Medium — requires the server to not specify algorithms explicitly.
- **Fix:** `npm install jsonwebtoken@9.0.0`
- **Auto-fix safe:** No (major version bump, verify API compatibility)
### CVE-2023-45857: axios
- **Severity:** High
- **Package:** `axios@0.21.1` → fix in `axios@1.6.0`
- **Impact:** SSRF vulnerability allows specially crafted requests to access internal
services. An attacker controlling request URLs could probe internal infrastructure.
- **Exploitability:** Medium — requires user-controlled URL input.
- **Fix:** `npm install axios@1.6.0`
- **Auto-fix safe:** No (major version bump)
## 📋 Summary & Recommended Actions
1. **Immediate:** Update qs to 6.11.0 — critical, actively exploited, safe auto-fix
2. **This sprint:** Update jsonwebtoken to 9.0.0 and axios to 1.6.0 (test for breaking changes)
3. **Safe auto-fix command:** `npm audit fix`
## 📊 Risk Assessment
This project has 1 critical and 2 high severity vulnerabilities. The critical qs
vulnerability is actively exploited and should be fixed immediately — it's a safe
auto-fix with no breaking changes. The jsonwebtoken and axios updates are major
version bumps that require testing but should be scheduled for the current sprint.
Overall dependency hygiene needs improvement — consider running automated CVE scans
weekly to catch issues earlier.
```
## Related Samples
- **[GitHub PR Review](./github-pr-review)** — Automated code review on PRs
- **[GitHub Integration](./github-integration)** — Respond to issues with @cline
- **[Model Orchestration](./model-orchestration)** — Multi-model workflows for cost optimization
-8
View File
@@ -47,14 +47,6 @@ This section provides sample implementations that demonstrate various Cline CLI
>
Automatically review Pull Requests with AI. Configures Cline in GitHub Actions to analyze diffs, check for security issues, and post detailed reviews with inline code suggestions.
</Card>
<Card
title="CVE Vulnerability Scanner (Actions)"
icon="shield-halved"
href="/cline-cli/samples/cve-scan"
>
Automatically scan dependencies for CVEs and get AI-powered security reports. Runs on PRs, weekly schedules, or on-demand. Supports npm, yarn, pnpm, pip, and custom scanners like Trivy and Snyk.
</Card>
</CardGroup>
## Additional Resources
+675
View File
@@ -0,0 +1,675 @@
---
title: "Cline SDK"
description: "Embed Cline as a programmable coding agent in your Node.js applications using an ACP-compatible TypeScript API."
---
# Cline SDK
The Cline SDK lets you embed Cline as a programmable coding agent in your Node.js applications. It exposes the same capabilities as the Cline CLI and VS Code extension — file editing, command execution, browser use, MCP servers — through a TypeScript API that conforms to the [Agent Client Protocol (ACP)](https://agentclientprotocol.com/protocol/schema).
## Installation
```bash
npm install cline
```
If you want direct ACP type imports as well:
```bash
npm install @agentclientprotocol/sdk
```
Requires Node.js 20+.
## Quick Start
```typescript
import { ClineAgent } from "cline";
const CLINE_DIR = "/Users/username/.cline";
const agent = new ClineAgent({ clineDir: CLINE_DIR });
// 1. Initialize — negotiates capabilities
const initializeResponse = await agent.initialize({
protocolVersion: 1,
// these are the capabilities that the client (you) supports
// The cline agent may or may not use them, but it needs to know about them to make informed decisions about what tools to use.
clientCapabilities: {
fs: { readTextFile: true, writeTextFile: true },
terminal: true,
},
});
const { agentInfo, authMethods } = initializeResponse;
console.log("Agent info:", agentInfo); // contains things like agent name and version
console.log("Auth methods:", authMethods); // contains a list of supported authentication methods. More auth methods coming soon
// 2. Authenticate if needed
// If you skip this step, ClineAgent will look in CLINE_DIR for any existing credentials and authenticate with those
await agent.authenticate({ methodId: "cline-oauth" });
// 3. Create a session.
// A session represents a conversation or task with the agent. You can have multiple sessions for different tasks or conversations.
const { sessionId } = await agent.newSession({
cwd: process.cwd(),
mcpServers: [], // mcpServers field not supported yet, but exposed here to maintain conformance with acp protocol
});
// 4. Agent updates are sent via events. You can subscribe to these events to get real-time updates on the agent's progress, tool calls, and more.
const emitter = agent.emitterForSession(sessionId);
emitter.on("agent_message_chunk", (payload) => {
process.stdout.write(
payload.content.type === "text"
? payload.content.text
: `[${payload.content.type}]`,
);
});
emitter.on("agent_thought_chunk", (payload) => {
process.stdout.write(
payload.content.type === "text"
? payload.content.text
: `[${payload.content.type}]`,
);
});
emitter.on("tool_call", (payload) => {
console.log(`[tool] ${payload.title}`);
});
emitter.on("error", (err) => {
console.error("[session error]", err);
});
// 5. Send a prompt and wait for completion
const { stopReason } = await agent.prompt({
sessionId,
prompt: [{ type: "text", text: "Create a hello world Express server" }],
});
console.log("Done:", stopReason);
// 6. Clean up
await agent.shutdown();
```
## Core Concepts
### Agent Lifecycle
The SDK follows the ACP lifecycle:
```
initialize() → authenticate() → newSession() → prompt() ⇄ events → shutdown()
```
| Step | Method | Purpose |
|------|--------|---------|
| Init | `initialize()` | Exchange protocol version and capabilities |
| Auth | `authenticate()` | OAuth flow for Cline or OpenAI Codex accounts. Optional step if cline config directory already has credentials |
| Session | `newSession()` | Create an isolated conversation context |
| Prompt | `prompt()` | Send user messages; blocks until the turn ends |
| Cancel | `cancel()` | Abort an in-progress prompt turn |
| Mode | `setSessionMode()` | Switch between `"plan"` and `"act"` modes |
| Model | `unstable_setSessionModel()` | Change the backing LLM (experimental) |
| Shutdown | `shutdown()` | Abort all tasks, flush state, release resources |
### Sessions
A session is an independent conversation with its own task history and working directory. You can run multiple sessions concurrently.
```typescript
const { sessionId, modes, models } = await agent.newSession({
cwd: "/path/to/project",
mcpServers: [], // mcpServers field not supported yet, but exposed here to maintain conformance with acp protocol
})
```
The response includes:
- `sessionId` — use this in all subsequent calls
- `modes` — available modes (`plan`, `act`) and the current mode
- `models` — available models and the current model ID
Access session metadata via the read-only `sessions` map:
```typescript
const session = agent.sessions.get(sessionId)
// { sessionId, cwd, mode, mcpServers, createdAt, lastActivityAt, ... }
```
### Prompting
`prompt()` sends a user message and blocks until the agent finishes its turn. While the prompt is processing, the agent streams output via session events.
```typescript
const response = await agent.prompt({
sessionId,
prompt: [
{ type: "text", text: "Refactor the auth module to use JWT" },
],
})
```
The prompt array accepts multiple content blocks:
```typescript
// Text + image + file context
await agent.prompt({
sessionId,
prompt: [
{ type: "text", text: "What's in this screenshot?" },
{ type: "image", data: base64ImageData, mimeType: "image/png" },
{
type: "resource",
resource: {
uri: "file:///path/to/relevant-file.ts",
mimeType: "text/plain",
text: fileContents,
},
},
],
})
```
#### Content Block Types
| Type | Fields | Description |
|------|--------|-------------|
| `TextContent` | `{ type: "text", text: string }` | Plain text message |
| `ImageContent` | `{ type: "image", mimeType: string, data: string }` | Base64-encoded image |
| `EmbeddedResource` | `{ type: "resource", resource: { uri: string, mimeType?: string, text?: string, blob?: string } }` | File or resource context |
#### Stop Reasons
`prompt()` resolves with a `stopReason`:
| Value | Meaning |
|-------|---------|
| `"end_turn"` | Agent finished normally (completed task or waiting for user input) |
| `"error"` | An error occurred |
### Streaming Events
Subscribe to real-time output via `ClineSessionEmitter`. Each session has its own emitter.
```typescript
const emitter = agent.emitterForSession(sessionId)
```
#### Event Types
All events correspond to [ACP `SessionUpdate` types](https://agentclientprotocol.com/protocol/schema#SessionUpdate):
| Event | Payload | Description |
|-------|---------|-------------|
| `agent_message_chunk` | `{ content: ContentBlock }` | Streamed text from the agent |
| `agent_thought_chunk` | `{ content: ContentBlock }` | Internal reasoning / chain-of-thought |
| `tool_call` | `ToolCall` | New tool invocation (file edit, command, etc.) |
| `tool_call_update` | `ToolCallUpdate` | Progress/result update for an existing tool call |
| `plan` | `{ entries: PlanEntry[] }` | Agent's execution plan |
| `available_commands_update` | `{ availableCommands: AvailableCommand[] }` | Slash commands the agent supports |
| `current_mode_update` | `{ currentModeId: string }` | Mode changed (plan/act) |
| `user_message_chunk` | `{ content: ContentBlock }` | User message chunks (for multi-turn) |
| `config_option_update` | `{ configOptions: SessionConfigOption[] }` | Configuration changed |
| `session_info_update` | Session metadata | Session metadata changed |
| `error` | `Error` | Session-level error (not an ACP update) |
```typescript
emitter.on("agent_message_chunk", (payload) => {
// payload.content is a ContentBlock — usually { type: "text", text: "..." }
process.stdout.write(payload.content.text)
})
emitter.on("agent_thought_chunk", (payload) => {
console.log("[thinking]", payload.content.text)
})
emitter.on("tool_call", (payload) => {
console.log(`[${payload.kind}] ${payload.title} (${payload.status})`)
})
emitter.on("tool_call_update", (payload) => {
console.log(` → ${payload.toolCallId}: ${payload.status}`)
})
emitter.on("error", (err) => {
console.error("Session error:", err)
})
```
The emitter supports `on`, `once`, `off`, and `removeAllListeners`.
### Permission Handling
When the agent wants to execute a tool (edit a file, run a command, etc.), it requests permission. You **must** set a permission handler or all tool calls will be auto-rejected.
```typescript
agent.setPermissionHandler(async (request) => {
// request.toolCall — details about what the agent wants to do
// request.options — available choices (allow_once, reject_once, etc.)
console.log(`Permission requested: ${request.toolCall.title}`)
console.log("Options:", request.options.map(o => `${o.optionId} (${o.kind})`))
// Auto-approve everything:
const allowOption = request.options.find(o => o.kind.includes("allow"))
if (allowOption) {
return { outcome: { outcome: "selected", optionId: allowOption.optionId } }
} else {
return { outcome: { outcome: "rejected" } }
}
})
```
#### Permission Options
Each permission request includes an array of `PermissionOption` objects:
| `kind` | Meaning |
|--------|---------|
| `allow_once` | Approve this single operation |
| `allow_always` | Approve and remember for future operations |
| `reject_once` | Deny this single operation |
| `reject_always` | Deny and remember for future operations |
**Important:** If no permission handler is set, all tool calls are rejected for safety.
### Modes
Cline supports two modes:
- **`plan`** — The agent gathers information and creates a plan without executing actions
- **`act`** — The agent executes actions (file edits, commands, etc.)
```typescript
// Switch to plan mode
await agent.setSessionMode({ sessionId, modeId: "plan" })
// Switch back to act mode
await agent.setSessionMode({ sessionId, modeId: "act" })
```
The current mode is returned in `newSession()`
### Model Selection
Change the backing model with `unstable_setSessionModel()`. The model ID format is `"provider/modelId"`.
```typescript
await agent.unstable_setSessionModel({
sessionId,
modelId: "anthropic/claude-sonnet-4-20250514",
})
```
This sets the model for both plan and act modes. Available providers include `anthropic`, `openai-native`, `gemini`, `bedrock`, `deepseek`, `mistral`, `groq`, `xai`, and others. Model Ids can be found in the NewSessionResponse object after calling `agent.newSession(..)`
> **Note:** This API is experimental and may change.
### Authentication
The SDK supports two OAuth flows:
```typescript
// Cline account (uses browser OAuth)
await agent.authenticate({ methodId: "cline-oauth" })
// OpenAI Codex / ChatGPT subscription
await agent.authenticate({ methodId: "openai-codex-oauth" })
```
Both methods open a browser window for the OAuth flow and block until authentication completes (5-minute timeout for Cline OAuth).
For BYO (bring-your-own) API key providers, configure the key through the cline config directory before creating a session. The `authenticate()` call is not needed for BYO providers. We plan to support more auth providers in the near future.
### Cancellation
Cancel an in-progress prompt turn:
```typescript
await agent.cancel({ sessionId })
```
## API Reference
### Constructor
```typescript
new ClineAgent(options: ClineAgentOptions)
```
```typescript
interface ClineAgentOptions {
/** Enable debug logging (default: false) */
debug?: boolean
/** Custom Cline config directory (default: ~/.cline) */
clineDir?: string
}
```
The `clineDir` option lets you isolate configuration and task history per-application:
```typescript
const agent = new ClineAgent({
clineDir: "/tmp/my-app-cline",
})
```
### Methods
#### `initialize(params): Promise<InitializeResponse>`
Initialize the agent and negotiate protocol capabilities.
```typescript
const response = await agent.initialize({
clientCapabilities: {},
protocolVersion: 1,
})
// Response includes:
{
protocolVersion: "0.9.0",
agentCapabilities: {
loadSession: true,
promptCapabilities: { image: true, audio: false, embeddedContext: true },
mcpCapabilities: { http: true, sse: false }
},
agentInfo: { name: "cline", version: "2.2.3" },
authMethods: [
{ id: "cline-oauth", name: "Sign in with Cline", description: "..." },
{ id: "openai-codex-oauth", name: "Sign in with ChatGPT", description: "..." }
]
}
```
#### `newSession(params): Promise<NewSessionResponse>`
Create a new conversation session.
```typescript
const session = await agent.newSession({
cwd: "/path/to/project",
mcpServers: [
{
type: "stdio",
name: "filesystem",
command: "npx",
args: ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/dir"],
env: {},
},
],
})
// Response includes:
{
sessionId: "uuid-string",
modes: {
availableModes: [
{ id: "plan", name: "Plan", description: "Gather information and create a detailed plan" },
{ id: "act", name: "Act", description: "Execute actions to accomplish the task" }
],
currentModeId: "act"
},
models: {
currentModelId: "anthropic/claude-sonnet-4-5-20241022",
availableModels: [{ modelId: "anthropic/claude-3-5-sonnet-20241022", name: "..." }]
}
}
```
> **Note:** `newSession()` may throw an auth-required error if credentials are not configured yet.
#### `prompt(params): Promise<PromptResponse>`
Send a user prompt to the agent. This is the main method for interacting with Cline. Blocks until the agent finishes its turn.
```typescript
const response = await agent.prompt({
sessionId: session.sessionId,
prompt: [
{ type: "text", text: "Create a function that adds two numbers" },
],
})
// Response: { stopReason: "end_turn" | "max_tokens" | "cancelled" | "error" }
```
#### `cancel(params): Promise<void>`
Cancel an ongoing prompt operation.
```typescript
await agent.cancel({ sessionId: session.sessionId })
```
#### `setSessionMode(params): Promise<SetSessionModeResponse>`
Switch between plan and act modes.
```typescript
await agent.setSessionMode({ sessionId, modeId: "plan" })
```
#### `unstable_setSessionModel(params): Promise<SetSessionModelResponse>`
Change the model for the session. Model ID format depends on the inference provider. See NewSessionResponse object to get modelIds.
```typescript
await agent.unstable_setSessionModel({
sessionId,
modelId: "anthropic/claude-sonnet-4-20250514",
})
```
#### `authenticate(params): Promise<AuthenticateResponse>`
Authenticate with a provider. Opens a browser window for OAuth flow.
```typescript
await agent.authenticate({ methodId: "cline-oauth" })
```
Current methodIds we support:
| methodId | Description |
| -------------------- | ----------------------------- |
| `cline-oauth` | use cline inference provider |
| `openai-codex-oauth` | use your chatgpt subscription |
| more coming soon!... | |
#### `shutdown(): Promise<void>`
Clean up all resources. Call this when done.
```typescript
await agent.shutdown()
```
#### `setPermissionHandler(handler)`
Set a callback to handle tool permission requests.
```typescript
agent.setPermissionHandler((request, resolve) => {
resolve({ outcome: { outcome: "selected", optionId: "allow_once" } })
})
```
#### `emitterForSession(sessionId): ClineSessionEmitter`
Get the typed event emitter for a session.
```typescript
const emitter = agent.emitterForSession(session.sessionId)
```
#### `sessions` (read-only Map)
Access active sessions:
```typescript
for (const [sessionId, session] of agent.sessions) {
console.log(sessionId, session.cwd, session.mode)
}
```
## Full Example: Auto-Approve Agent
```typescript
import { ClineAgent } from "cline";
async function runTask(taskPrompt: string, cwd: string) {
const agent = new ClineAgent({ clineDir: "/Users/maxpaulus/.cline" });
await agent.initialize({
protocolVersion: 1,
clientCapabilities: {},
});
const { sessionId } = await agent.newSession({ cwd, mcpServers: [] });
// Auto-approve all tool calls
agent.setPermissionHandler(async (request) => {
const allow = request.options.find((o) => o.kind === "allow_once");
return {
outcome: allow
? { outcome: "selected", optionId: allow.optionId }
: { outcome: "cancelled" },
};
});
// Collect output
const output: string[] = [];
const emitter = agent.emitterForSession(sessionId);
emitter.on("agent_message_chunk", (p) => {
if (p.content.type === "text") output.push(p.content.text);
});
emitter.on("tool_call", (p) => {
console.log(`[tool] ${p.title}`);
});
const { stopReason } = await agent.prompt({
sessionId,
prompt: [{ type: "text", text: taskPrompt }],
});
console.log("\n--- Agent Output ---");
console.log(output.join(""));
console.log(`\nStop reason: ${stopReason}`);
await agent.shutdown();
}
runTask("Create a README.md for this project", process.cwd());
```
## Full Example: Interactive Permission Flow
```typescript
import { ClineAgent, type PermissionHandler } from "cline";
import * as readline from "readline";
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
const ask = (q: string) => new Promise<string>((res) => rl.question(q, res));
const interactivePermissions: PermissionHandler = async (request) => {
console.log(`\n⚠️ Permission: ${request.toolCall.title}`);
for (const [i, opt] of request.options.entries()) {
console.log(` ${i + 1}. [${opt.kind}] ${opt.name}`);
}
const choice = await ask("Choose (number): ");
const idx = parseInt(choice, 10) - 1;
const selected = request.options[idx];
if (selected) {
return {
outcome: { outcome: "selected", optionId: selected.optionId },
};
} else {
return { outcome: { outcome: "cancelled" } };
}
};
async function main() {
const agent = new ClineAgent({});
await agent.initialize({ protocolVersion: 1, clientCapabilities: {} });
const { sessionId } = await agent.newSession({
cwd: process.cwd(),
mcpServers: [],
});
agent.setPermissionHandler(interactivePermissions);
const emitter = agent.emitterForSession(sessionId);
emitter.on("agent_message_chunk", (p) => {
if (p.content.type === "text") process.stdout.write(p.content.text);
});
// Multi-turn conversation
while (true) {
const userInput = await ask("\n> ");
if (userInput === "exit") break;
const { stopReason } = await agent.prompt({
sessionId,
prompt: [{ type: "text", text: userInput }],
});
console.log(`\n[${stopReason}]`);
}
await agent.shutdown();
rl.close();
}
main();
```
## Exported Types
All types are re-exported from the `cline` package. Key types:
| Type | Description |
|------|-------------|
| `ClineAgent` | Main agent class |
| `ClineSessionEmitter` | Typed event emitter for session events |
| `ClineAgentOptions` | Constructor options |
| `ClineAcpSession` | Session metadata (read-only) |
| `ClineSessionEvents` | Event name → handler signature map |
| `PermissionHandler` | `(request, resolve) => void` callback |
| `PermissionResolver` | `(response) => void` callback |
| `SessionUpdate` | Union of all session update types |
| `SessionUpdateType` | Discriminator values (`"agent_message_chunk"`, `"tool_call"`, etc.) |
| `ToolCall` | Tool call details (id, title, kind, status, content) |
| `ToolCallUpdate` | Partial update to an existing tool call |
| `ToolCallStatus` | `"pending" \| "in_progress" \| "completed" \| "failed"` |
| `ToolKind` | `"read" \| "edit" \| "delete" \| "execute" \| "search" \| ...` |
| `StopReason` | `"end_turn" \| "cancelled" \| "error" \| "max_tokens" \| ...` |
| `ContentBlock` | `TextContent \| ImageContent \| AudioContent \| ...` |
| `McpServer` | MCP server configuration (stdio, http) |
| `PromptRequest` / `PromptResponse` | Prompt call types |
| `NewSessionRequest` / `NewSessionResponse` | Session creation types |
| `InitializeRequest` / `InitializeResponse` | Initialization types |
See the [ACP Schema](https://agentclientprotocol.com/protocol/schema) for the full type definitions.
## Relationship to ACP
The Cline SDK implements the [Agent Client Protocol](https://agentclientprotocol.com) `Agent` interface. The key difference from a standard ACP stdio agent is that the SDK uses an **event emitter pattern** instead of a transport connection:
| ACP Stdio (via `AcpAgent`) | SDK (via `ClineAgent`) |
|-----------------------------|------------------------|
| Session updates sent over JSON-RPC stdio | Session updates emitted via `ClineSessionEmitter` |
| Permissions requested via `connection.requestPermission()` | Permissions requested via `setPermissionHandler()` callback |
| Single process, single connection | Embeddable, multiple concurrent sessions |
If you need stdio-based ACP communication (e.g., for IDE integration), use the `cline` CLI binary directly. The SDK is for embedding Cline in your own Node.js processes.
+48 -9
View File
@@ -143,16 +143,35 @@ echo '{"cancel":false}'
<Steps>
<Step title="Create the hook file">
Save the script above as `~/Documents/Cline/Hooks/file-logger` (macOS/Linux) or create it through the Hooks UI.
Save the script above as `~/Documents/Cline/Hooks/file-logger` or create it through the Hooks UI.
</Step>
<Step title="Make it executable">
Run `chmod +x ~/Documents/Cline/Hooks/file-logger` in your terminal.
On macOS/Linux, run `chmod +x ~/Documents/Cline/Hooks/file-logger`.
</Step>
<Step title="Enable it">
<Step title="Enable it (macOS/Linux only)">
In Cline's Hooks tab, find "file-logger" under PreToolUse hooks and toggle it on.
</Step>
</Steps>
<Note>
On Windows, hooks are executed with PowerShell and run whenever the hook file exists. In this
foundation PR, hook enable/disable toggling is not yet supported on Windows.
</Note>
<Note>
Coming next: JSON-backed hook enabled/disabled state across platforms, so toggle behavior is
consistent on Windows, macOS, and Linux.
</Note>
<Note>
Hook filenames are platform-specific:
- **Windows**: only `HookName.ps1` is supported (PowerShell script files)
- **macOS/Linux**: only extensionless `HookName` is supported (executable files like bash scripts or binaries)
Wrong-platform naming is ignored by hook discovery.
</Note>
### Test It
Ask Cline to read any file in your project: "What's in package.json?"
@@ -190,9 +209,15 @@ Every hook receives a JSON object with common fields plus hook-specific data:
```json
{
"taskId": "abc123",
"hookName": "PreToolUse",
"clineVersion": "3.17.0",
"timestamp": 1736654400000,
"workspacePath": "/path/to/project",
"timestamp": "1736654400000",
"workspaceRoots": ["/path/to/project"],
"userId": "user_123",
"model": {
"provider": "openrouter",
"slug": "anthropic/claude-sonnet-4.5"
},
// Hook-specific field (name matches hook type in camelCase)
"taskStart": {
@@ -201,6 +226,17 @@ Every hook receives a JSON object with common fields plus hook-specific data:
}
```
`model.provider` and `model.slug` are machine-stable identifiers for the active provider/model at hook execution time. If unavailable, Cline sends deterministic fallback values: `"unknown"`.
<Note>
Migration note for existing hook scripts:
- `timestamp` is a string (milliseconds since epoch), not a number
- `workspaceRoots` is an array of workspace root paths and replaces the old singular `workspacePath`
If your scripts previously read `.workspacePath`, switch to `.workspaceRoots[0]` (or iterate all roots).
</Note>
The hook-specific field name matches the hook type:
- `taskStart`, `taskResume`, `taskCancel`, `taskComplete` contain `{ task: string }`
- `preToolUse` contains `{ tool: string, parameters: object }`
@@ -420,7 +456,7 @@ Inject project-specific information when a task begins:
# TaskStart hook
INPUT=$(cat)
WORKSPACE=$(echo "$INPUT" | jq -r '.workspacePath')
WORKSPACE=$(echo "$INPUT" | jq -r '.workspaceRoots[0] // empty')
# Read project info if available
if [[ -f "$WORKSPACE/.project-context" ]]; then
@@ -444,14 +480,17 @@ cline config set hooks-enabled=true
```
<Note>
CLI hooks are only supported on macOS and Linux.
Windows hooks require PowerShell (`powershell.exe`) available on your PATH.
</Note>
## Troubleshooting
**Hook not running?**
- Check that the file is executable (`chmod +x hookname`)
- Verify the hook is enabled (toggle is on in the Hooks tab)
- On macOS/Linux, check that the file is executable (`chmod +x hookname`)
- On Windows, ensure PowerShell is available (`powershell -NoProfile -Command "$PSVersionTable.PSVersion"`)
- On Windows, ensure the hook file is named `<HookName>.ps1` (for example `PreToolUse.ps1`)
- On macOS/Linux, ensure the hook file uses extensionless `<HookName>` naming (for example `PreToolUse`)
- On macOS/Linux, verify the hook is enabled (toggle is on in the Hooks tab)
- Check that Hooks are enabled globally in Settings
**Hook output not parsed?**
+37 -3
View File
@@ -110,13 +110,13 @@
"cline-cli/samples/github-issue-rca",
"cline-cli/samples/github-integration",
"cline-cli/samples/github-pr-review",
"cline-cli/samples/cve-scan",
"cline-cli/samples/model-orchestration",
"cline-cli/samples/worktree-workflows"
]
},
"cline-cli/configuration",
"cline-cli/acp-editor-integrations",
"cline-sdk/overview",
"cline-cli/cli-reference"
]
},
@@ -287,7 +287,8 @@
{
"group": "Control Other Cline Features",
"pages": [
"enterprise-solutions/configuration/infrastructure-configuration/control-other-cline-features/yolo-mode"
"enterprise-solutions/configuration/infrastructure-configuration/control-other-cline-features/yolo-mode",
"enterprise-solutions/configuration/infrastructure-configuration/control-other-cline-features/mcp-marketplace"
]
},
{
@@ -297,7 +298,36 @@
"enterprise-solutions/monitoring/telemetry",
"enterprise-solutions/monitoring/opentelemetry"
]
}
},
"enterprise-solutions/api-reference"
]
}
]
},
{
"tab": "API",
"icon": "code",
"groups": [
{
"group": "Cline API",
"pages": [
"api/overview",
"api/getting-started",
"api/authentication"
]
},
{
"group": "Endpoints",
"pages": [
"api/chat-completions"
]
},
{
"group": "Reference",
"pages": [
"api/models",
"api/errors",
"api/sdk-examples"
]
}
]
@@ -607,6 +637,10 @@
{
"source": "/features/skills",
"destination": "/customization/skills"
},
{
"source": "/api/reference",
"destination": "/api/overview"
}
],
"search": {
+208
View File
@@ -0,0 +1,208 @@
---
title: "Enterprise API Reference"
sidebarTitle: "API Reference"
description: "REST API endpoints for managing users, organizations, billing, plans, and API keys."
---
The Enterprise API provides REST endpoints for account management, organization administration, billing, and API key management. These are separate from the [Chat Completions API](/api/reference), which handles model inference.
## Base URL
```
https://api.cline.bot
```
## Authentication
All endpoints require a Bearer token in the `Authorization` header:
```bash
Authorization: Bearer YOUR_AUTH_TOKEN
```
Use the same API key or account auth token described in the [public API reference](/api/reference#authentication).
## Quick Example
```bash
# Get your user profile
curl https://api.cline.bot/api/v1/users/me \
-H "Authorization: Bearer YOUR_AUTH_TOKEN"
```
```json
{
"id": "user_abc123",
"email": "you@company.com",
"name": "Your Name",
"active_account_id": "org_xyz789"
}
```
---
## Users
Manage user accounts, accept terms, check balances, view usage, and configure payment methods.
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | `/api/v1/users/me` | Get current user profile |
| `PATCH` | `/api/v1/users/me` | Update current user profile |
| `DELETE` | `/api/v1/users/me` | Delete current user account |
| `POST` | `/api/v1/users/me/accept-terms` | Accept terms of service |
| `GET` | `/api/v1/users/me/remote-config` | Get remote configuration for the current user |
| `PUT` | `/api/v1/users/active-account` | Switch active account (personal or organization) |
| `GET` | `/api/v1/users/{id}/balance` | Get credit balance |
| `GET` | `/api/v1/users/{id}/usages` | Get usage history |
### Payments and Credits
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | `/api/v1/users/{id}/payments` | List payment history |
| `GET` | `/api/v1/users/{id}/payments/{paymentId}` | Get payment details |
| `GET` | `/api/v1/users/{id}/payments/{paymentId}/status` | Check payment status |
| `GET` | `/api/v1/users/{id}/payments/provider/{paymentId}` | Get provider-side payment details |
| `POST` | `/api/v1/users/credits/checkout` | Start a credit purchase checkout |
| `POST` | `/api/v1/users/{id}/credits/purchase` | Purchase credits directly |
### Billing Configuration
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | `/api/v1/users/{id}/auto-top-up` | Get auto top-up settings |
| `PUT` | `/api/v1/users/{id}/auto-top-up` | Configure auto top-up |
| `GET` | `/api/v1/users/{id}/payment-method/default` | Get default payment method |
| `POST` | `/api/v1/users/{id}/payment-method/setup-session` | Start payment method setup |
| `GET` | `/api/v1/users/{id}/promotions` | List active promotions |
---
## Organizations
Create and manage organizations. Organization admins can configure remote settings, manage members, and control billing.
| Method | Endpoint | Description |
|--------|----------|-------------|
| `POST` | `/api/v1/organizations` | Create a new organization |
| `GET` | `/api/v1/organizations/{id}` | Get organization details |
| `PUT` | `/api/v1/organizations/{id}` | Update organization settings |
| `DELETE` | `/api/v1/organizations/{id}` | Delete an organization |
| `GET` | `/api/v1/organizations/{id}/api-keys` | List organization API keys |
| `GET` | `/api/v1/organizations/{id}/remote-config` | Get remote config for the org |
| `GET` | `/api/v1/organizations/{orgId}/metrics` | Get organization usage metrics |
---
## Organization Members
Manage who has access to the organization and what role they hold.
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | `/api/v1/organizations/{orgId}/members` | List all members |
| `DELETE` | `/api/v1/organizations/{orgId}/members` | Remove members |
| `GET` | `/api/v1/organizations/{orgId}/members/available-roles` | List assignable roles |
| `PUT` | `/api/v1/organizations/{orgId}/members/{memberId}/role` | Change a member's role |
| `GET` | `/api/v1/organizations/{orgId}/members/{memberId}/usages` | Get a member's usage |
<Tip>
For a walkthrough of member management in the UI, see [Managing Members](/enterprise-solutions/team-management/managing-members).
</Tip>
---
## Organization Invites
Invite new members to join your organization.
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | `/api/v1/organizations/{orgId}/invites` | List pending invites |
| `POST` | `/api/v1/organizations/{orgId}/invites` | Send new invites |
| `GET` | `/api/v1/organizations/{orgId}/invites/count` | Get invite count |
| `DELETE` | `/api/v1/organizations/{orgId}/invites/{inviteId}` | Revoke an invite |
| `POST` | `/api/v1/invites/accept` | Accept an invite (called by the invitee) |
---
## Organization Balance and Payments
Manage credits and payments at the organization level. These mirror the user-level payment endpoints but operate on the organization's account.
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | `/api/v1/organizations/{orgId}/balance` | Get org credit balance |
| `GET` | `/api/v1/organizations/{orgId}/payments` | List payment history |
| `GET` | `/api/v1/organizations/{orgId}/payments/{paymentId}` | Get payment details |
| `GET` | `/api/v1/organizations/{orgId}/payments/{paymentId}/status` | Check payment status |
| `GET` | `/api/v1/organizations/{orgId}/payments/provider/{paymentId}` | Provider-side payment details |
| `POST` | `/api/v1/organizations/{orgId}/credits/checkout` | Start credit checkout |
| `POST` | `/api/v1/organizations/{orgId}/credits/purchase` | Purchase credits |
| `GET` | `/api/v1/organizations/{orgId}/auto-top-up` | Get auto top-up config |
| `PUT` | `/api/v1/organizations/{orgId}/auto-top-up` | Configure auto top-up |
| `GET` | `/api/v1/organizations/{orgId}/payment-method/default` | Get default payment method |
| `POST` | `/api/v1/organizations/{orgId}/payment-method/setup-session` | Start payment method setup |
| `GET` | `/api/v1/organizations/{id}/promotions` | List active promotions |
---
## Organization Plans
Subscribe to, upgrade, or cancel plans. Manage seat counts for your team.
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | `/api/v1/plans` | List all available plans |
| `GET` | `/api/v1/organizations/{orgId}/plan` | Get current plan |
| `GET` | `/api/v1/organizations/{orgId}/plan/history` | View plan change history |
| `GET` | `/api/v1/organizations/{orgId}/plan/{planId}` | Get specific plan details |
| `POST` | `/api/v1/organizations/{orgId}/plan` | Subscribe to a plan |
| `PUT` | `/api/v1/organizations/{orgId}/plan/seats` | Update seat count |
| `DELETE` | `/api/v1/organizations/{orgId}/plan/{planId}` | Cancel a plan |
---
## Organization Usage
Track token consumption and costs across your organization.
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | `/api/v1/organizations/{orgId}/usages` | Get aggregated usage data |
<Tip>
For dashboards and monitoring, see [Monitoring Overview](/enterprise-solutions/monitoring/overview) and [Telemetry](/enterprise-solutions/monitoring/telemetry).
</Tip>
---
## API Keys
Create and manage API keys for programmatic access. Keys created here work with both the [Chat Completions API](/api/reference) and the endpoints on this page.
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | `/api/v1/api-keys` | List your API keys |
| `POST` | `/api/v1/api-keys` | Create a new API key |
| `DELETE` | `/api/v1/api-keys/{key_id}` | Delete an API key |
---
## Related
<CardGroup cols={2}>
<Card title="Chat Completions API" icon="code" href="/api/reference">
The public inference API for sending prompts and receiving completions.
</Card>
<Card title="SSO Setup" icon="key" href="/enterprise-solutions/sso-setup">
Configure single sign-on for your organization.
</Card>
<Card title="Managing Members" icon="users" href="/enterprise-solutions/team-management/managing-members">
Add, remove, and manage member roles in the UI.
</Card>
<Card title="Monitoring" icon="chart-line" href="/enterprise-solutions/monitoring/overview">
Track usage, costs, and telemetry across your organization.
</Card>
</CardGroup>
@@ -0,0 +1,298 @@
---
title: "MCP Marketplace"
sidebarTitle: "MCP Marketplace"
description: "Enterprise controls for MCP Marketplace access, server allowlisting, and remote MCP server management"
---
The MCP Marketplace lets developers discover and install MCP servers that extend Cline's capabilities. For Enterprise administrators, this page covers how to control marketplace access, restrict which servers are available, and push pre-configured MCP servers to your organization.
<Note>
For complete details about the MCP Marketplace and how developers use it, see [MCP Made Easy](/mcp/mcp-marketplace).
</Note>
## Overview
Enterprise administrators have four configuration options to govern MCP server usage across their organization:
| Setting | Purpose |
|---------|---------|
| `mcpMarketplaceEnabled` | Enable or disable the MCP Marketplace entirely |
| `allowedMCPServers` | Restrict the marketplace to only approved MCP servers |
| `remoteMCPServers` | Push pre-configured remote MCP servers to all users |
| `blockPersonalRemoteMCPServers` | Prevent users from adding their own remote MCP servers |
These settings are applied through your organization's [remote configuration](/enterprise-solutions/configuration/remote-configuration/overview) and take effect immediately for all team members.
## Disabling the MCP Marketplace
To completely disable the MCP Marketplace for your organization, set `mcpMarketplaceEnabled` to `false`:
```json
{
"mcpMarketplaceEnabled": false
}
```
When `mcpMarketplaceEnabled` is set to `false`:
- The MCP Marketplace tab is hidden from all users
- Users cannot browse or install MCP servers from the marketplace
- Locally configured MCP servers are blocked
- Enterprise policy takes precedence over individual preferences
When `mcpMarketplaceEnabled` is set to `true` or omitted:
- Users can freely browse and install MCP servers from the marketplace
- No organizational restrictions apply to marketplace access
<Warning>
Disabling the marketplace entirely also blocks locally configured MCP servers. If you want to allow specific servers while restricting others, use the allowlist approach described below instead.
</Warning>
## Restricting the Marketplace to Approved Servers
Rather than disabling the marketplace entirely, you can restrict it to a curated list of approved MCP servers using the `allowedMCPServers` setting. This is the recommended approach for most enterprises — it lets developers benefit from MCP while ensuring only vetted servers are available.
### Configuration
Add an `allowedMCPServers` array to your remote configuration. Each entry requires an `id` field set to the server's GitHub repository path:
```json
{
"allowedMCPServers": [
{ "id": "github.com/modelcontextprotocol/server-filesystem" },
{ "id": "github.com/modelcontextprotocol/server-github" },
{ "id": "github.com/your-org/internal-mcp-server" }
]
}
```
### How It Works
When `allowedMCPServers` is configured:
- The marketplace catalog is filtered to show **only** the servers in your allowlist
- Users can browse, view details, and install any server on the list
- Servers not on the list are completely hidden from the marketplace
- The allowlist applies to all team members in the organization
When `allowedMCPServers` is omitted or `undefined`:
- The full marketplace catalog is available with no restrictions
When `allowedMCPServers` is set to an empty array (`[]`):
- The marketplace shows no servers — effectively disabling installation while keeping the UI visible
### Finding Server IDs
The `id` for each allowed server is its GitHub repository path (without the `https://` prefix). For example:
| Server | ID |
|--------|----|
| Filesystem | `github.com/modelcontextprotocol/server-filesystem` |
| GitHub | `github.com/modelcontextprotocol/server-github` |
| Custom internal server | `github.com/your-org/your-mcp-server` |
You can find the correct ID by checking the `githubUrl` field of any server in the [MCP Marketplace](/mcp/mcp-marketplace) and removing the `https://` prefix.
## Pushing Pre-Configured Remote MCP Servers
Use `remoteMCPServers` to push MCP servers directly to all users without requiring them to install anything from the marketplace. This is ideal for internal MCP servers or third-party servers that need specific configuration.
### Configuration
```json
{
"remoteMCPServers": [
{
"name": "Internal Code Search",
"url": "https://mcp.internal.yourcompany.com/code-search",
"alwaysEnabled": true,
"headers": {
"Authorization": "Bearer ${AUTH_TOKEN}"
}
},
{
"name": "Documentation Server",
"url": "https://mcp.internal.yourcompany.com/docs",
"alwaysEnabled": false
}
]
}
```
### Remote Server Options
Each remote MCP server entry supports the following fields:
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `name` | string | Yes | Display name for the server |
| `url` | string | Yes | The URL endpoint of the MCP server |
| `alwaysEnabled` | boolean | No | When `true`, users cannot disable this server |
| `headers` | object | No | Custom HTTP headers for authentication |
### Always-Enabled Servers
When `alwaysEnabled` is set to `true`:
- The server is automatically active for all users
- Users cannot toggle the server off
- The server appears in the user's MCP configuration but the disable control is locked
- This is useful for compliance, security, or internal tooling servers that must always be available
## Blocking Personal Remote MCP Servers
To prevent users from adding their own remote MCP servers, set `blockPersonalRemoteMCPServers` to `true`:
```json
{
"blockPersonalRemoteMCPServers": true
}
```
When `blockPersonalRemoteMCPServers` is `true`:
- Users cannot add or configure remote MCP servers on their own
- Only servers defined in the organization's `remoteMCPServers` configuration are available
- This ensures all remote MCP connections go through approved, organization-managed endpoints
When `blockPersonalRemoteMCPServers` is `false` or omitted:
- Users can freely add their own remote MCP server connections
## Combined Configuration Examples
### Locked-Down Environment
For organizations that need strict control over all MCP server access:
```json
{
"mcpMarketplaceEnabled": true,
"allowedMCPServers": [
{ "id": "github.com/modelcontextprotocol/server-filesystem" },
{ "id": "github.com/modelcontextprotocol/server-github" }
],
"remoteMCPServers": [
{
"name": "Internal API Gateway",
"url": "https://mcp.internal.yourcompany.com/gateway",
"alwaysEnabled": true,
"headers": {
"X-Api-Key": "org-managed-key"
}
}
],
"blockPersonalRemoteMCPServers": true
}
```
This configuration:
- Allows the marketplace but limits it to two approved servers
- Pushes an always-enabled internal MCP server to all users
- Blocks users from adding their own remote MCP servers
### Open Environment with Internal Servers
For organizations that want flexibility with internal server access:
```json
{
"remoteMCPServers": [
{
"name": "Company Knowledge Base",
"url": "https://mcp.yourcompany.com/kb",
"alwaysEnabled": true
}
]
}
```
This configuration:
- Leaves the full marketplace open (no `allowedMCPServers` restriction)
- Ensures all developers have access to the company knowledge base
- Allows users to add their own remote MCP servers
### Marketplace Disabled with Internal Servers Only
For organizations that want to fully manage the MCP experience:
```json
{
"mcpMarketplaceEnabled": false,
"remoteMCPServers": [
{
"name": "Approved Code Assistant",
"url": "https://mcp.internal.yourcompany.com/code-assist",
"alwaysEnabled": true
},
{
"name": "Internal Docs Search",
"url": "https://mcp.internal.yourcompany.com/docs",
"alwaysEnabled": true
}
],
"blockPersonalRemoteMCPServers": true
}
```
This configuration:
- Disables the marketplace completely
- Provides only organization-managed MCP servers
- Prevents users from adding any additional remote servers
## Enterprise Policy Recommendations
### Recommended Approach
Most organizations should **use the allowlist** (`allowedMCPServers`) rather than disabling the marketplace entirely. This gives developers access to useful tools while ensuring security review of each server.
<AccordionGroup>
<Accordion title="Security Review Process" icon="shield">
Before adding an MCP server to your allowlist:
- Review the server's source code on GitHub
- Evaluate the server's permissions and data access patterns
- Check for active maintenance and security practices
- Assess whether the server's data handling meets your compliance requirements
- Test the server in a sandbox environment before approving
</Accordion>
<Accordion title="Internal MCP Servers" icon="building">
For internal tooling, use `remoteMCPServers` with `alwaysEnabled: true`:
- Connect Cline to internal APIs, databases, and knowledge bases
- Ensure consistent access across all developers
- Manage authentication centrally through custom headers
- Use `blockPersonalRemoteMCPServers` to prevent shadow IT
</Accordion>
<Accordion title="Compliance Considerations" icon="clipboard-check">
MCP servers can access external APIs and process data:
- Audit which servers handle sensitive data
- Ensure servers comply with your data residency requirements
- Document approved servers in your security policies
- Regularly review and update your allowlist
</Accordion>
</AccordionGroup>
### Recommendations by Organization Size
#### Small Teams (520 developers)
- **Marketplace:** Open or lightly restricted with an allowlist
- **Remote Servers:** Push internal servers as needed
- **Personal Servers:** Allow with guidance
- **Review Cadence:** Quarterly allowlist review
#### Medium Organizations (20100 developers)
- **Marketplace:** Restricted to an approved allowlist
- **Remote Servers:** Push internal servers with `alwaysEnabled`
- **Personal Servers:** Consider blocking (`blockPersonalRemoteMCPServers: true`)
- **Review Cadence:** Monthly allowlist review
#### Large Enterprises (100+ developers)
- **Marketplace:** Strictly restricted to a vetted allowlist
- **Remote Servers:** All MCP access through organization-managed servers
- **Personal Servers:** Blocked (`blockPersonalRemoteMCPServers: true`)
- **Review Cadence:** Formal approval process for new servers with security review
## Support & Questions
For help configuring MCP Marketplace policies:
- Review [Remote Configuration Overview](/enterprise-solutions/configuration/remote-configuration/overview)
- See [MCP Made Easy](/mcp/mcp-marketplace) for marketplace functionality details
- See [MCP Overview](/mcp/mcp-overview) for general MCP concepts
- Contact your Enterprise support representative
- Join our [Discord](https://discord.gg/cline) for community discussion
+15 -1
View File
@@ -9,7 +9,21 @@ Cline Enterprise integrates with your identity provider (IdP) via **WorkOS AuthK
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).
If you haven't completed initial onboarding, start with [Onboarding](/enterprise-solutions/onboarding).
### Video Walkthrough
<Frame>
<iframe
src="https://www.youtube.com/embed/QC7mzXLjIH8"
title="SSO Setup with WorkOS"
width="100%"
style={{ aspectRatio: "16/9" }}
frameBorder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
/>
</Frame>
## Where setup happens
SSO setup spans two places:
-48
View File
@@ -1,48 +0,0 @@
---
title: "Dictation (Deprecated)"
description: "Voice input feature has been removed from Cline"
---
# Dictation Feature Removed
The dictation (voice-to-text) feature has been removed from Cline as of this release.
## What Happened?
The voice input feature that allowed you to speak to Cline instead of typing has been discontinued and is no longer available in the extension.
## Alternative Workflows
While the built-in dictation feature is no longer available, you can still work efficiently with Cline using these approaches:
### 1. System-Level Voice Input
Both macOS and Windows offer built-in dictation features that work across all applications:
- **macOS**: Press `Fn` twice (or `Fn Fn`) to activate dictation in any text field
- **Windows**: Press `Windows + H` to open voice typing
- **Linux**: Various desktop environments offer voice input through accessibility features
These system-level tools will work in Cline's chat input just like any other text field.
### 2. Copy-Paste from Voice Notes
If you prefer to think out loud:
1. Use your phone's voice recorder or a voice memo app
2. Transcribe using your preferred tool (many phones have built-in transcription)
3. Copy and paste the transcribed text into Cline
### 3. Third-Party Transcription Tools
Many standalone transcription tools can be used alongside Cline:
- Browser-based transcription services
- Desktop transcription applications
- AI-powered note-taking apps with transcription features
## Why Was It Removed?
The dictation feature was removed to streamline Cline's core functionality and focus development efforts on the primary AI assistance capabilities.
## Questions?
If you have questions about this change or need help setting up alternative voice input methods, please reach out through Cline's support channels.
+6 -4
View File
@@ -1,6 +1,6 @@
---
title: "MiniMax"
description: "Learn how to configure and use MiniMax models with Cline. Access MiniMax-M2 series models with large context windows and prompt caching."
description: "Learn how to configure and use MiniMax models with Cline. Access MiniMax-M2 series models with large context windows, prompt caching, and reasoning support."
---
MiniMax provides AI models with large context windows and competitive pricing, featuring the MiniMax-M2 series.
@@ -18,9 +18,10 @@ MiniMax provides AI models with large context windows and competitive pricing, f
Cline supports the following MiniMax models:
- `MiniMax-M2.1` (Default) - Latest model with 192K context and prompt caching ($0.30/$1.20 per 1M tokens)
- `MiniMax-M2.5` (Default) - Latest model with 192K context, prompt caching, and reasoning/thinking support ($0.30/$1.20 per 1M tokens)
- `MiniMax-M2.1` - Previous generation with 192K context and prompt caching ($0.30/$1.20 per 1M tokens)
- `MiniMax-M2.1-lightning` - Fast variant with higher output pricing ($0.30/$2.40 per 1M tokens)
- `MiniMax-M2` - Previous generation with 192K context ($0.30/$1.20 per 1M tokens)
- `MiniMax-M2` - Earlier generation with 192K context ($0.30/$1.20 per 1M tokens)
### Configuration in Cline
@@ -32,5 +33,6 @@ Cline supports the following MiniMax models:
### Tips and Notes
- **Large Context:** All models support 192K token context windows.
- **Prompt Caching:** M2.1 models support prompt caching for reduced costs on repeated queries.
- **Reasoning Support:** M2.5 supports extended thinking/reasoning for complex tasks.
- **Prompt Caching:** M2.5 and M2.1 models support prompt caching for reduced costs on repeated queries.
- **Pricing:** Check the [MiniMax pricing page](https://www.minimax.io/platform/document/pricing) for current rates.
@@ -177,4 +177,22 @@ describe("FailureClassifier", () => {
expect(failures).toEqual([])
})
})
describe("YAML Safety (JSON_SCHEMA)", () => {
it("rejects patterns file with custom YAML tags", () => {
// Write a temp YAML file with a !!js/function tag
const fs = require("fs")
const path = require("path")
const os = require("os")
const tmpFile = path.join(os.tmpdir(), "unsafe-patterns.yaml")
fs.writeFileSync(
tmpFile,
`version: "1.0"\npatterns:\n - name: !!js/function 'function(){ return "pwned" }'\n`,
)
expect(() => new FailureClassifier(tmpFile)).toThrow()
fs.unlinkSync(tmpFile)
})
})
})
+1 -1
View File
@@ -43,7 +43,7 @@ export class FailureClassifier {
private loadPatternsFromYaml(filePath: string): FailurePatternsConfig {
const content = fs.readFileSync(filePath, "utf-8")
const config = yaml.load(content) as FailurePatternsConfig
const config = yaml.load(content, { schema: yaml.JSON_SCHEMA }) as FailurePatternsConfig
if (!config.version || !config.patterns) {
throw new Error("Invalid patterns YAML: missing version or patterns")
+145 -75
View File
@@ -11,15 +11,16 @@
* npx tsx evals/smoke-tests/run-smoke-tests.ts [options]
*
* Options:
* --provider <name> Run tests for a specific provider (default: all configured)
* --trials <n> Number of trials per test (default: 3)
* --scenario <name> Run a specific scenario (default: all)
* --model <id> Override model for all scenarios
* --output <file> Write JSON results to file
*/
import { execSync, spawn } from "child_process"
import * as fs from "fs"
import * as path from "path"
import * as dotenv from "dotenv"
import { MetricsCalculator } from "../analysis/src/metrics"
// Default provider and model for smoke tests
@@ -43,24 +44,23 @@ function checkClineCli(): boolean {
// Use user's existing Cline config (already has auth configured)
// For CI, this would be set up by the auth step before tests run
const CLINE_CONFIG_DIR = path.join(process.env.HOME || "", ".cline")
const configuredAuthCache = new Set<string>()
// Configure authentication using CLINE_API_KEY environment variable
// Returns success if auth is configured, error message otherwise
function configureAuth(): { ok: boolean; error?: string } {
const apiKey = process.env.CLINE_API_KEY
if (!apiKey) {
return {
ok: false,
error: "CLINE_API_KEY environment variable not set",
}
}
function configureAuth(options: { provider: string; apiKey: string; modelId: string; baseUrl?: string }): {
ok: boolean
error?: string
} {
// Ensure config directory exists
fs.mkdirSync(CLINE_CONFIG_DIR, { recursive: true })
try {
// Run quick auth setup (non-interactive when all flags provided)
execSync(`cline auth --config "${CLINE_CONFIG_DIR}" -p ${DEFAULT_PROVIDER} -k "${apiKey}" -m "${DEFAULT_MODEL}"`, {
const args = [`cline auth --config "${CLINE_CONFIG_DIR}"`, `-p "${options.provider}"`, `-k "${options.apiKey}"`, `-m "${options.modelId}"`]
if (options.baseUrl) {
args.push(`-b "${options.baseUrl}"`)
}
execSync(args.join(" "), {
encoding: "utf-8",
timeout: 10000,
stdio: "pipe",
@@ -74,6 +74,22 @@ function configureAuth(): { ok: boolean; error?: string } {
}
}
function loadEnvFiles(): void {
const repoRoot = path.resolve(__dirname, "..", "..")
const envFiles = [path.join(repoRoot, ".env"), path.join(repoRoot, ".env.local")]
for (const envPath of envFiles) {
if (fs.existsSync(envPath)) {
dotenv.config({ path: envPath, override: false })
}
}
}
interface ScenarioAuthConfig {
apiKeyEnv?: string
baseUrlEnv?: string
modelId?: string
}
// Smoke test scenario definition
interface SmokeScenario {
id: string
@@ -85,6 +101,9 @@ interface SmokeScenario {
expectedContent?: { file: string; contains: string }[] // Content checks
timeout: number // Seconds
models?: string[] // Optional: override default models for this scenario
provider?: string // Provider override (defaults to DEFAULT_PROVIDER)
requiredEnv?: string[] // Env vars required for this scenario to run
auth?: ScenarioAuthConfig // Optional auth overrides for provider-specific scenarios
}
// Load scenarios from disk
@@ -108,6 +127,54 @@ function loadScenarios(scenariosDir: string): SmokeScenario[] {
return scenarios
}
function getMissingEnvVars(requiredEnv: string[] | undefined): string[] {
if (!requiredEnv || requiredEnv.length === 0) {
return []
}
return requiredEnv.filter((key) => !process.env[key])
}
function getScenarioProvider(scenario: SmokeScenario): string {
return scenario.provider || DEFAULT_PROVIDER
}
function ensureScenarioAuth(scenario: SmokeScenario, modelId: string): { ok: boolean; error?: string } {
const provider = getScenarioProvider(scenario)
const authModelId = scenario.auth?.modelId || modelId
const apiKeyEnv = scenario.auth?.apiKeyEnv || (provider === DEFAULT_PROVIDER ? "CLINE_API_KEY" : undefined)
const apiKey = apiKeyEnv ? process.env[apiKeyEnv] : undefined
const baseUrl = scenario.auth?.baseUrlEnv ? process.env[scenario.auth.baseUrlEnv] : undefined
const authCacheKey = `${provider}|${authModelId}|${baseUrl || ""}|${apiKeyEnv || ""}`
if (apiKey) {
if (configuredAuthCache.has(authCacheKey)) {
return { ok: true }
}
const result = configureAuth({ provider, apiKey, modelId: authModelId, baseUrl })
if (result.ok) {
configuredAuthCache.add(authCacheKey)
}
return result
}
// For default provider, local developers can rely on existing ~/.cline auth.
if (provider === DEFAULT_PROVIDER) {
return { ok: true }
}
if (!apiKeyEnv) {
return {
ok: false,
error: `Provider '${provider}' requires auth.apiKeyEnv in scenario config or preconfigured credentials`,
}
}
return {
ok: false,
error: `Missing required auth env var '${apiKeyEnv}' for provider '${provider}'`,
}
}
// Run a single trial
interface TrialResult {
passed: boolean
@@ -318,6 +385,8 @@ interface SmokeTestReport {
// Main execution
async function main() {
loadEnvFiles()
const args = process.argv.slice(2)
// Parse arguments
@@ -357,24 +426,6 @@ async function main() {
process.exit(1)
}
// Configure authentication if CLINE_API_KEY is set
// Otherwise use existing auth from ~/.cline
if (process.env.CLINE_API_KEY) {
console.log("Configuring authentication from CLINE_API_KEY...")
const authResult = configureAuth()
if (!authResult.ok) {
console.error("")
console.error("ERROR: Authentication failed")
console.error(` ${authResult.error}`)
console.error("")
process.exit(1)
}
console.log("Authentication configured")
} else {
console.log("Using existing authentication from ~/.cline")
}
console.log("")
// Load scenarios
const scenariosDir = path.join(__dirname, "scenarios")
let scenarios = loadScenarios(scenariosDir)
@@ -392,12 +443,40 @@ async function main() {
}
}
const skippedByEnv: Array<{ id: string; missingEnv: string[] }> = []
if (selectedScenario) {
const missingEnv = getMissingEnvVars(scenarios[0].requiredEnv)
if (missingEnv.length > 0) {
console.error(`Scenario '${selectedScenario}' missing required env: ${missingEnv.join(", ")}`)
process.exit(1)
}
} else {
scenarios = scenarios.filter((scenario) => {
const missingEnv = getMissingEnvVars(scenario.requiredEnv)
if (missingEnv.length > 0) {
skippedByEnv.push({ id: scenario.id, missingEnv })
return false
}
return true
})
}
// Filter models
let models = MODELS
if (selectedModel) {
models = [selectedModel]
}
if (scenarios.length === 0) {
console.error("No runnable scenarios after env filtering")
if (skippedByEnv.length > 0) {
for (const skipped of skippedByEnv) {
console.error(` - ${skipped.id}: missing ${skipped.missingEnv.join(", ")}`)
}
}
process.exit(1)
}
// Create results directory with timestamp
const timestamp = new Date().toISOString().replace(/[:.]/g, "-")
const resultsBaseDir = path.join(__dirname, "results")
@@ -406,11 +485,17 @@ async function main() {
// Models are now always explicit
const resolvedModels = models
const providersInRun = [...new Set(scenarios.map((scenario) => getScenarioProvider(scenario)))]
console.log(`Running ${scenarios.length} scenarios × ${models.length} models × ${trials} trials`)
console.log(`Provider: ${DEFAULT_PROVIDER}`)
console.log(`Providers: ${providersInRun.join(", ")}`)
console.log(`Models: ${resolvedModels.join(", ")}`)
console.log(`Scenarios: ${scenarios.map((s) => s.id).join(", ")}`)
if (skippedByEnv.length > 0) {
console.log(
`Skipped by env: ${skippedByEnv.map((skipped) => `${skipped.id} (missing: ${skipped.missingEnv.join(", ")})`).join("; ")}`,
)
}
console.log(`Results: ${resultsDir}`)
console.log(`Parallel: ${parallel ? `yes (limit: ${parallelLimit})` : "no"}`)
console.log("")
@@ -420,7 +505,7 @@ async function main() {
// Build list of all scenario+model combinations
interface TestJob {
scenario: Scenario
scenario: SmokeScenario
modelId: string
}
const jobs: TestJob[] = []
@@ -437,6 +522,26 @@ async function main() {
const logDir = path.join(resultsDir, scenario.id, modelId)
fs.mkdirSync(logDir, { recursive: true })
const authResult = ensureScenarioAuth(scenario, modelId)
if (!authResult.ok) {
const trialResults = Array.from({ length: trials }, () => ({
passed: false,
error: authResult.error || "Scenario authentication failed",
durationMs: 0,
stdout: "",
stderr: "",
}))
return {
scenarioId: scenario.id,
scenarioName: scenario.name,
model: modelId,
modelId,
trials: trialResults,
metrics: metricsCalc.calculateTaskMetrics(trialResults.map((t) => t.passed)),
status: metricsCalc.getTaskStatus(trialResults.map((t) => t.passed)),
}
}
const trialResults: TrialResult[] = []
const trialWorkdirs: string[] = []
@@ -504,58 +609,23 @@ async function main() {
// Sequential execution
for (const job of jobs) {
console.log(`\n[${job.scenario.id}] ${job.scenario.name} (${job.modelId})`)
const logDir = path.join(resultsDir, job.scenario.id, job.modelId)
fs.mkdirSync(logDir, { recursive: true })
const trialResults: TrialResult[] = []
const trialWorkdirs: string[] = []
for (let t = 0; t < trials; t++) {
const trialWorkdir = path.join(logDir, `workspace-trial-${t + 1}`)
trialWorkdirs.push(trialWorkdir)
process.stdout.write(` Trial ${t + 1}/${trials}... `)
const result = await runTrial(job.scenario, job.modelId, trialWorkdir)
trialResults.push(result)
console.log(result.passed ? "✓ PASS" : `✗ FAIL: ${result.error}`)
}
trialResults.forEach((result, t) => {
const trialNum = t + 1
const logContent =
`# Trial ${trialNum}\n` +
`Status: ${result.passed ? "PASS" : "FAIL"}\n` +
`Duration: ${result.durationMs}ms\n` +
(result.error ? `Error: ${result.error}\n` : "") +
`\n## STDOUT\n${result.stdout || "(empty)"}\n` +
`\n## STDERR\n${result.stderr || "(empty)"}\n`
fs.writeFileSync(path.join(logDir, `trial-${trialNum}.log`), logContent)
})
const trialBools = trialResults.map((t) => t.passed)
const metrics = metricsCalc.calculateTaskMetrics(trialBools)
const status = metricsCalc.getTaskStatus(trialBools)
results.push({
scenarioId: job.scenario.id,
scenarioName: job.scenario.name,
model: job.modelId,
modelId: job.modelId,
trials: trialResults,
metrics,
status,
const result = await runJob(job)
result.trials.forEach((trial, index) => {
console.log(` Trial ${index + 1}/${trials}... ${trial.passed ? "✓ PASS" : `✗ FAIL: ${trial.error}`}`)
})
results.push(result)
// Display pass@k where k = actual trials (pass@3 is meaningless with fewer trials)
const passMetric = trials >= 3 ? metrics.passAt3 : metrics.passAt1
const passMetric = trials >= 3 ? result.metrics.passAt3 : result.metrics.passAt1
const passLabel = trials >= 3 ? "pass@3" : "pass@1"
console.log(` Result: ${status.toUpperCase()} | ${passLabel}: ${(passMetric * 100).toFixed(0)}%`)
console.log(` Result: ${result.status.toUpperCase()} | ${passLabel}: ${(passMetric * 100).toFixed(0)}%`)
}
}
// Generate report
const report: SmokeTestReport = {
timestamp: new Date().toISOString(),
provider: DEFAULT_PROVIDER,
provider: providersInRun.join(","),
models: resolvedModels,
scenarios: scenarios.map((s) => s.id),
trialsPerTest: trials,
@@ -0,0 +1,28 @@
{
"name": "Edit file with gpt-oss via OpenAI-compatible",
"description": "Reproduces openai-compatible gpt-oss file editing reliability when native tool calling is enabled",
"prompt": "Edit the file config.txt and change the line 'debug = false' to 'debug = true'. Prefer direct file-edit tools instead of shell command workarounds.",
"provider": "openai",
"models": [
"gpt-oss-120b"
],
"requiredEnv": [
"OPENAI_COMPAT_API_KEY",
"OPENAI_COMPAT_BASE_URL"
],
"auth": {
"apiKeyEnv": "OPENAI_COMPAT_API_KEY",
"baseUrlEnv": "OPENAI_COMPAT_BASE_URL",
"modelId": "gpt-oss-120b"
},
"expectedFiles": [
"config.txt"
],
"expectedContent": [
{
"file": "config.txt",
"contains": "debug = true"
}
],
"timeout": 180
}
@@ -0,0 +1,11 @@
# Application Configuration
name = MyApp
version = 1.0.0
# Debug settings
debug = false
log_level = info
# Server settings
host = localhost
port = 8080
+4 -9
View File
@@ -56,15 +56,10 @@
- قم بتحديث الاختبارات الحالية إذا كانت تغييراتك تؤثر عليها
- تضمين كل من اختبارات الوحدة واختبارات التكامل حيثما كان ذلك مناسبًا
4. **إدارة الإصدار مع Changesets**
4. **ملاحظات الإصدار وسجل التغييرات**
- أنشئ changeset لأي تغييرات واجهة المستخدم باستخدام `npm run changeset`
- اختر زيادة الإصدار المناسبة:
- `major` للتغييرات الكبيرة (1.0.0 → 2.0.0)
- `minor` للميزات الجديدة (1.0.0 → 1.1.0)
- `patch` لإصلاحات الأخطاء (1.0.0 → 1.0.1)
- اكتب رسائل changeset واضحة ووصفية تشرح التأثير
- لا تتطلب التغييرات في الوثائق فقط changesets
- لا يحتاج المساهمون إلى إنشاء ملفات changelog-entry ضمن PR.
- يتولى فريق الصيانة إدارة إصدار النسخ وتنسيق سجل التغييرات أثناء عملية الإصدار.
5. **إرشادات الالتزام (Commit Guidelines)**
@@ -90,4 +85,4 @@
من خلال إرسال طلب سحب، فإنك توافق على أن مساهماتك سيتم ترخيصها بنفس ترخيص المشروع ([Apache 2.0](LICENSE)).
تذكر: المساهمة في Cline لا تقتصر فقط على كتابة الكود - إنها تتعلق بأن تكون جزءًا من مجتمع يُشكل مستقبل التطوير بمساعدة الذكاء الاصطناعي. لنبنِ شيئًا رائعًا معًا! 🚀
تذكر: المساهمة في Cline لا تقتصر فقط على كتابة الكود - إنها تتعلق بأن تكون جزءًا من مجتمع يُشكل مستقبل التطوير بمساعدة الذكاء الاصطناعي. لنبنِ شيئًا رائعًا معًا! 🚀
+5 -17
View File
@@ -163,27 +163,15 @@
<details>
<summary>إنشاء طلب سحب (Pull Request)</summary>
1. قبل إنشاء PR، قم بإنشاء إدخال للتغييرات:
```bash
npm run changeset
```
سيطلب منك تحديد:
- نوع التغيير (رئيسي، ثانوي، إصلاح)
- `رئيسي` → تغييرات غير متوافقة (1.0.0 → 2.0.0)
- `ثانوي` → ميزات جديدة (1.0.0 → 1.1.0)
- `إصلاح` → إصلاحات للأخطاء (1.0.0 → 1.0.1)
- وصف التغييرات التي قمت بها
1. قم بعمل commit لتغييراتك.
2. قم بحفظ التغييرات وملف `.changeset` الذي تم إنشاؤه
2. ادفع فرعك وأنشئ PR على GitHub. سيقوم CI بـ:
- تشغيل الاختبارات والفحوصات
3. ادفع فرعك وأنشئ PR على GitHub. سيقوم CI بـ:
- تشغيل الاختبارات والفحوصات
- سيقوم Changesetbot بإنشاء تعليق يوضح تأثير الإصدار
- عند الدمج مع الفرع الرئيسي، سيقوم Changesetbot بإنشاء PR لحزم الإصدار
- عند دمج PR لحزم الإصدار، سيتم نشر إصدار جديد
3. يتولى فريق الصيانة إدارة إصدار النسخ وتنسيق سجل التغييرات أثناء عملية الإصدار.
</details>
## الرخصة
[Apache 2.0 © 2025 Cline Bot Inc.](./LICENSE)
[Apache 2.0 © 2025 Cline Bot Inc.](./LICENSE)
+3 -8
View File
@@ -56,15 +56,10 @@ Cline에 기여하는 것에 관심을 가져주셔서 감사합니다! 버그
- 변경사항이 기존 테스트에 영향을 미치는 경우 해당 테스트를 업데이트해 주세요
- 적절한 경우 단위 테스트와 통합 테스트를 모두 포함해 주세요
4. **Changesets를 활용한 버전 관리**
4. **버전/릴리스 노트 관리**
- 사용자에게 영향을 미치는 변경 사항이 있는 경우, `npm run changeset`을 실행하여 changeset을 생성해 주세요
- 적절한 버전 증가 옵션을 선택하세요:
- `major` 호환되지 않는 변경 (1.0.0 → 2.0.0)
- `minor` 새로운 기능 추가 (1.0.0 → 1.1.0)
- `patch` 버그 수정 (1.0.0 → 1.0.1)
- 영향을 설명하는 명확한 변경사항 메시지를 작성해 주세요
- 문서 변경만 있는 경우 changeset이 필요하지 않습니다
- 기여자는 PR에서 changelog-entry 파일을 만들 필요가 없습니다.
- 릴리스 버전 관리와 CHANGELOG 정리는 메인테이너가 릴리스 과정에서 수행합니다.
5. **커밋 가이드라인**
+4 -16
View File
@@ -146,24 +146,12 @@ Cline이 작업을 진행하는 동안 확장 프로그램은 각 단계에서
<details>
<summary>Pull Request 생성 방법</summary>
1. PR을 만들기 전, 변경 사항을 기록하는 changeset 항목을 생성:
```bash
npm run changeset
```
이후 프롬프트에서 다음 정보를 입력하세요:
- 변경 유형 (major, minor, patch)
- `major` → 호환되지 않는 변경 (1.0.0 → 2.0.0)
- `minor` → 새로운 기능 추가 (1.0.0 → 1.1.0)
- `patch` → 버그 수정 (1.0.0 → 1.0.1)
- 변경 사항 설명 입력
1. 변경 사항을 커밋하세요.
2. 변경 사항과 생성된 `.changeset` 파일을 커밋 후 브랜치를 푸시하고 GitHub에서 PR을 생성하세요.
3. 브랜치를 푸시하고 GitHub에서 PR을 생성하세요. CI가 다음과 같은 작업을 수행합니다:
2. 브랜치를 푸시하고 GitHub에서 PR을 생성하세요. CI가 다음과 같은 작업을 수행합니다:
- 테스트 및 코드 검증 실행
- Changesetbot이 버전 변경 영향을 보여주는 코멘트를 생성
- 브랜치가 메인에 머지되면, Changesetbot이 버전 패키지 PR을 생성
- 버전 패키지 PR이 머지되면, 새로운 릴리즈가 게시됨
3. 버전 관리 및 변경 로그 정리는 릴리스 과정에서 메인테이너가 처리합니다.
</details>
+3 -8
View File
@@ -52,14 +52,9 @@
- 若您的變更影響現有測試,請更新測試
- 適當時包含單元測試與整合測試
4. **使用 Changesets 管理版本**
- 使用 `npm run changeset` 為任何面向使用者的變更建立 changeset
- 選擇適當的版本升級:
- `major` 重大變更 (1.0.0 → 2.0.0)
- `minor` 新功能 (1.0.0 → 1.1.0)
- `patch` 錯誤修正 (1.0.0 → 1.0.1)
- 撰寫清晰且描述性的 changeset 訊息,說明影響
- 僅文件變更不需建立 changeset
4. **版本與變更日誌說明**
- 貢獻者不需要在 PR 中建立 changelog-entry 檔案。
- 維護者會在發版流程中處理版本管理與變更日誌整理。
5. **提交指引**
- 撰寫清晰且描述性的提交訊息
+4 -18
View File
@@ -162,26 +162,12 @@ Cline 可以直接在您的編輯器中建立和編輯檔案,並顯示變更
<details>
<summary>建立 Pull Request</summary>
1. 在建立 PR 前,產生一個 changeset 項目:
1. 提交您的變更。
```bash
npm run changeset
```
這會提示您填寫:
- 變更類型(major、minor、patch
- `major` → 重大變更(1.0.0 → 2.0.0
- `minor` → 新功能(1.0.0 → 1.1.0
- `patch` → 錯誤修正(1.0.0 → 1.0.1
- 您的變更說明
2. 提交您的變更和產生的 `.changeset` 檔案
3. 推送您的分支並在 GitHub 上建立 PR。我們的 CI 會:
2. 推送您的分支並在 GitHub 上建立 PR。我們的 CI 會:
- 執行測試和檢查
- Changesetbot 會建立一個顯示版本影響的評論
- 當合併到 main 時,changesetbot 會建立一個 Version Packages PR
- 當 Version Packages PR 合併時,就會發布新版本
3. 版本管理與變更日誌整理會由維護者在發版流程中處理。
</details>
+24 -733
View File
@@ -1,12 +1,12 @@
{
"name": "claude-dev",
"version": "3.66.0",
"version": "3.72.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "claude-dev",
"version": "3.66.0",
"version": "3.72.0",
"license": "Apache-2.0",
"workspaces": [
".",
@@ -112,7 +112,6 @@
"devDependencies": {
"@biomejs/biome": "^2.3.14",
"@bufbuild/buf": "^1.54.0",
"@changesets/cli": "^2.27.12",
"@types/better-sqlite3": "^7.6.13",
"@types/chai": "^5.0.1",
"@types/clone-deep": "^4.0.4",
@@ -162,7 +161,7 @@
},
"cli": {
"name": "cline",
"version": "2.4.1",
"version": "2.6.1",
"cpu": [
"x64",
"arm64"
@@ -182,6 +181,7 @@
"ink": "npm:@jrichman/ink@6.4.7",
"ink-picture": "^1.3.3",
"ink-spinner": "^5.0.0",
"marked": "^17.0.3",
"nanoid": "^5.1.6",
"ora": "^8.0.1",
"pino": "^10.0.0",
@@ -193,6 +193,7 @@
"cline": "dist/cli.mjs"
},
"devDependencies": {
"@types/marked": "^5.0.2",
"@types/node": "20.x",
"@types/prompts": "^2.4.9",
"@types/react": "^19.2.9",
@@ -1782,16 +1783,6 @@
"node": ">=6.0.0"
}
},
"node_modules/@babel/runtime": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz",
"integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/template": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
@@ -2193,249 +2184,6 @@
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"license": "MIT"
},
"node_modules/@changesets/apply-release-plan": {
"version": "7.0.14",
"resolved": "https://registry.npmjs.org/@changesets/apply-release-plan/-/apply-release-plan-7.0.14.tgz",
"integrity": "sha512-ddBvf9PHdy2YY0OUiEl3TV78mH9sckndJR14QAt87KLEbIov81XO0q0QAmvooBxXlqRRP8I9B7XOzZwQG7JkWA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@changesets/config": "^3.1.2",
"@changesets/get-version-range-type": "^0.4.0",
"@changesets/git": "^3.0.4",
"@changesets/should-skip-package": "^0.1.2",
"@changesets/types": "^6.1.0",
"@manypkg/get-packages": "^1.1.3",
"detect-indent": "^6.0.0",
"fs-extra": "^7.0.1",
"lodash.startcase": "^4.4.0",
"outdent": "^0.5.0",
"prettier": "^2.7.1",
"resolve-from": "^5.0.0",
"semver": "^7.5.3"
}
},
"node_modules/@changesets/assemble-release-plan": {
"version": "6.0.9",
"resolved": "https://registry.npmjs.org/@changesets/assemble-release-plan/-/assemble-release-plan-6.0.9.tgz",
"integrity": "sha512-tPgeeqCHIwNo8sypKlS3gOPmsS3wP0zHt67JDuL20P4QcXiw/O4Hl7oXiuLnP9yg+rXLQ2sScdV1Kkzde61iSQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@changesets/errors": "^0.2.0",
"@changesets/get-dependents-graph": "^2.1.3",
"@changesets/should-skip-package": "^0.1.2",
"@changesets/types": "^6.1.0",
"@manypkg/get-packages": "^1.1.3",
"semver": "^7.5.3"
}
},
"node_modules/@changesets/changelog-git": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/@changesets/changelog-git/-/changelog-git-0.2.1.tgz",
"integrity": "sha512-x/xEleCFLH28c3bQeQIyeZf8lFXyDFVn1SgcBiR2Tw/r4IAWlk1fzxCEZ6NxQAjF2Nwtczoen3OA2qR+UawQ8Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"@changesets/types": "^6.1.0"
}
},
"node_modules/@changesets/cli": {
"version": "2.29.8",
"resolved": "https://registry.npmjs.org/@changesets/cli/-/cli-2.29.8.tgz",
"integrity": "sha512-1weuGZpP63YWUYjay/E84qqwcnt5yJMM0tep10Up7Q5cS/DGe2IZ0Uj3HNMxGhCINZuR7aO9WBMdKnPit5ZDPA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@changesets/apply-release-plan": "^7.0.14",
"@changesets/assemble-release-plan": "^6.0.9",
"@changesets/changelog-git": "^0.2.1",
"@changesets/config": "^3.1.2",
"@changesets/errors": "^0.2.0",
"@changesets/get-dependents-graph": "^2.1.3",
"@changesets/get-release-plan": "^4.0.14",
"@changesets/git": "^3.0.4",
"@changesets/logger": "^0.1.1",
"@changesets/pre": "^2.0.2",
"@changesets/read": "^0.6.6",
"@changesets/should-skip-package": "^0.1.2",
"@changesets/types": "^6.1.0",
"@changesets/write": "^0.4.0",
"@inquirer/external-editor": "^1.0.2",
"@manypkg/get-packages": "^1.1.3",
"ansi-colors": "^4.1.3",
"ci-info": "^3.7.0",
"enquirer": "^2.4.1",
"fs-extra": "^7.0.1",
"mri": "^1.2.0",
"p-limit": "^2.2.0",
"package-manager-detector": "^0.2.0",
"picocolors": "^1.1.0",
"resolve-from": "^5.0.0",
"semver": "^7.5.3",
"spawndamnit": "^3.0.1",
"term-size": "^2.1.0"
},
"bin": {
"changeset": "bin.js"
}
},
"node_modules/@changesets/config": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@changesets/config/-/config-3.1.2.tgz",
"integrity": "sha512-CYiRhA4bWKemdYi/uwImjPxqWNpqGPNbEBdX1BdONALFIDK7MCUj6FPkzD+z9gJcvDFUQJn9aDVf4UG7OT6Kog==",
"dev": true,
"license": "MIT",
"dependencies": {
"@changesets/errors": "^0.2.0",
"@changesets/get-dependents-graph": "^2.1.3",
"@changesets/logger": "^0.1.1",
"@changesets/types": "^6.1.0",
"@manypkg/get-packages": "^1.1.3",
"fs-extra": "^7.0.1",
"micromatch": "^4.0.8"
}
},
"node_modules/@changesets/errors": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/@changesets/errors/-/errors-0.2.0.tgz",
"integrity": "sha512-6BLOQUscTpZeGljvyQXlWOItQyU71kCdGz7Pi8H8zdw6BI0g3m43iL4xKUVPWtG+qrrL9DTjpdn8eYuCQSRpow==",
"dev": true,
"license": "MIT",
"dependencies": {
"extendable-error": "^0.1.5"
}
},
"node_modules/@changesets/get-dependents-graph": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@changesets/get-dependents-graph/-/get-dependents-graph-2.1.3.tgz",
"integrity": "sha512-gphr+v0mv2I3Oxt19VdWRRUxq3sseyUpX9DaHpTUmLj92Y10AGy+XOtV+kbM6L/fDcpx7/ISDFK6T8A/P3lOdQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@changesets/types": "^6.1.0",
"@manypkg/get-packages": "^1.1.3",
"picocolors": "^1.1.0",
"semver": "^7.5.3"
}
},
"node_modules/@changesets/get-release-plan": {
"version": "4.0.14",
"resolved": "https://registry.npmjs.org/@changesets/get-release-plan/-/get-release-plan-4.0.14.tgz",
"integrity": "sha512-yjZMHpUHgl4Xl5gRlolVuxDkm4HgSJqT93Ri1Uz8kGrQb+5iJ8dkXJ20M2j/Y4iV5QzS2c5SeTxVSKX+2eMI0g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@changesets/assemble-release-plan": "^6.0.9",
"@changesets/config": "^3.1.2",
"@changesets/pre": "^2.0.2",
"@changesets/read": "^0.6.6",
"@changesets/types": "^6.1.0",
"@manypkg/get-packages": "^1.1.3"
}
},
"node_modules/@changesets/get-version-range-type": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/@changesets/get-version-range-type/-/get-version-range-type-0.4.0.tgz",
"integrity": "sha512-hwawtob9DryoGTpixy1D3ZXbGgJu1Rhr+ySH2PvTLHvkZuQ7sRT4oQwMh0hbqZH1weAooedEjRsbrWcGLCeyVQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@changesets/git": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@changesets/git/-/git-3.0.4.tgz",
"integrity": "sha512-BXANzRFkX+XcC1q/d27NKvlJ1yf7PSAgi8JG6dt8EfbHFHi4neau7mufcSca5zRhwOL8j9s6EqsxmT+s+/E6Sw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@changesets/errors": "^0.2.0",
"@manypkg/get-packages": "^1.1.3",
"is-subdir": "^1.1.1",
"micromatch": "^4.0.8",
"spawndamnit": "^3.0.1"
}
},
"node_modules/@changesets/logger": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/@changesets/logger/-/logger-0.1.1.tgz",
"integrity": "sha512-OQtR36ZlnuTxKqoW4Sv6x5YIhOmClRd5pWsjZsddYxpWs517R0HkyiefQPIytCVh4ZcC5x9XaG8KTdd5iRQUfg==",
"dev": true,
"license": "MIT",
"dependencies": {
"picocolors": "^1.1.0"
}
},
"node_modules/@changesets/parse": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/@changesets/parse/-/parse-0.4.2.tgz",
"integrity": "sha512-Uo5MC5mfg4OM0jU3up66fmSn6/NE9INK+8/Vn/7sMVcdWg46zfbvvUSjD9EMonVqPi9fbrJH9SXHn48Tr1f2yA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@changesets/types": "^6.1.0",
"js-yaml": "^4.1.1"
}
},
"node_modules/@changesets/pre": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@changesets/pre/-/pre-2.0.2.tgz",
"integrity": "sha512-HaL/gEyFVvkf9KFg6484wR9s0qjAXlZ8qWPDkTyKF6+zqjBe/I2mygg3MbpZ++hdi0ToqNUF8cjj7fBy0dg8Ug==",
"dev": true,
"license": "MIT",
"dependencies": {
"@changesets/errors": "^0.2.0",
"@changesets/types": "^6.1.0",
"@manypkg/get-packages": "^1.1.3",
"fs-extra": "^7.0.1"
}
},
"node_modules/@changesets/read": {
"version": "0.6.6",
"resolved": "https://registry.npmjs.org/@changesets/read/-/read-0.6.6.tgz",
"integrity": "sha512-P5QaN9hJSQQKJShzzpBT13FzOSPyHbqdoIBUd2DJdgvnECCyO6LmAOWSV+O8se2TaZJVwSXjL+v9yhb+a9JeJg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@changesets/git": "^3.0.4",
"@changesets/logger": "^0.1.1",
"@changesets/parse": "^0.4.2",
"@changesets/types": "^6.1.0",
"fs-extra": "^7.0.1",
"p-filter": "^2.1.0",
"picocolors": "^1.1.0"
}
},
"node_modules/@changesets/should-skip-package": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/@changesets/should-skip-package/-/should-skip-package-0.1.2.tgz",
"integrity": "sha512-qAK/WrqWLNCP22UDdBTMPH5f41elVDlsNyat180A33dWxuUDyNpg6fPi/FyTZwRriVjg0L8gnjJn2F9XAoF0qw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@changesets/types": "^6.1.0",
"@manypkg/get-packages": "^1.1.3"
}
},
"node_modules/@changesets/types": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/@changesets/types/-/types-6.1.0.tgz",
"integrity": "sha512-rKQcJ+o1nKNgeoYRHKOS07tAMNd3YSN0uHaJOZYjBAgxfV7TUE7JE+z4BzZdQwb5hKaYbayKN5KrYV7ODb2rAA==",
"dev": true,
"license": "MIT"
},
"node_modules/@changesets/write": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/@changesets/write/-/write-0.4.0.tgz",
"integrity": "sha512-CdTLvIOPiCNuH71pyDu3rA+Q0n65cmAbXnwWH84rKGiFumFzkmHNT8KHTMEchcxN+Kl8I54xGUhJ7l3E7X396Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"@changesets/types": "^6.1.0",
"fs-extra": "^7.0.1",
"human-id": "^4.1.1",
"prettier": "^2.7.1"
}
},
"node_modules/@colors/colors": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz",
@@ -3665,45 +3413,6 @@
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@inquirer/external-editor": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz",
"integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==",
"dev": true,
"license": "MIT",
"dependencies": {
"chardet": "^2.1.1",
"iconv-lite": "^0.7.0"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@types/node": ">=18"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
}
}
},
"node_modules/@inquirer/external-editor/node_modules/iconv-lite": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
"integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
"dev": true,
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/@isaacs/balanced-match": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz",
@@ -3877,119 +3586,6 @@
"integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==",
"license": "MIT"
},
"node_modules/@manypkg/find-root": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@manypkg/find-root/-/find-root-1.1.0.tgz",
"integrity": "sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.5.5",
"@types/node": "^12.7.1",
"find-up": "^4.1.0",
"fs-extra": "^8.1.0"
}
},
"node_modules/@manypkg/find-root/node_modules/@types/node": {
"version": "12.20.55",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz",
"integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@manypkg/find-root/node_modules/fs-extra": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
"dev": true,
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^4.0.0",
"universalify": "^0.1.0"
},
"engines": {
"node": ">=6 <7 || >=8"
}
},
"node_modules/@manypkg/get-packages": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@manypkg/get-packages/-/get-packages-1.1.3.tgz",
"integrity": "sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.5.5",
"@changesets/types": "^4.0.1",
"@manypkg/find-root": "^1.1.0",
"fs-extra": "^8.1.0",
"globby": "^11.0.0",
"read-yaml-file": "^1.1.0"
}
},
"node_modules/@manypkg/get-packages/node_modules/@changesets/types": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/@changesets/types/-/types-4.1.0.tgz",
"integrity": "sha512-LDQvVDv5Kb50ny2s25Fhm3d9QSZimsoUGBsUioj6MC3qbMUCuC8GPIvk/M6IvXx3lYhAs0lwWUQLb+VIEUCECw==",
"dev": true,
"license": "MIT"
},
"node_modules/@manypkg/get-packages/node_modules/fs-extra": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
"dev": true,
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^4.0.0",
"universalify": "^0.1.0"
},
"engines": {
"node": ">=6 <7 || >=8"
}
},
"node_modules/@manypkg/get-packages/node_modules/globby": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
"integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
"dev": true,
"license": "MIT",
"dependencies": {
"array-union": "^2.1.0",
"dir-glob": "^3.0.1",
"fast-glob": "^3.2.9",
"ignore": "^5.2.0",
"merge2": "^1.4.1",
"slash": "^3.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@manypkg/get-packages/node_modules/ignore": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
"integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 4"
}
},
"node_modules/@manypkg/get-packages/node_modules/slash": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
"integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/@mapbox/node-pre-gyp": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-2.0.3.tgz",
@@ -7771,6 +7367,13 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/marked": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/@types/marked/-/marked-5.0.2.tgz",
"integrity": "sha512-OucS4KMHhFzhz27KxmWg7J+kIYqyqoW5kdIEI319hqARQQUTqhao3M/F+uFnDXD0Rg72iDDZxZNxq5gvctmLlg==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/mocha": {
"version": "10.0.10",
"resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz",
@@ -9159,16 +8762,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/array-union": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
"integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/arraybuffer.prototype.slice": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz",
@@ -9487,19 +9080,6 @@
"node": ">=10.0.0"
}
},
"node_modules/better-path-resolve": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/better-path-resolve/-/better-path-resolve-1.0.0.tgz",
"integrity": "sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-windows": "^1.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/better-sqlite3": {
"version": "12.6.2",
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.6.2.tgz",
@@ -10266,22 +9846,6 @@
"url": "https://github.com/sponsors/colinhacks"
}
},
"node_modules/ci-info": {
"version": "3.9.0",
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
"integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/sibiraj-s"
}
],
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/cjs-module-lexer": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz",
@@ -11126,16 +10690,6 @@
"node": ">= 0.8"
}
},
"node_modules/detect-indent": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz",
"integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/detect-libc": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
@@ -11167,29 +10721,6 @@
"integrity": "sha512-98l0sW87ZT58pU4i61wa2OHwxbiYSbuxsCBozaVnYX2iCnr3bLM3fIes1/ej7h1YdOKuKt/MLs706TVnALA65w==",
"license": "BSD-2-Clause"
},
"node_modules/dir-glob": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
"integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
"dev": true,
"license": "MIT",
"dependencies": {
"path-type": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/dir-glob/node_modules/path-type": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/dom-serializer": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
@@ -11456,43 +10987,6 @@
"node": ">=10.13.0"
}
},
"node_modules/enquirer": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz",
"integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-colors": "^4.1.1",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8.6"
}
},
"node_modules/enquirer/node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/enquirer/node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
@@ -12230,13 +11724,6 @@
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
"license": "MIT"
},
"node_modules/extendable-error": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/extendable-error/-/extendable-error-0.1.7.tgz",
"integrity": "sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==",
"dev": true,
"license": "MIT"
},
"node_modules/extract-zip": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
@@ -12752,21 +12239,6 @@
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
"license": "MIT"
},
"node_modules/fs-extra": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz",
"integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
"dev": true,
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.1.2",
"jsonfile": "^4.0.0",
"universalify": "^0.1.0"
},
"engines": {
"node": ">=6 <7 || >=8"
}
},
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -13577,16 +13049,6 @@
"node": ">= 14"
}
},
"node_modules/human-id": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/human-id/-/human-id-4.1.3.tgz",
"integrity": "sha512-tsYlhAYpjCKa//8rXZ9DqKEawhPoSytweBC2eNvcaDK+57RZLHGqNs3PZTQO6yekLFSuvA6AlnAfrw1uBvtb+Q==",
"dev": true,
"license": "MIT",
"bin": {
"human-id": "dist/cli.js"
}
},
"node_modules/human-signals": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz",
@@ -14609,19 +14071,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-subdir": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/is-subdir/-/is-subdir-1.2.0.tgz",
"integrity": "sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw==",
"dev": true,
"license": "MIT",
"dependencies": {
"better-path-resolve": "1.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/is-symbol": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz",
@@ -15122,16 +14571,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/jsonfile": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
"integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
"dev": true,
"license": "MIT",
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
},
"node_modules/jsonwebtoken": {
"version": "9.0.3",
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz",
@@ -15883,13 +15322,6 @@
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
"license": "MIT"
},
"node_modules/lodash.startcase": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.startcase/-/lodash.startcase-4.4.0.tgz",
"integrity": "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==",
"dev": true,
"license": "MIT"
},
"node_modules/lodash.truncate": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz",
@@ -16233,6 +15665,18 @@
"markdown-it": "bin/markdown-it.mjs"
}
},
"node_modules/marked": {
"version": "17.0.3",
"resolved": "https://registry.npmjs.org/marked/-/marked-17.0.3.tgz",
"integrity": "sha512-jt1v2ObpyOKR8p4XaUJVk3YWRJ5n+i4+rjQopxvV32rSndTJXvIzuUdWWIy/1pFQMkQmvTXawzDNqOH/CUmx6A==",
"license": "MIT",
"bin": {
"marked": "bin/marked.js"
},
"engines": {
"node": ">= 20"
}
},
"node_modules/marky": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/marky/-/marky-1.3.0.tgz",
@@ -16831,16 +16275,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/mri": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
"integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -17964,13 +17398,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/outdent": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/outdent/-/outdent-0.5.0.tgz",
"integrity": "sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==",
"dev": true,
"license": "MIT"
},
"node_modules/own-keys": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz",
@@ -17989,29 +17416,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/p-filter": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/p-filter/-/p-filter-2.1.0.tgz",
"integrity": "sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==",
"dev": true,
"license": "MIT",
"dependencies": {
"p-map": "^2.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/p-filter/node_modules/p-map": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz",
"integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
@@ -18160,16 +17564,6 @@
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
"license": "BlueOak-1.0.0"
},
"node_modules/package-manager-detector": {
"version": "0.2.11",
"resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-0.2.11.tgz",
"integrity": "sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"quansync": "^0.2.7"
}
},
"node_modules/pako": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
@@ -18679,22 +18073,6 @@
"node": ">=10"
}
},
"node_modules/prettier": {
"version": "2.8.8",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz",
"integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==",
"dev": true,
"license": "MIT",
"bin": {
"prettier": "bin-prettier.js"
},
"engines": {
"node": ">=10.13.0"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/pretty-ms": {
"version": "9.3.0",
"resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz",
@@ -18960,23 +18338,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/quansync": {
"version": "0.2.11",
"resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz",
"integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==",
"dev": true,
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/antfu"
},
{
"type": "individual",
"url": "https://github.com/sponsors/sxzz"
}
],
"license": "MIT"
},
"node_modules/queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@@ -19156,42 +18517,6 @@
"node": ">=4"
}
},
"node_modules/read-yaml-file": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/read-yaml-file/-/read-yaml-file-1.1.0.tgz",
"integrity": "sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==",
"dev": true,
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.1.5",
"js-yaml": "^3.6.1",
"pify": "^4.0.1",
"strip-bom": "^3.0.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/read-yaml-file/node_modules/pify": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
"integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/read-yaml-file/node_modules/strip-bom": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
"integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/readable-stream": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz",
@@ -20584,17 +19909,6 @@
"dev": true,
"license": "ISC"
},
"node_modules/spawndamnit": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/spawndamnit/-/spawndamnit-3.0.1.tgz",
"integrity": "sha512-MmnduQUuHCoFckZoWnXsTg7JaiLBJrKFj9UI2MbRPGaJeVpsLcVBu6P/IGZovziM/YBsellCmsprgNA+w0CzVg==",
"dev": true,
"license": "SEE LICENSE IN LICENSE",
"dependencies": {
"cross-spawn": "^7.0.5",
"signal-exit": "^4.0.1"
}
},
"node_modules/spdx-correct": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz",
@@ -21246,19 +20560,6 @@
"node": ">=18"
}
},
"node_modules/term-size": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz",
"integrity": "sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/terminal-link": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-4.0.0.tgz",
@@ -21914,16 +21215,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/universalify": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 4.0.0"
}
},
"node_modules/unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+1 -4
View File
@@ -2,7 +2,7 @@
"name": "claude-dev",
"displayName": "Cline",
"description": "Autonomous coding agent right in your IDE, capable of creating/editing files, running commands, using the browser, and more with your permission every step of the way.",
"version": "3.66.0",
"version": "3.72.0",
"icon": "assets/icons/icon.png",
"workspaces": [
".",
@@ -437,8 +437,6 @@
"publish:marketplace:prerelease": "vsce publish --allow-package-secrets sendgrid --pre-release && ovsx publish --pre-release",
"publish:marketplace:nightly": "node ./scripts/publish-nightly.mjs",
"prepare": "npx husky",
"changeset": "changeset",
"version-packages": "changeset version",
"docs": "cd docs && npm run dev",
"docs:check-links": "cd docs && npm run check",
"docs:rename-file": "cd docs && npm run rename",
@@ -462,7 +460,6 @@
"devDependencies": {
"@biomejs/biome": "^2.3.14",
"@bufbuild/buf": "^1.54.0",
"@changesets/cli": "^2.27.12",
"@types/better-sqlite3": "^7.6.13",
"@types/chai": "^5.0.1",
"@types/clone-deep": "^4.0.4",

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