Compare commits

...

171 Commits

Author SHA1 Message Date
Tony Loehr e073277d70 Merge branch 'main' into cve-example 2026-02-23 13:47:42 -08:00
Bee 01547ba1f4 feat: add AgentConfigLoader for file-based agent configs (ENG-1547) (#9245)
* refactor: centralize tool handler registration and filter by allowed tools

- Create centralized toolHandlersMap for all tool handler instantiation
- Add registerToolHandlers method to register only allowed tools from config
- Filter subagent tools to only include allowed tools from allowedTools config
- Remove scattered tool handler registration logic in favor of single source of truth
- Improve maintainability by consolidating tool handler creation in one place

This refactoring ensures subagents only have access to explicitly allowed tools
and makes the tool registration process more maintainable and consistent.

It also improves separation of concerns by having the coordinator manage all tool handler registration, while the executor focuses on orchestration. The allowedTools parameter enables runtime filtering of available tools for different contexts.

Also update PromptRegistry to load synchronous and simplify variant lookup

- Convert async load() to synchronous, called in constructor as both loadVariants and loadComponents are not async functions
- Remove health check logic and loaded state tracking
- Extract getVariant() method with proper generic fallback
- Add getComponents() accessor and simplify component loading
- Convert variant/component loaders from async to synchronous
- Remove unnecessary await calls throughout the codebase
- Add PromptRegistry tests for variant resolution and components

* fix test

* feat: add AgentConfigLoader for file-based agent configs

Add AgentConfigLoader singleton to manage agent configurations loaded from
YAML files in the agents directory. Supports hot-reloading via file watcher,
validates config schema with Zod, and integrates with extension lifecycle
(StateManager initialization and tearDown disposal).

* add tests

* add missing export

* update tests

* feat(tools): implement dynamic tool registration for subagents

Updates the tool system to support dynamically registered subagents as individual tools.

- Modifies `ClineToolSet` to generate specific tool definitions for configured subagents via `AgentConfigLoader`.
- Updates `parseAssistantMessageV2` to use `getToolUseNames()` instead of a static list, enabling the parser to recognize dynamic tool tags.
- Replaces the generic `USE_SUBAGENTS` tool with specific subagent instances when available in the system prompt context.

* update config path and refine tool descriptions

- Relocate the subagent configuration directory from `~/.cline/data/agents` to `~/Documents/Cline/Agents` to improve user accessibility.
- Update subagent tool descriptions and parameter instructions in the system prompt to be more descriptive and helpful for the model.

* revert unrelated changes

* revert unrelated changes

* update unit test

* fix: await AgentConfigLoader initialization before StateManager completes

Ensure agent configs are fully loaded during StateManager initialization
by awaiting the `ready()` promise. Previously, `AgentConfigLoader` was
instantiated without waiting for the initial load to complete, causing
potential race conditions where configs might not be available when
needed.

- Add `initialLoadPromise` field to track the async initial load
- Expose a `ready()` method to allow callers to await initialization
- Await `AgentConfigLoader.getInstance().ready()` in StateManager

* set previousRequestTotalTokens
2026-02-23 13:26:41 -08:00
Robin Newhouse 7f1632f09f fix: prevent reasoning delta crash on usage-only stream chunks (#9432)
* fix: guard missing delta in reasoning streams

* test: isolate litellm prompt cache call-count assertions
2026-02-23 12:29:34 -08:00
Tony Loehr 5a2756d778 Merge branch 'main' into cve-example 2026-02-23 11:39:23 -08:00
Han Wang 1b0ab3d01b sambanova provider: update models list (#9479)
* Add changeset

* Allow temperature config

* update issues summary

* Update SambaNova docs

* Update list of sambanova models

* Update minimax m2.5

* remove 2 models

* Remove residual
2026-02-23 11:27:36 -08:00
Tony Loehr a41753c9a1 docs: add CVE scan sample to navigation, fix accordion labels, clarify --yolo flag 2026-02-23 11:21:41 -08:00
Chaitanya Eranki dce0902596 Oca Messages API implementation for new Claude Models (#9447)
* Made messages api changes

* Made changes

* Added changes

* Made maxTokens point to the right thing

* Removed deprecated max_tokens field

* Reverted the change

* Making an additional change to not cause any issues with chat completions

* reverting changes so we can make them in the backend

* Added changeset

* Fixed changes based on AI comments
2026-02-23 11:20:05 -08:00
Bee 9e7a30bd34 feat: preconnect websocket to reduce response latency (#9458)
Warm up the OpenAI WebSocket connection early in WebSocket mode to avoid handshake latency on the first response.create call. This introduces a responsesWsReadyPromise to track the connection state and prevent duplicate connection attempts while the initial connection is in flight.
2026-02-23 11:17:03 -08:00
Bee 2b1b1d1cf2 fix: restrict OpenAI tool ID transformation to native provider (#9459)
* fix: restrict OpenAI tool ID transformation to native provider

Update `convertToOpenAiMessages` and `transformToolCallId` to only apply tool ID transformations when the provider is explicitly set to `openai-native`. This prevents unintended ID modifications for other providers (like OpenRouter or local LLMs) that use the OpenAI format but may have different tool ID requirements or already provide compatible IDs.

* update tests

* transformToolCallIdForNativeApi
2026-02-23 11:16:55 -08:00
Max fcf3792f63 fix auth check for acp mode (#9491)
- acp code wasn't using the proper 'isAuthConfigured' method for
checking auth status

Co-authored-by: Max Paulus 🥪 <max@cline.bot>
2026-02-23 09:41:56 -08:00
shey-cline 0e833ade82 Add /q command to quit CLI (#9400)
* init

* changeset

* Apply suggestion from @greptile-apps[bot]

oops

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

* add tests

* add /q info to help panel

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-02-23 07:37:34 -08:00
Ara 4455db5198 Pull Cline's recommended from internal endpoint (#9376)
* feat(cline): fetch recommended models from API endpoint

* Adding 1m

* Adding 1m

* Adding 1m

* fix: harden model tag label handling and tab init

* fix(models): add retry-safe fetch, id canonicalization, and shared filtering

* chore: trigger PR head refresh

* refactor(models): remove canonical alias map for OpenRouter IDs

* refactor(webview): remove redundant cline fetch on mount

* Adding 1m
2026-02-22 22:28:55 -08:00
Bee 03ab2968a6 feat: responses api for openai native provider (#9411)
* fix: openai native provider token usage mapping

- Add `store` parameter support to OpenAI native provider options to allow persisting completions.
- Fix incorrect mapping of `cached_tokens` and `reasoning_tokens` in usage statistics.
- Include `thoughtsTokenCount` in the final usage report to track reasoning model performance and costs.

* feat: add websocket support for OpenAI Responses API

This commit introduces WebSocket support for the OpenAI Native provider's Responses API, providing an alternative to the standard HTTP streaming.

- Implement `createResponseStreamWebsocket` in `OpenAiNativeHandler` with a fallback to HTTP on failure.
- Refactor `OpenAiNativeHandler` to modularize tool mapping and parameter construction for the Responses API.
- Update `OcaHandler` to explicitly disable `previousResponseId` when using the Responses API and add validation for model information.
- Integrate `undici` WebSocket for better compatibility in the extension environment.

* disablePreviousResponseId

* feat: add timestamp to conversation messages for response chaining

Add `ts` field to `ClineStorageMessage` to track when messages were
created. Use this timestamp to enforce a 23-hour validity window when
chaining OpenAI responses via `previousResponseId`, since the API only
retains responses for 24 hours. Also fix non-null assertion operators
in tests to use optional chaining for safer access.

* add OpenAI Responses Websocket Mode ApiFormat support

- Add `OPENAI_RESPONSES_WEBSOCKET_MODE` to the `ApiFormat` enum in proto definitions.
- Update `OpenAiNativeHandler` to use the new API format for determining when to use websocket mode, replacing previous environment-based logic.
- Refactor tool mapping for OpenAI Responses to support strict mode and correctly handle null parameters.
- Ensure the `store` option is disabled when `previous_response_id` is present in websocket mode.
- Bump version to 2.4.1 and update package dependencies.

* use abortController

* add support for websocket mode to openai-codex

* set behind feature flag
2026-02-20 17:17:54 -08:00
Max a0d52d4d59 cli yolo mode should not persist yolo setting to disk ever (#9370)
- added a method to StateManager, setSessionOverride, which overrides
state settings while the statemanager lives in memory

Co-authored-by: Max Paulus 🥪 <max@cline.bot>
2026-02-20 11:16:29 -08:00
Max 75fbeb4aad fix cline auth with acp flag (#9405)
- was missing a check for "cline:clineAccountId" in the isAuthed method
of acp agent

Co-authored-by: Max Paulus 🥪 <max@cline.bot>
2026-02-20 11:15:45 -08:00
Bee 70db6bde34 fix: inline focus-chain slider within its feature row (#9444)
* fix: inline focus-chain slider within its feature row

Moves the focus-chain reminder interval `SettingsSlider` from a
standalone element rendered after all experimental feature rows to
being rendered directly beneath the focus-chain `FeatureRow`. The
slider now renders conditionally when `feature.id === "focus-chain"`
and the feature is enabled, improving UI cohesion and making the
relationship between the toggle and its configuration more explicit.

Additionally:
- Relocates focus-chain from `experimentalFeatures` to `agentFeatures`
- Removes the `isExperimental` prop and "Experimental:" label badge
  from `FeatureRow` and related feature toggle definitions
- Simplifies `SettingsSlider` markup by removing the wrapper card
  styling, making it suitable for inline embedding
  - Removes unused line from common.ts

* nestedKey
2026-02-20 10:58:46 -08:00
Saoud Rizwan 02c2601e0e fix(gemini): add 3.1 pro while keeping 3.0 compatibility (#9438) 2026-02-20 09:18:40 -08:00
Juan Pablo Flores 94692b5091 docs: add MCP support documentation for Cline CLI (#9390) 2026-02-20 09:39:02 -06:00
Robin Newhouse a2794c680f fix(evals): restore missing smoke eval npm scripts (#9429) 2026-02-19 14:49:27 -08:00
CandiedUniverse 6d3f8e1d5d fix(release-eng): Pin VSCode nightly build to node version 22 (#9423)
* fix(release-eng): Pin VSCode nightlybuild to node version 22

* fix(release-eng): Pin publish.yml GitHub workflow to node version 22
2026-02-19 12:04:14 -08:00
cryptoque 3e5847890b feat: add dynamic flag to adjust banner cache duration (#9421) 2026-02-19 11:03:50 -08:00
CandiedUniverse 0eab54ab12 fix(release-eng): Fix nightly extension publish failure caused by workspace self-link mismatch (#9420) 2026-02-19 10:40:45 -08:00
github-actions[bot] 7fa0a4924b Changeset version bump (#9413)
* changeset version bump

* Updating CHANGELOG.md format

* Adding 1m

* Adding 1m

* Adding 1m

* Adding 1m

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: Arafatkatze <arafat.da.khan@gmail.com>
2026-02-19 10:36:21 -08:00
Junjie Tang 8680218e0e Update sap-ai-sdk version (#9417) 2026-02-19 06:46:09 -08:00
Dominic Cooney 8787ab35b9 Update package-lock.json. (#9414) 2026-02-18 22:56:49 -08:00
Dominic Cooney 6ed3944f04 Export global, workspace and secrets from VSCode to share with CLI (#9227)
Caveat: Task history, tasks, etc. are not written to the same place by all clients yet. This is just about globalState, workspaceState and secrets.
2026-02-19 15:46:23 +09:00
Bee 1dd8e763d1 fix(chat): make Cmd/Ctrl+A select-all deterministic (#9408)
Handle plain Cmd/Ctrl+A directly in `ChatTextArea` keydown to force
textarea-wide selection via `setSelectionRange`, while preventing default
and propagation. This avoids intermittent failures caused by global shortcut
listener races, and keeps Cmd/Ctrl+Shift+A behavior unchanged.fix(chat): make Cmd/Ctrl+A select-all deterministic

Handle plain Cmd/Ctrl+A directly in `ChatTextArea` keydown to force
textarea-wide selection via `setSelectionRange`, while preventing default
and propagation. This avoids intermittent failures caused by global shortcut
listener races, and keeps Cmd/Ctrl+Shift+A behavior unchanged.
2026-02-18 20:55:57 -08:00
Bee 28e6297769 fix: flaky Cancel behavior by preventing duplicate cancel actions (CLINE-1380) (#9409)
* fix: flaky Cancel behavior by preventing duplicate cancel actions

This PR fixes chat cancel behavior where users sometimes had to click Cancel multiple times, and repeated clicks could accidentally transition into Resume/restart behavior.

* move to finally
2026-02-18 20:55:41 -08:00
Bee 5a2a5d1c0a refactor: replace non-null assertions with checks in PatchParser (#9402)
* refactor: replace non-null assertions with safe null checks in PatchParser

Replace all forbidden non-null assertions (`!`) in PatchParser.ts with
safe alternatives using optional chaining (`?.`) and nullish coalescing
(`?? ""`/`?? 0`). Also refactor the Levenshtein distance matrix from a
2D array to a flat array to eliminate index-based non-null assertions,
improving type safety and code robustness.

No feature behavior changes.

* simplify Levenshtein matrix indexing

Initialize the distance matrix with zeroes and add `at`/`set` helpers for flat-array access in `levenshteinDistance`.

This removes repeated index math and nullish fallbacks, making the algorithm easier to read while keeping bounds-safe access and identical behavior.refactor(patch-parser): simplify Levenshtein matrix indexing

Initialize the distance matrix with zeroes and add `at`/`set` helpers for flat-array access in `levenshteinDistance`.

This removes repeated index math and nullish fallbacks, making the algorithm easier to read while keeping bounds-safe access and identical behavior.
2026-02-18 19:35:48 -08:00
Max 113039a259 CLI 2.0: allow custom inference profile arn for bedrock provider (#9271) 2026-02-18 19:16:32 -08:00
Max 4023c18257 remove default timeout (#9401) 2026-02-18 17:28:50 -08:00
cryptoque 7c95b53892 Add support for DB backed Welcome Banner (#9315)
* feat: add welcome banner support from backend

* make DB banner format conform with existing banners

* add support for welcome banner actions

* remove debugging helper that bypass dismissal, dismissal should work again

* undo changes to make welcome banner always appear during debugging

* remove console logs for debugging

* clean up bannerservice

* clean up welcomesection.tsx

* add new tests for ide type filtering

* add welcome banner own feature flag and conditionally display between hard coded welcome banner and DB backed ones

* turn on welcome banner flag locally by default

* close welcome banners when clicking on actions

* apply bot review suggestion, fix memory leak

* address feedback: use p without span

* split welcome banners into a seperate component to keep whatsnewmodal clean

* get action through api schema instead of extractin it from rules_json

* use only bannerWaitTimeoutRef, remove waitingForBannersRef

* resolve new merge conflict

* linter

* cerebra
2026-02-18 14:52:02 -08:00
Bee 9fd2b99be4 chore: remove autoCondenseThreshold setting and related code (#9396)
- Remove `auto_condense_threshold` from `Settings` and `UpdateSettingsRequest` in `state.proto`.
- Remove `autoCondenseThreshold` from `ApiProviderInfo` interface.
- Update `generate-state-proto.mjs` to remove double field handling and improve integer parsing.
- Add error handling to `ContextManager` when parsing previous request JSON to prevent crashes on malformed data.
2026-02-18 12:43:30 -08:00
github-actions[bot] 7c782abaf4 Changeset version bump (#9364)
* changeset version bump

* Updating CHANGELOG.md format

* update package versions

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: Max Paulus 🥪 <max@cline.bot>
2026-02-18 12:22:53 -08:00
Ara 3a14a88f4f fix: include root workspace for changesets release (#9393) 2026-02-18 11:40:02 -08:00
Saoud Rizwan 28d60a83a4 fix(models): keep Sonnet 4.5 as default for now (#9389)
* fix(models): keep Sonnet 4.5 as default

* chore(changeset): add release note for Sonnet 4.5 default

* fix(models): remove Sonnet 4.6 from curated model lists

* fix(models): restore Sonnet 4.6 in web recommended list
2026-02-18 10:53:07 -08:00
Saoud Rizwan ae6468b161 fix(models): reinstate minimax m2.5 free promo surfaces (#9387) 2026-02-18 10:28:54 -08:00
Saoud Rizwan af93e31862 feat(models): make sonnet 4.6 default and remove free promo positioning (#9377) 2026-02-18 10:07:20 -08:00
Tomás Barreiro ca154eb8f5 Add MiniMax M2.5 to the MiniMax provider (#9381)
* Add MiniMax M2.5 to the MiniMax provider

* Add changeset

* Update default model
2026-02-18 09:16:23 -08:00
Tomás Barreiro 1d8497c6bf Prevent error messages when displaying featured models in the CLI (#9379)
* Fix the featured models key

* Add changeset
2026-02-18 16:51:29 +01:00
Seb Duerr 5871fd02b1 feat(cerebras): remove deprecated llama-3.3-70b and qwen-3-32b models (#9345)
* feat(cerebras): remove deprecated llama-3.3-70b and qwen-3-32b models

These models have been deprecated from the Cerebras inference platform.

- Remove llama-3.3-70b and qwen-3-32b from cerebrasModels in api.ts
- Update supported models documentation in cerebras.mdx
- Add changeset for the deprecation

* fix: remove stale llama-3.3-70b and qwen-3-32b references from rate limits

Remove dead switch cases in getRateLimits() that referenced deprecated models
no longer present in cerebrasModels.
2026-02-17 23:39:38 -08:00
alex-lum 2d81c310d2 Alex/inf 413 bug no telemetry for versions greater than 20 (#9372) 2026-02-17 18:55:07 -08:00
Robin Newhouse 0c691f72d2 feat(cli): add /skills slash command (#9089)
* feat(cli): add /skills slash command for managing skills

- Add /skills to CLI_ONLY_COMMANDS in slashCommands.ts
- Create SkillsPanelContent component with:
  - Display global and workspace skills with toggle indicators
  - Enter to use skill (inserts @path into input)
  - Space to toggle skill enabled/disabled
  - Selectable marketplace link to skills.sh
  - Keyboard navigation with arrow keys and vim keys
- Wire up panel in ChatView.tsx
- Add comprehensive tests for keyboard interactions

* refactor(cli): use static skill controller imports

* fix(cli): add React import to skills panel test

* fix(cli): suppress required React import lint in skills test

* fix(cli): harden /skills panel interactions

Revert optimistic skill toggle state when persistence fails, and surface a fallback URL when opening the marketplace fails. Also tighten and extend tests to verify exact marketplace URL handling and rollback behavior.

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

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-17 17:13:51 -08:00
Robin Newhouse a8409137b3 fix: disable click-to-set auto-condense threshold and hardcode default (#9348)
* fix: disable click-to-set auto-condense threshold and hardcode default

Clicking anywhere on the context window progress bar silently set
autoCondenseThreshold to a value based on click position (e.g. 0.05),
persisting in globalState. This caused compaction to fire at ~10K tokens
instead of the intended ~150K, resulting in ~20 context resets per task.

- Comment out click and keyboard handlers on progress bar (keep components
  for future release with proper UX)
- Hardcode threshold to 0.75 default, ignoring corrupted stored values

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

* test: add shouldCompactContextWindow unit tests

Cover threshold math including the accidental low-threshold bug case,
undefined/zero fallbacks, cache token inclusion, and maxAllowedSize cap.

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

* fix: hardcode autoCondenseThreshold in all remaining callsites

Address Greptile review: SubagentRunner.ts, task/index.ts display
logic, and controller/index.ts webview state all still read the
corrupted value from globalState. Hardcode 0.75 everywhere.

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

* style: remove unnecessary union type on hardcoded threshold

Drop `number | undefined` annotation from the hardcoded 0.75 literal
in SubagentRunner.ts per Greptile review feedback.

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

* refactor: use SETTINGS_DEFAULTS constant, remove commented-out code, clarify test

- Replace hardcoded 0.75 with SETTINGS_DEFAULTS.autoCondenseThreshold
  across all 4 callsites for a single source of truth
- Delete commented-out click/keyboard handlers in ContextWindow.tsx,
  replace with TODO referencing PR #9348
- Make bug-case test self-documenting by deriving token values from
  the threshold calculation

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

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-17 16:32:13 -08:00
Saoud Rizwan 34a21b8e26 docs: add security policy (#9365) 2026-02-17 16:00:01 -08:00
ClineXDiego eb9f53edf4 fix: smarter retry for write_to_file missing content parameter (#9276)
* fix: smarter retry for write_to_file missing content parameter (#7998)

Replace generic 'missing parameter' error with progressive guidance when
write_to_file fails due to empty content parameter. This breaks the
infinite retry loop where the model repeatedly attempts the same
write_to_file call that exceeds output token limits.

Changes:
- Add writeToFileMissingContentError() to formatResponse with 3 tiers:
  1st failure: suggestions (use skeleton + replace_in_file)
  2nd failure: strong directive (stop retrying write_to_file)
  3rd+ failure: CRITICAL stop, forces alternative strategies
- Add context window awareness: warns model when >50% context used
- Add getContextUsagePercent() helper to WriteToFileToolHandler
- Add 22 unit tests covering progressive escalation and context awareness

Fixes #7998

* add changeset for write_to_file retry fix

* refactor: simplify write_to_file error handling per review

- Simplify writeToFileMissingContentError to single-tier error following
  existing diffError pattern (no progressive escalation)
- Use shared getLastApiReqTotalTokens() for context window awareness
- Remove private getContextUsagePercent() method from handler
- Add proactive skeleton + replace_in_file guidance to write_to_file
  tool description for all variants
- Simplify tests to match new API (11 tests)

* test: update system prompt snapshots

* chore: revert write_to_file prompt guidance

* feat: restore progressive 3-tier guidance for write_to_file missing content

Restore the progressive escalation that was removed in dd3c12d4e:
- Tier 1 (1st failure): Gentle suggestions (skeleton + replace_in_file)
- Tier 2 (2nd failure): Strong directive, 'Do NOT attempt full write again'
- Tier 3 (3rd+ failure): CRITICAL stop, forces alternative strategies
- Context window warning when >50% full
- Dynamic UI message: 'Retrying...' vs 'multiple times — different approach'
- 21 tests covering all tiers and context awareness

* nit: extract context window warning threshold to named constant

Also replace emoji with plain text in warning message for consistency.

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

---------

Co-authored-by: Robin Newhouse <robin@cline.bot>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-17 19:08:42 -03:00
github-actions[bot] c60f18d907 Adding 1m (#9346)
Co-authored-by: Arafatkatze <arafat.da.khan@gmail.com>
2026-02-17 11:11:54 -08:00
Saoud Rizwan 36c68a6ab9 fix: remove expired MiniMax free promo surfaces (#9361)
* fix: remove expired MiniMax free promo surfaces

* fix: remove MiniMax M2.5 from recommended models

* chore: update GLM 5 whats-new promo wording
2026-02-17 10:57:19 -08:00
Saoud Rizwan 80dfce0f60 docs: remove stale Claude 5 mention from Auto Compact docs (#9360)
* docs: remove stale Claude 5 wording from auto compact docs

* Remove stale Claude 5 mention from docs
2026-02-17 10:48:23 -08:00
Saoud Rizwan 955ae2f62f feat: add Claude Sonnet 4.6 support and surface it as free (#9356)
* feat: add Sonnet 5 support and make it default across surfaces

* feat: surface Sonnet 5 as free while keeping Sonnet 4.5 defaults

* fix: rename Sonnet 5 support to Sonnet 4.6 across providers and UI

* fix: allow duplicate onboarding model ids across free and frontier

* chore: update Sonnet 4.6 banner to limited-time free messaging

* fix: align Bedrock Sonnet 4.6 model ids with AWS format

* feat: update whats new promo to Sonnet 4.6 free offer

* chore: update Sonnet 4.6 promo copy and timing
2026-02-17 10:43:24 -08:00
Dominic Cooney 8cb0c6d236 Fix e2e tests. (#9350) 2026-02-17 15:06:40 +09:00
github-actions[bot] bb05b2f7b0 changeset version bump (#9316)
Updating CHANGELOG.md format

update changelog

update banner and bump version

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-16 12:46:01 -08:00
alex-lum 1a911f4232 fix(telemetry): refresh OTEL org attributes on identify (#9318)
* fix(telemetry): refresh OTEL user/org attributes on every identify

* fix(telemetry): refresh OTEL user/org attributes on every identify

* test(telemetry): cover OTEL identifyUser org refresh scenarios

* refactor(telemetry): rename member_roles to member_role (singular)

* Apply suggestion from @BarreiroT

simpler commenting

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

* removing verbose comments

/**
 * Helper to build a ClineAccountUserInfo with an active organization.
 */

* removing verbose comments

* removing unnecessary logger

* assert -> chai expect

---------

Co-authored-by: Tomás Barreiro <52393857+BarreiroT@users.noreply.github.com>
2026-02-16 12:45:31 -08:00
Ara 1cfd560921 feat(cline): add z-ai/glm-5 to free model recommendations (#9341)
* feat: add z-ai/glm-5 to free models list

Include Z.AI's GLM 5 in the free model whitelist for zero-cost usage
and update the model picker UI to display the free label.

* Adding thinking

* Adding thinking

* Adding thinking
2026-02-16 11:59:09 -08:00
Juan Pablo Flores 203ff8f549 docs: enrich Quick Start guide with more context for new users (#9338)
* docs: enrich Quick Start guide with more context for new users

* docs: add Antigravity to full installation guide
2026-02-16 09:00:50 -08:00
Saoud Rizwan e920f1de02 fix(chat): keep reasoning visible before low-stakes tool groups (#9335)
* fix(chat): keep reasoning visible before low-stakes tool groups

* chore(changeset): add patch note for tool-group reasoning visibility

* fix(chat): keep thinking loader visibility aligned with waiting states

* fix(chat): avoid clipping descenders in thinking label
2026-02-15 17:14:41 -08:00
Saoud Rizwan b266475d0a fix(chat): prevent partial row churn during native tool arg streaming (#9334)
* fix(chat): prevent partial text flicker during native tool streaming

* fix(chat): revert act mode partial dedupe change
2026-02-15 15:40:54 -08:00
Saoud Rizwan 402361c482 fix(chat): restore reasoning traces and polish thinking UX (#9330)
* fix(chat): restore reasoning traces and polish thinking UX

* chore(changeset): add patch note for reasoning trace UX fixes
2026-02-15 01:19:59 -08:00
Renee Huang e884699d24 New Cline Docs (#9280)
* only doc changes

* merge

* fix installtion page redirects

* fix redirect, remove unused parts

* rm irrelevant info

* clean up terminal guides

* docs: add home page and reorganize navigation

* chore: remove 71 unused docs files not referenced in navigation

Remove .mdx files that are no longer referenced in docs.json navigation
and only existed as stale content from previous documentation restructuring.
These files were either completely orphaned or only served as redirect
source pages (Mintlify handles redirects at the routing level without
needing the source file to exist).

Updated docs.json redirects that previously pointed to archive/ pages
to point to current equivalents instead:
  - /archive/understanding-context-management → /model-config/context-windows
  - /archive/prompt-engineering-guide → /customization/cline-rules
  - /archive/telemetry → /enterprise-solutions/monitoring/telemetry

Deleted files by category:

Archive (entire directory removed):
  - archive/prompt-engineering-guide.mdx
  - archive/telemetry.mdx
  - archive/understanding-context-management.mdx

Cline CLI:
  - cline-cli/cli-reference-deprecated.mdx

Enterprise Solutions (16 files):
  - enterprise-solutions/bundled-endpoints.mdx
  - enterprise-solutions/configuration/overview.mdx
  - enterprise-solutions/configuration/choosing-your-deployment.mdx
  - enterprise-solutions/configuration/infrastructure-configuration/overview.mdx
  - enterprise-solutions/configuration/infrastructure-configuration/rules.mdx
  - enterprise-solutions/configuration/infrastructure-configuration/workflows.mdx
  - enterprise-solutions/configuration/infrastructure-configuration/control-other-cline-features/overview.mdx
  - enterprise-solutions/configuration/infrastructure-configuration/mcp/overview.mdx
  - enterprise-solutions/configuration/infrastructure-configuration/mcp/mcp-marketplace.mdx
  - enterprise-solutions/configuration/infrastructure-configuration/mcp/remote-mcp-servers.mdx
  - enterprise-solutions/configuration/infrastructure-configuration/providers/overview.mdx
  - enterprise-solutions/configuration/infrastructure-configuration/providers/custom.mdx
  - enterprise-solutions/configuration/infrastructure-configuration/providers/aws-bedrock/overview.mdx
  - enterprise-solutions/configuration/infrastructure-configuration/providers/google-vertex/overview.mdx
  - enterprise-solutions/configuration/infrastructure-configuration/providers/litellm/overview.mdx
  - enterprise-solutions/monitoring/opentelemetry_override.mdx

Exploring Cline's Tools (entire directory removed):
  - exploring-clines-tools/cline-tools-guide.mdx
  - exploring-clines-tools/new-task-tool.mdx
  - exploring-clines-tools/remote-browser-support.mdx

Features — old pages consolidated into core-workflows/ and customization/ (37 files):
  - features/checkpoints.mdx
  - features/drag-and-drop.mdx
  - features/editing-messages.mdx
  - features/explain-changes.mdx
  - features/skills.mdx
  - features/yolo-mode.mdx
  - features/at-mentions/ (7 files — all consolidated into core-workflows/working-with-files)
  - features/cline-rules/ (2 files — consolidated into customization/cline-rules)
  - features/commands-and-shortcuts/ (5 files — consolidated into core-workflows/using-commands)
  - features/customization/ (2 files)
  - features/hooks/ (3 files — consolidated into customization/hooks)
  - features/slash-commands/ (7 files)
  - features/slash-commands/workflows/ (3 files — consolidated into customization/workflows)
  - features/tasks/ (2 files — consolidated into core-workflows/task-management)

Introduction (entire directory removed):
  - introduction/overview.mdx
  - introduction/welcome.mdx

MCP:
  - mcp/adding-mcp-servers-from-github.mdx
  - mcp/configuring-mcp-servers.mdx

More Info (entire directory removed):
  - more-info/telemetry.mdx

Prompting (entire directory removed):
  - prompting/cline-memory-bank.mdx
  - prompting/prompt-engineering-guide.mdx
  - prompting/understanding-context-management.mdx

Provider Config:
  - provider-config/fireworks-ai.mdx
  - provider-config/ollama.mdx

Getting Started:
  - getting-started/selecting-your-model.mdx

Total: 71 files deleted, 12,344 lines removed.

* docs: update and add documentation pages

* revert unintended formatting changes to src files

* new first project docs

---------

Co-authored-by: Juan Pablo Flores <juan@cline.bot>
2026-02-14 22:02:22 -08:00
Saoud Rizwan f59d950ac6 fix: open Cline sidebar for task deeplinks (#9320)
* fix: open Cline sidebar for task deeplinks

* refactor: share task URI path constant
2026-02-14 01:15:13 -08:00
Ara d2e4f1c7b9 feat(zai): add glm-5 pricing and make it default (#9253) 2026-02-13 20:42:49 -08:00
Ara 49975fd0a3 feat(cli): support Moonshot provider across CLI flows (#9314)
* feat(cli): add moonshot provider support across CLI flows

* Update cli/man/cline.1

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

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-13 16:10:10 -08:00
shey-cline c0020e10b1 Allow Custom AWS Regions in Bedrock (CLI) (#9103)
* init

* changeset

* prevent regoinIndex = -1 when navigating with arrow keys while filteredRegions.length = 0

* refactor

* content fix
2026-02-13 15:46:36 -08:00
Ara 8dc5e15ee0 chore: bump version to 3.62.0 (#9313) 2026-02-13 15:13:36 -08:00
github-actions[bot] 276cb4c3a4 Changeset version bump (#9312)
* changeset version bump

* v3.62.0 Release Notes

- Banners now display immediately when opening the extension instead of requiring user interaction first
- Resolved 17 security vulnerabilities including high-severity DoS issues in dependencies (body-parser, axios, qs, tar, and others)

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@github.com>
2026-02-13 14:24:54 -08:00
Saoud Rizwan 8169abbc74 fix(e2e): stabilize banner and code action assertions (#9311) 2026-02-13 14:11:55 -08:00
Ara a47ad46824 fix: rename package from "claude-dev" to "cline" in changesets (#9309)
Update changeset metadata files to use the correct package name
"cline" instead of the legacy "claude-dev" identifier.
2026-02-13 14:08:57 -08:00
Saoud Rizwan 7896e6d896 feat: promote MiniMax M2.5 in top banner and route CTA to free tab (#9307)
* feat: add minimax promo banner and free-tab model routing

* Add changeset for promoting MiniMax M2.5
2026-02-13 13:20:22 -08:00
aikido-autofix[bot] 166ec38d26 fix(security): update dependencies (#9234)
Co-authored-by: aikido-autofix[bot] <119856028+aikido-autofix[bot]@users.noreply.github.com>
2026-02-13 12:42:06 -08:00
Tomás Barreiro e44ea9d772 Post state to webview after fetching the banners (#9306)
* Post state to webview after fetching

* Test post state to webview is called

* refactors
2026-02-13 21:18:32 +01:00
Ara fcca1d4fe3 v3.61.0 Release Notes (#9305) 2026-02-13 10:35:16 -08:00
github-actions[bot] 34f0795217 v3.60.0 Release Notes (#9274)
- Fixes for Minimax model family
- Fixes for Response chaining for OpenAI's Responses API

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-13 09:02:46 -08:00
Bee 0648ed42b2 feat: make response ID chaining configurable (#9285)
Add an optional `usePreviousResponseId` flag to
`convertToOpenAIResponsesInput` and gate previous assistant-response
lookup behind it.

This makes response chaining opt-in instead of always-on, so providers
can control whether to continue from the latest stored OpenAI response
ID.feat(openai): make response ID chaining configurable

Add an optional `usePreviousResponseId` flag to
`convertToOpenAIResponsesInput` and gate previous assistant-response
lookup behind it.

This makes response chaining opt-in instead of always-on, so providers
can control whether to continue from the latest stored OpenAI response
ID.
2026-02-13 08:40:02 -08:00
Saoud Rizwan f32dde2c16 chore: update codex environment config 2026-02-13 04:17:07 -08:00
Saoud Rizwan 605c497eaf chore: update codex environment config 2026-02-13 04:14:22 -08:00
Saoud Rizwan a8322eec44 fix(task): avoid duplicate partial text from reentrant presentation (#9302) 2026-02-13 03:47:56 -08:00
Saoud Rizwan 974a7a748a fix(webview): add spacing before ask followup options 2026-02-13 02:31:14 -08:00
Saoud Rizwan fb2d092301 fix(hooks): preserve streaming tool rows in combineHookSequences (#9301) 2026-02-13 02:10:27 -08:00
Saoud Rizwan 45b3eb4833 fix(chat): stabilize tool-group text and thinking footer behavior (#9300)
* fix(chat): ignore streamed text after tool group starts

* fix(chat): suppress thinking during ask and completion handoff

* fix(chat): prevent thinking footer shimmer remount flicker
2026-02-13 02:03:32 -08:00
Bee a5048189e5 fix: remove focused BannerService test to run full suite (#9299)
The unit test suite currenlt is running the BannerService tests only when it should run the full suite.
Also update package-lock.json that wentout of sync.
2026-02-13 01:08:30 -08:00
Saoud Rizwan 897e842eb4 fix(task): ignore interleaved reasoning UI after text starts (#9298) 2026-02-13 00:25:08 -08:00
Saoud Rizwan 0389d4de07 Revert "fix(task): prevent duplicate streamed text rows after completion (#9235)" (#9297)
This reverts commit b514f18e4f.
2026-02-13 00:20:33 -08:00
Saoud Rizwan d99eec15d8 fix(minimax): emit single reasoning chunk on thinking start (#9290) 2026-02-12 23:46:53 -08:00
Ara 98ed009e69 fix: add missing name fields to free featured models and improve type safety (#9291)
- Add `name` property to minimax, kat-coder-pro, and trinity-large-preview
  models that were previously missing it
- Move type annotation from `as FeaturedModel[]` casts to the variable
  declaration for proper type checking at assignment time
- Add test to verify all featured models include a display name
2026-02-12 23:18:51 -08:00
Saoud Rizwan 8fb7b94297 Revert "Jose/thinking and flicker fix (#9148)" (#9292)
This reverts commit d8397c71b2.
2026-02-12 22:54:07 -08:00
Jose R. Perez d8397c71b2 Jose/thinking and flicker fix (#9148)
* feat: persistant thinking loader at bottom of stream during any cline activity with no visual feedback

* feat: thinking and flicker fix

* refactor: remove multi-layer throttling, use single canonical throttle point

Collapse 4 independent throttle layers (up to ~500ms added latency) into
a single 50ms debounce in subscribeToPartialMessage. Replace index-based
partial message tracking with stable ts-based tracking. Remove webview
queue/timer/flush system in favor of cheap equality dedup.

* fix: Add production-grade improvements to flicker fix

- Fix global mutable state bug in subscribeToPartialMessage.ts
- Add comprehensive test coverage (51 tests passing)
- Rename ThrottledApiHandler → SanitizedApiHandler
- Remove incomplete OpenAI reasoning effort code

* Fix test failures

* PR changes as per Greptile feedback

* Fixes as per feedback during PR review

---------

Co-authored-by: cline-test <132302818+candieduniverse@users.noreply.github.com>
2026-02-12 22:28:46 -08:00
Robin Newhouse 3899469d76 feat(evals): comprehensive LLM evaluation framework with CI (#8909)
* chore(evals): reorganize eval structure with purpose-based naming

- Move evals/diff-edits/ → evals/benchmarks/tool-precision/replace-in-file/
- Move evals/cli/ → evals/legacy/cli/ (preserve for reference)
- Create evals/benchmarks/real-world/ directory
- Create evals/benchmarks/coding-exercises/cases/ directory
- Create evals/analysis/ directory structure

Note: No repositories/exercism/ directory found to move.
Skipping pre-commit hook as this is a reorganization of legacy code.

* chore(evals): remove legacy evaluation code

Remove abandoned evaluation infrastructure:
- evals/benchmarks/tool-precision/ - Dashboard, database, diff implementations
- evals/legacy/cli/ - Old HTTP-based eval harness

This functionality is superseded by the new testing pyramid:
- Tool precision is now covered by contract tests in src/core/
- E2E testing uses the cline-bench framework

* feat(evals): add analysis framework for benchmark results

Add shared infrastructure for analyzing evaluation results:
- TypeScript schemas for Harbor and analysis output formats
- Parsers for Harbor, tool-precision, and exercise results
- Failure classifier with pattern matching (cline-failures.yaml)
- Metrics calculator (pass@k, consistency, latency)
- JSON and Markdown reporters
- CLI with analyze and compare commands
- Unit tests for classifier and metrics

This framework is used by both smoke tests and E2E evaluations
to provide consistent metrics and failure categorization.

* feat(evals): add contract tests for API transforms

Add tests to verify API response transformations preserve data correctly:
- thinking-traces.test.ts: Tests thinking block extraction and formatting
- tool-parsing.test.ts: Tests tool call parsing across providers

These contract tests catch regressions when modifying transform logic,
ensuring API responses are correctly processed regardless of provider.
Run with: npm run test:unit

* feat(evals): add provider smoke tests with pass@k metrics

Add lightweight smoke tests that validate provider integrations work
correctly with real LLM calls:

Scenarios (5 curated tests):
- 01-create-file: Tests write_to_file tool
- 02-edit-file: Tests replace_in_file tool
- 03-read-summarize: Tests read_file tool
- 04-multi-file: Tests multi-file edits
- 05-typescript-function: Tests code generation

Features:
- CLI-based runner using the cline CLI
- Multiple trials per scenario for reliability testing
- pass@k metrics (solution finding) and pass^k (consistency)
- Results storage with logs and latest symlink
- Adaptive metric display based on trial count

Run locally: npm run eval:smoke

* feat(evals): add E2E runner with cline-bench

Add end-to-end testing infrastructure using real-world production bugs:

- cline-bench submodule: 12 curated tasks from actual Cline sessions
  - Complex multi-file refactors
  - Bug fixes requiring deep context understanding
  - Cross-language/framework tasks

- E2E runner (evals/e2e/run-cline-bench.ts):
  - Integrates with Harbor for containerized execution
  - Supports single task or full suite runs
  - Pass/fail metrics with detailed logging

Run: npm run eval:e2e -- --task discord-trivia

Note: E2E tests require Docker and are intended for weekly/release
testing, not per-commit CI (each task takes 20-30 minutes).

* feat(evals): add CI workflow and documentation

CI Workflow (.github/workflows/cline-evals-regression.yml):
- Triggers on push/PR to main (src/core, src/shared, proto, evals paths)
- Builds CLI from source with Go 1.24
- Runs 5 smoke test scenarios in parallel
- Uses Anthropic API with claude-sonnet-4
- Uploads results as artifacts with summary

npm scripts:
- eval:smoke - Run smoke tests locally (builds CLI first)
- eval:smoke:run - Run smoke tests (assumes CLI is built)
- eval:e2e - Run cline-bench E2E tests

Documentation:
- ARCHITECTURE.md: Testing pyramid overview with ASCII diagrams
- EVALS_OVERVIEW.md: High-level introduction for mixed audience
- Updated README.md with current structure and usage

* chore(evals): restore tool-precision as deprecated legacy

Restore the diff edit evaluation framework for @ara's use case.
Marked as DEPRECATED - target removal Q2 2026 when cline-bench
is fully operational for model comparison.

Note: Skipping linter as this is legacy code being preserved as-is.

* feat(evals): add per-scenario model support and apply_patch test

Also honor --model overrides and prune stubs.

* chore(evals): update smoke tests for CLI 2.0

- Remove Go setup from workflow (CLI 2.0 is TypeScript)
- Build CLI via `npm run build` in cli/ directory
- Install CLI via `npm link` to test built code from PR
- Update CLI flags: -y -m model --json (remove -o and -s)
- Provider configured via `cline auth` before tests run

* chore(evals): add auth check and CLI 2.0 flags

- Add configureAuth() that runs cline auth non-interactively
- Require CLINE_API_KEY env var or use existing ~/.cline auth
- Add --config flag to use shared config directory
- Add -t timeout flag to CLI args
- Reduce scenario timeout to 30s for faster iteration
- Remove --json flag (CLI doesn't output errors in json mode)

* feat(evals): add parallel execution and move workspaces to results

- Add --parallel flag to run scenarios concurrently (default limit: 4)
- Move trial workspaces from scenarios/ to results/ directory
- Workspaces now cleaned up with `npm run eval:smoke:clean`
- Keeps scenarios/ clean and version-controllable

* ci: add smoke tests workflow with parallel execution

- Single job runs all 7 scenarios in parallel using test runner's --parallel flag
- Builds CLI in-job (no artifact passing needed)
- Outputs summary.md to GitHub step summary
- Syncs package-lock.json for tiktoken/commander deps

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

* fix(evals): increase 01-create-file timeout to 120s

The 30s timeout was too short for reliable execution.

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

* chore: restore changesets deleted during rebase

These changesets belong to the already-merged CLI fix (#9073)
and should not be deleted by this branch.

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

* chore(evals): remove unused dependencies from package.json

Drop execa, node-fetch, ora, sqlite, uuid, yargs and their types.
These were leftovers from the old CLI-based eval runner. The smoke
tests use Node builtins and the tool-precision benchmark only needs
axios, better-sqlite3, chalk, commander, dotenv, tiktoken.

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

* Add TypeScript build info files to .gitignore

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
2026-02-12 22:23:35 -06:00
Robin Newhouse e85332319e feat: add .agents/skills directory support for skill discovery (#9074)
* feat: add .agents/skills directory support for skill discovery

Add compatibility for the standardized .agents/skills directory pattern,
both globally (~/.agents/skills) and locally (.agents/skills in workspace).

* feat: make .agents/skills the default for new skills

New skills are now created in .agents/skills (local) and ~/.agents/skills
(global) by default. These directories also have highest priority in
skill discovery, overriding skills with the same name from other locations.

* docs: update skills documentation for .agents/skills directories

* refactor skills directory helpers
2026-02-12 22:08:30 -06:00
Bee 1a29d428ae refactor: BannerService initialization and cache management (#8969)
* increases banner cache duration to 24 hours so we make one api calls per day per user; implements a circuit breaker that stops retrying after 3 consecutive failures

* add new tests

* Clear banner cache when auth status changes

* revert 5898bc6e0e

* Fixing circuit breaker

* fix: reset circuitBreakerOpenedAt on failed half-open recovery

Previously, circuitBreakerOpenedAt was only set when consecutiveFailures
reached exactly MAX_CONSECUTIVE_FAILURES. This meant that after a failed
half-open recovery attempt, the timestamp wasn't updated, causing the
circuit breaker to immediately enter half-open state again on the next call.

Now circuitBreakerOpenedAt is updated on every failure once the circuit
breaker is tripped, ensuring proper timeout between recovery attempts.

* refactor: BannerService initialization and cache management

- Move BannerService initialization from common.ts to AuthService (which is initialized in controller)
- Re-initialize BannerService after auth state updates to ensure user context
- Add HostRegistryInfo to centralize host/platform information collection
- Improve rate limiting with exponential backoff (5min → 15min → 30min)
- Refactor error handling to better distinguish between rate limits and server errors
- Remove temporary disabled banner fetching comments

This change ensures banners are only fetched when user authentication is
available and implements more robust rate limiting to prevent API hammering.
The banner service now properly tracks user context and respects server
rate limits with progressive backoff delays.

* refactor(banner): simplify banner service initialization and usage

- Remove `getBanners()` wrapper method from Controller class
- Call `BannerService.get().getActiveBanners()` directly in Controller
- Change `BannerService.initialize()` to synchronous, returns instance immediately
- Make banner fetching non-blocking by moving to background
- Remove unused `BannerCardData` import from Controller
- Update tests to handle asynchronous background fetching with timeouts
- Clean up AuthService banner service initialization comment

This change simplifies the banner service API by removing unnecessary abstraction layers and making initialization non-blocking. The service now fetches banners in the background rather than blocking on initialization, improving application startup performance.

* clean up

* apply feedback

* un-skip unit test

* mock

* mock env

* clean up and add debounce fetch

* log fetch time

* revert

* feature flag: remote-banners

* fix loop in authService on auth update

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

* Fix tests

* small fixes

* use .? for banner

* moves initializeDistinctId to StateManager

* initializeDistinctId

* use v2 endpoint

---------

Co-authored-by: Zhongying Qiao <cryptoque@users.noreply.github.com>
Co-authored-by: Arafatkatze <arafat.da.khan@gmail.com>
Co-authored-by: Tomás Barreiro <BarreiroT@users.noreply.github.com>
Co-authored-by: Tomás Barreiro <52393857+BarreiroT@users.noreply.github.com>
Co-authored-by: BarreiroT <tomasmbarreiroi@gmail.com>
2026-02-12 18:28:12 -08:00
Saoud Rizwan 92cf03e42c feat(subagents): simplify research output guidance and command workflow (#9284) 2026-02-12 16:00:02 -08:00
Bee 3ea393a5e9 fix: openai native provider token usage mapping (#9272)
* fix: openai native provider token usage mapping

- Add `store` parameter support to OpenAI native provider options to allow persisting completions.
- Fix incorrect mapping of `cached_tokens` and `reasoning_tokens` in usage statistics.
- Include `thoughtsTokenCount` in the final usage report to track reasoning model performance and costs.

* Update src/core/api/providers/openai-native.ts

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-02-12 14:26:35 -08:00
Max 9829e7d49e restore yolo mode to what it was before cline cli started (#9205)
Apply suggestions from code review

Co-authored-by: Max Paulus 🥪 <max@cline.bot>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-12 11:10:24 -08:00
Max 56de96e5ff fix oca auth (#9145)
Co-authored-by: Max Paulus 🥪 <max@cline.bot>
2026-02-12 10:20:28 -08:00
github-actions[bot] ecde79cf08 v3.59.0 Release Notes (#9263)
- Added Minimax 2.5 Free Promo
- Fixed Response chaining for OpenAI's Responses API

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-12 10:20:19 -08:00
Bee ff36cbcb87 feat: implement response chaining for Responses API (#9270)
* feat: implement response chaining for Responses API

Implement response chaining by tracking and passing previous_response_id
to continue conversations from the last assistant message. This enables
the Responses API to maintain context across multiple turns.

Key changes:
- Search backwards through messages to find last assistant message with ID
- Only send new messages after the chained response
- Track function call metadata (call_id, name, id) across chunks
- Include call_id in tool_call events for proper correlation
- Clean up debug logging and remove commented code
- Remove redundant "Ran out of tokens" log message

This improves conversation continuity and ensures function calls are
properly tracked with their associated IDs throughout the streaming
response lifecycle.

* clean up

* update oca

* codex
2026-02-12 10:01:34 -08:00
shey-cline 5a75f08118 Prevent Parent Container Scrolling In Dropdowns (#9146)
* init

* missed some dropdowns & make scroll behavior work for scrolling nested elements

* add changeset

* add combobox roles
2026-02-12 10:00:40 -08:00
shey-cline 120754c2fe Allow Custom AWS Regions in Bedrock (Extension) (#9104)
* init

* changeset

* addressed comments – add onBlur, aria attributes and redundant useMemo
2026-02-12 09:57:04 -08:00
Saoud Rizwan 5d048d09f8 fix(subagents): retry initial stream bootstrap failures (#9264)
* fix(subagents): retry initial stream bootstrap failures

* fix(subagents): align initial retry classification with main loop

* fix(subagents): compact context on window limit during startup

* fix(subagents): proactively compact context at token thresholds

* feat(subagents): optimize file reads before context truncation
2026-02-12 06:34:43 -08:00
Saoud Rizwan 36580ce086 chore(codex): update environment to use launch script and simplify reinstall
Point the VS Code action at the new run-extension-host.sh script and
drop the git checkout of lock files from the reinstall action.
2026-02-12 05:53:25 -08:00
Saoud Rizwan c584bf4185 feat(dev): add tmux-based extension host launch script
Replaces the inline VS Code launch command with a proper dev script that:
- Builds protos and webview upfront
- Runs esbuild, tsc, and webview watchers in parallel tmux panes
- Waits for dist/extension.js before launching the extension host
- Cleans up all processes and closes the dev window on Ctrl+C
2026-02-12 05:53:18 -08:00
Saoud Rizwan 8133babf41 fix(chat): keep focus chain placeholder visible to prevent layout jump (#9266)
* fix(webview): stabilize focus chain header space and placeholder

* fix(chat): add follow-up bottom scroll to avoid short scroll

* style(chat): refine markdown spacing and tool group summary tone

* fix(chat): retry auto-scroll at 40ms and 70ms

* fix(chat): keep focus chain placeholder visible until checklist exists
2026-02-12 03:50:01 -08:00
Saoud Rizwan 741f524da7 chore(deps): upgrade openai sdk to 6.21.0 for xhigh reasoning (#9267) 2026-02-12 03:48:13 -08:00
Robin Newhouse d3918dd7df fix(task): canonicalize attempt_completion result parameter (#9262) 2026-02-12 00:37:27 -06:00
alex-lum 024bb65443 Add organization attributes to telemetry metrics (#9242) 2026-02-11 16:51:24 -08:00
github-actions[bot] 58ebbdbf80 Changeset version bump (#9252)
* changeset version bump

* Updating CHANGELOG.md format

* changeset version bump

* Updating CHANGELOG.md format

* Eve manually updating the banner and the release version

* Manually update the changelog

* Fix GLM 5 model ID in banner

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: cline-test <132302818+candieduniverse@users.noreply.github.com>
2026-02-11 16:30:14 -08:00
Juan Pablo Flores cee74d2f3a docs: add subagents feature documentation (#9258)
* docs: add subagents feature documentation

Add new documentation page covering the Subagents feature, including
how it works, enabling/configuring, auto-approve behavior, available
tools, and usage guidance. Register the page in docs.json sidebar nav.

* docs: remove hardcoded subagent limit from subagents page

Remove references to 'up to five' subagents, as the limit is no longer
fixed. Updates both the intro paragraph and the How It Works section.
2026-02-11 15:54:57 -08:00
Ara a6f3b9f856 Revert "fix MCP OAuth: add missing scope parameter (#9117)" (#9256)
This reverts commit 401358374f.
2026-02-11 15:07:34 -08:00
Ara 0e524ffc3a feat(zai): add glm-5 pricing and make it default (#9254)
* feat(zai): add glm-5 pricing and make it default

* fix(zai,qwen): fallback model id when apiModelId is invalid
2026-02-11 14:05:50 -08:00
Ara 95ca14fa2a Fixing changeset files (#9251) 2026-02-11 12:38:31 -08:00
Max a6c57a4ce5 print task id in headless modes (#9229)
Co-authored-by: Max Paulus 🥪 <max@cline.bot>
2026-02-11 12:21:02 -08:00
Tomás Barreiro 9470cf19cd Add headers to the Remotely Configured MCP server schema (#9238) 2026-02-11 18:53:44 +01:00
Saoud Rizwan acf34860bb chore: fix codex desktop app configuration 2026-02-11 02:34:33 -08:00
Saoud Rizwan 49cad88aca chore: fix codex desktop app configuration 2026-02-11 02:25:59 -08:00
Saoud Rizwan 3c65cbc7f2 chore: add codex desktop app configuration 2026-02-11 02:22:29 -08:00
Saoud Rizwan 12603d4be1 feat: replace legacy CLI subagents with native use_subagents tool (#9208)
* feat: checkpoint subagent tool workflow and approval UX

* feat: support subagent tool execution without native tool calls

* fix: expose use_subagents when native tool calling is disabled

* fix: stabilize subagent command UX and suppress nested command rows

* chore: tune subagent prompt guidance for context-heavy exploration

* fix: align subagent row spacing with chat row conventions

* fix: keep cancelled subagent state during immediate resume

* feat: implement subagent message rendering for approval prompts and progress updates

* feat: enhance SubagentRunner with tool use ID resolution and fallback handling

* fix: stabilize subagent cline requests with ulid and initial workspace metadata

* refactor: unify subagent chat row rendering

* feat: surface subagent costs in task metrics and status rows

* fix: refine cli subagent tree alignment and wrapping

* fix: refine subagent streaming rows in cli and webview

* fix: ensure unique act mode hint keys in CLI chat

* feat: add subagents settings toggle wiring across webview and cli

* fix(webview): stream subagent stats per prompt while constructing prompts

* fix: remove duplicate subagentsEnabled declaration after rebase

* chore: restore package lockfiles to main

* fix: harden task history usage parsing and clean prompt separators

* chore: refine subagent response formatting guidance

* feat: collapse subagent prompts with show more

* feat: show latest subagent tool call in status rows

* fix: fall back to non-native mode for subagents when native tools are unavailable

* fix: retry empty subagent responses before failing

* fix(subagents): require attempt_completion and dedupe tool result formatting

* feat(subagents): polish prompt guidance and webview status row
2026-02-11 02:17:45 -08:00
Robin Newhouse b514f18e4f fix(task): prevent duplicate streamed text rows after completion (#9235)
* fix(task): prevent duplicate partial text rows after completion

Avoid adding a new partial text message when the latest text row is already completed with the same content. This stops a presenter race from rendering duplicate streamed text lines for MiniMax-style timing.

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

* test(task): cover duplicate partial text dedupe behavior

Add a Task.say unit test that reproduces the duplicate-partial-after-complete scenario and verifies we skip creating a second text row with identical content.

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

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-10 23:44:01 -08:00
Saoud Rizwan ce85d7d414 fix(cli): preserve OAuth callback paths in auth redirects (#9237) 2026-02-10 19:28:28 -08:00
Saoud Rizwan 739d75afe3 fix(claude-code): add opus 4.6 1m model option (#9231)
* fix(claude-code): add opus 4.6 1m model option

* fix(claude-code): support opus[1m] alias and align opus alias

* fix(claude-code): add sonnet[1m] model support
2026-02-11 04:19:44 +01:00
Max fc1be2baac add more shortcuts to help output (#9204)
* add more shortcuts to help output

* Apply suggestions from code review

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

---------

Co-authored-by: Max Paulus 🥪 <max@cline.bot>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-10 16:14:11 -08:00
Saoud Rizwan 5dcaa8c8cc fix(vertex): add opus 4.6 1m model support on Vertex (#9230)
* fix(vertex): add opus 4.6 1m and global endpoint support

* fix(vertex): enable thinking for opus 4.6 1m in webview
2026-02-10 15:30:12 -08:00
CandiedUniverse 79cf77db0a Finish adding Amazon Bedrock to isNexGenModelProvider() list [CLINE-1291] (#9216)
* Add Bedrock to the list in isNextGenModelProvider()

* feat(bedrock): Remove testing script used to develop isNextGenModelProvider() change against

* refactor: extract shared isParallelToolCallingEnabled into model-utils

Consolidate duplicated parallel tool calling logic from ToolExecutor.ts
and task/index.ts into a single exported function in model-utils.ts.

Both callers now delegate to the shared function, eliminating the need
to maintain identical checks in two places.
2026-02-10 14:39:48 -08:00
Robin Newhouse 4b61799df5 docs: improve PR creation skill to use --body-file flag (#8789)
Replaces inline --body with --body-file approach in the PR creation skill documentation. This avoids shell escaping issues, newline problems, and command-line flakiness when creating PRs with complex markdown content.

Related to #8785
2026-02-10 15:52:05 -06:00
cryptoque 806708e802 feat: enable sync-ed deletion for remote mcp servers from remote config to extension (#9210)
* feat: enable sync-ed deletion for remote mcp servers from remote config to extension

* chore: add tests for syncing remote mcp server adding and removal

* address comments
2026-02-10 10:24:19 -08:00
Max 642ea849e3 fix publish-cli-trusted workflow (#9220)
- parent workflow needs to request permissions for children workflows

Co-authored-by: Max Paulus 🥪 <max@cline.bot>
2026-02-09 21:53:40 -08:00
Max dff8193de5 make trusted npm publish workflow (#9219)
Co-authored-by: Max Paulus 🥪 <max@cline.bot>
2026-02-09 21:40:04 -08:00
Max a05c1c5e53 store input text on remount (#9124)
- my input was getting cleared when i resized the screen. this fixes
that

Co-authored-by: Max Paulus 🥪 <max@cline.bot>
2026-02-09 21:06:59 -08:00
Max 9a11976d27 improve cline config command (#9212)
Co-authored-by: Max Paulus 🥪 <max@cline.bot>
2026-02-09 18:15:05 -08:00
Tomás Barreiro b54647ab17 [PF-389] Render remote config options and add test buttons (#9051)
* WIP - Render a Remote Config secttion and add an option to refresh

* Add the different remote config sections and test them

* fixes

* refactor

* Add proper wrapping

* Stack more values

* Properly report errors when prompt uploading fails

* Add a better error message for the otel test button

* clean

* Fix option rendering

* Render less options if they aren't configured
2026-02-10 02:46:40 +01:00
ClineXDiego 6e253dfa9a Fix/vscode web oauth callback (#9173)
* fix: use vscode.env.asExternalUri for web OAuth callbacks

In VS Code Web (Codespaces, code serve-web), OAuth callbacks using
http://127.0.0.1:PORT break because the extension host runs remotely.

Changes:
- getCallbackUrl now accepts a path parameter
- Desktop: uses vscode://extension-id/path directly
- Web (UIKind.Web): uses vscode.env.asExternalUri() for web-reachable URL
- Updated all callers (/auth, /openrouter, /hicap, /requesty, MCP) to
  pass path and use URL+searchParams for proper encoding
- Added regression test asserting web callback URL is not 127.0.0.1
- AuthHandler (localhost HTTP) now only used by CLI/standalone mode

* fix: use URL.searchParams for proper callback URL encoding

Callers were using template literal interpolation to embed callback URLs
into query strings, which breaks when the URL contains special characters
(e.g. from asExternalUri with query params). Use URL+searchParams.set()
which automatically encodes values.

* chore: revert unrelated whitespace change in account.proto

* revert: remove non-essential URL encoding changes in auth callers

Keep only the core fix (getCallbackUrl path parameter + asExternalUri for web).
Revert the URL+searchParams encoding improvement to minimize diff.

* fix: URL-encode callback_url in auth callers, add encoding test

In VS Code Web, callback URLs from asExternalUri can contain their own
query params (?tkn=...&extra=...). String-interpolating them into
callback_url= causes everything after the first & to be parsed as
top-level params, truncating the callback URL.

Use URL + searchParams.set() in openrouter, hicap, and requesty callers.
Replace tautology test with deterministic round-trip encoding assertions.
2026-02-09 16:43:24 -08:00
Ara 7236d02ebb feat(tools): add auto-approval support for attempt_completion commands (#8926)
* feat(tools): add auto-approval support for attempt_completion commands

- Add auto-approval logic for bash commands in AttemptCompletionHandler
- Show commands as 'say' instead of 'ask' when auto-approved
- Display notification prompting user approval when manual approval needed
- Add 30-second timeout notification for long-running auto-approved commands
- Fix Logger import path from @/shared to @shared

* Send to cline provider
2026-02-09 15:57:29 -08:00
Saoud Rizwan 84fef6fe1f chore(ci): remove ai review workflows and publish caching (#9211) 2026-02-09 15:42:44 -08:00
CandiedUniverse 7bdbf0a9a7 feat(bedrock): Support parallel tool calling in Amazon Bedrock [CLINE-1291] (#9150)
* feat(bedrock): Create agent implementation plan for supporting parallel tool calling.

* Add Bedrock tool calling support

* Improve Bedrock tool calling test guidance

* Add Bedrock CLI parallel tool calling test script

* fix: add ALLOW_AWS_DEFAULT_CHAIN support to live integration test script

* chore: add changeset for Bedrock parallel tool calling

* feat(bedrock): enable native parallel tool calling for Bedrock provider

- Add 'bedrock' to isNextGenModelProvider() so native tool calling is enabled
- Add 'bedrock' to getNativeConverter() to use Anthropic-format tool specs (input_schema)
- Fix empty tool description validation error in mapClineToolsToBedrockToolConfig
  (Bedrock requires description length >= 1)
- Update CLI test to use Sonnet 4.5 (Haiku too small for native tool calling)
- Add <invoke> XML detection to CLI test to catch XML fallback

Verified: conversation history shows 3 native tool_use blocks in a single
assistant response with 3 matching tool_result blocks — true parallel
tool calling via Bedrock Converse API.

* docs: mark all phases complete in bedrock parallel tool calling implementation plan

* chore: switch test scripts default model to Haiku 4.5 (cheaper for testing)

* feat: enhance CLI verification suite with 3 test cases (single, parallel, round-trip)

* Remove bedrock parallel tool calling implementation plan doc.

* refactor: simplify to single CLI verification script for bedrock parallel tool calling

Remove the handler-level test script (test-bedrock-tool-calling.ts) and consolidate
into a single focused CLI test that proves parallel tool calling works end-to-end:
- Spawns Cline CLI with Bedrock config
- Asks it to read 3 files
- Verifies ≥2 parallel native tool calls (not XML fallback)
- Task completion proves tool result round-trip works

* refactor(bedrock): improve type safety and code quality for parallel tool calling

- Add typed interfaces (ToolUseStart, ToolUseDelta) for Bedrock stream
  events instead of relying on `as any` casts
- Extend ContentBlockStart and ContentBlockDelta interfaces with toolUse
  fields so stream parsing uses typed property access
- Remove dead `inputBuffer` field from activeToolCalls Map (was tracked
  but never read — tool input deltas are yielded immediately)
- Add JSDoc to mapClineToolsToBedrockToolConfig explaining its purpose
  and return semantics
- Document why createDeepseekMessage intentionally ignores the tools
  parameter (DeepSeek R1 uses InvokeModel, not Converse API)

* refactor(scripts): improve test script readability and resource cleanup

- Add try/finally with cleanupDirs() to remove temp workspace and config
  dirs after each run (previously accumulated in $TMPDIR)
- Extract named constants for CLI_TIMEOUT_SECONDS and HEARTBEAT_INTERVAL_MS
- Add CliResult interface for the runCli return type
- Rename cryptic variables: hb → heartbeatInterval, c → chunk, p/d → filePath/data
- Add JSDoc to parseReadFilePaths and hasXmlFallback
- Add explanatory comments to empty catch blocks
- Log stderr on non-zero exit code for easier debugging
- Extract createTestWorkspace() to separate workspace setup from main flow
- Add section separator comments for visual structure

* test(bedrock): add missing edge-case tests and remove dead describe block

- Add tests for mapClineToolsToBedrockToolConfig edge cases:
  undefined/empty input returns undefined, tools without input_schema
  are silently dropped
- Add test for formatMessagesForConverseAPI with array tool_result
  content (multi-block text responses)
- Add test for tool_result is_error → status:'error' mapping
- Remove empty 'reasoning content handling (deprecated)' describe block

35 tests passing (was 31).

* test(bedrock): add integration-level tests covering E2E script gaps

Add 'native tool calling integration' test suite that validates the
concerns previously only covered by the live E2E CLI script:

- Bedrock + Claude 4 is recognized as native tool calling eligible
  (catches silent regression if Bedrock is removed from
  isNextGenModelProvider or Claude 4 from isNextGenModelFamily)
- Bedrock + Claude 3.x correctly does NOT qualify (pre-4.0 guard)
- Native tool calling disabled when user setting is off
- createAnthropicMessage passes toolConfig to ConverseStreamCommand
  (catches the tool spec not reaching the API)
- Full multi-turn tool call round-trip formatting (tool_use in
  assistant → tool_result in user → reformatted for next API call)

40 tests passing (was 35).

* Remove functional verification script before code review
2026-02-09 15:23:56 -08:00
Max bab336f172 use cline provider for cline pr review workflow. use npx instead of npm install (#9202)
Co-authored-by: Max Paulus 🥪 <max@cline.bot>
2026-02-09 10:21:32 -08:00
Max 967342999f if yolo mode is on, don't ask permission to use mcp tools (#9100)
Co-authored-by: Max Paulus 🥪 <max@cline.bot>
2026-02-09 10:12:30 -08:00
Jose R. Perez d19a8779e7 feat: consolidate ViewHeader and styling (#8989)
* feat: consolidate ViewHeader and styling

* feat: changeset

* fix: added back environment variables for color differentiation

* fix: co-pilot fixes

* feat: github copilot fix
2026-02-09 10:02:16 -08:00
Saoud Rizwan 5c04fa3aa3 fix(cli): flush telemetry on shutdown and include activation metadata (#9195) 2026-02-08 22:00:45 -08:00
Saoud Rizwan 7c31c1d02a fix: restore reasoning behavior parity after #9168 (#9188)
* fix: restore reasoning parity after #9168

* fix: restore webview reasoning support compatibility checks

fix: simplify reasoning support model matching
2026-02-08 19:43:23 -08:00
Saoud Rizwan 740f99400b feat(cli): add max-consecutive-mistakes task flag (#9194) 2026-02-08 18:52:45 -08:00
Igor Tceglevskii 195294f389 feat: bundled endpoints.json (#9113) 2026-02-08 18:31:01 -08:00
Saoud Rizwan 2cc070eee2 fix(api): preserve vercel model id when metadata is missing (#9192) 2026-02-08 18:00:13 -08:00
Saoud Rizwan 627838243e fix(e2e): increase test timeout for Windows CI runners (#9185)
The diff editor e2e test flakes consistently on Windows CI because the
40s test timeout is too tight. The test does signin, message send,
history verification, then a second message send before the diff
assertion -- on slow Windows runners this setup alone can eat most of
the budget. Bumping to 60s gives enough headroom.
2026-02-08 11:27:48 -08:00
Saoud Rizwan f8a1f75664 feat: add output precision and threshold rules to double-check prompt (#9184)
Add Terminal-Bench-proven rules as items 5 and 6 in the double-check
re-verification checklist, so they're enforced at completion
verification time rather than in the system prompt.
2026-02-08 11:09:27 -08:00
Saoud Rizwan 54aeba1fee feat: add double-check completion experimental feature (#9180)
* feat: add double-check completion experimental feature

When enabled, the first attempt_completion call in a task is rejected
with a tool error that instructs the model to re-verify its work
against the original task requirements. The rejection includes the
initial task text for context. The second call proceeds normally.

This is opt-in (default off) and available via:
- Settings > Features > Experimental > Double-Check Completion
- CLI flag: --double-check-completion
- CLI TUI settings panel toggle

Adds completionAttemptCount to TaskState, plumbs the setting through
TaskConfig/ToolExecutor following existing patterns, and includes
9 unit tests.

* chore: add cli:run script for quick CLI testing

* fix: increase task preview to 8000 chars, revert unintended regex change

* fix: preserve existing proto field numbers

The auto-generator renumbered open_ai_headers (175->177) and
openai_codex_oauth_credentials (46->48), and dropped the reserved 146
comment. Restore original field numbers to avoid breaking wire-format
compatibility.

* fix: remove partial completion_result message on double-check rejection

During streaming, handlePartialBlock shows the completion_result in
the chat view. When we reject the first attempt, we need to clean up
that partial message so the user doesn't see a stale completion that
was actually rejected.

* refactor: switch from counter to boolean toggle for double-check

Use a boolean pending flag instead of a counter so that every
attempt_completion gets double-checked, not just the first one in
a task. The flag toggles: reject (set pending), accept (clear pending),
so if the model does more work and tries to complete again later, it
gets double-checked again.
2026-02-08 10:56:10 -08:00
Robin Newhouse a03642ba4a fix(prompt): add output precision and threshold iteration rules (#9178)
* fix(prompt): add output precision and threshold iteration rules

Two concise rules proven effective via Terminal-Bench testing:

1. Output precision: produce exactly what's specified, no extra columns/fields/debug output
2. Threshold iteration: verify results meet numerical criteria before completing

Tested on 6 targeted Terminal-Bench tasks (job 2026-02-07__16-15-00):
- log-summary-date-ranges: FAIL→PASS (output precision rule eliminated extra columns)
- dna-insert: FAIL→PASS (iterate rule helped agent meet Tm threshold)

A third rule (no-cleanup) was tested and deliberately excluded: it failed to
prevent self-sabotage on configure-git-webserver despite STRICTLY FORBIDDEN
language, and caused a side-effect on polyglot-c-py by preventing legitimate
build artifact cleanup. The cleanup behavior is too deeply trained to override
via prompt rules alone.

* test: update prompt snapshots for new rules
2026-02-08 09:37:04 -08:00
Saoud Rizwan b65435fc55 fix(cli): route PostHog networking through shared fetch (#9149)
* fix(cli): route PostHog networking through shared fetch

* remove unnecessary `as RequestInit` casts from PostHog fetch wrappers

PostHogFetchOptions is a structural subset of RequestInit, so the cast
is unnecessary. Also removes a stale comment about shared client support
in PostHogErrorProvider.
2026-02-07 18:26:23 -08:00
Saoud Rizwan 88694d39fa feat(cli): allow --thinking flag to accept custom token budget (#9177)
The --thinking flag now accepts an optional number argument to set a
custom thinking budget instead of always using the 1024 default.

  cline "prompt" --thinking         # 1024 tokens (default)
  cline "prompt" --thinking 8000    # 8000 tokens

Invalid values get a warning and fall back to 1024.
2026-02-07 17:06:33 -08:00
Saoud Rizwan 6c53daa88e feat: move reasoning effort to model config and settings UX (#9168)
* feat: move reasoning effort to model config and update model selection UX

* refactor: dedupe reasoning effort handling and drop lockfile churn

* refactor: default reasoning effort to low

* refactor(cli): sync mode-scoped thinking and reasoning writes

* fix: centralize reasoning effort normalization and avoid implicit openai effort

* fix: restore proto field number for codex credentials and reserve removed fields

- Keep openai_codex_oauth_credentials at field 46 (was incorrectly
  changed to 47)
- Add reserved 146 in Settings for removed openai_reasoning_effort
- Add reserved 15 in UpdateSettingsRequest for removed openai_reasoning_effort
- Remove stale openai_reasoning_effort field from UpdateSettingsRequest

* fix: map medium reasoning effort to LOW for Gemini models

Gemini API only accepts LOW and HIGH thinking levels. MEDIUM exists in
the SDK enum but is rejected at the API level. Map medium to LOW and
update the default fallback accordingly.
2026-02-07 16:58:10 -08:00
Ara 1f3c00c613 feat(task): add support for writing prompt metadata artifacts (#9158)
Introduces a mechanism to save system prompts and task metadata to disk for debugging and analysis purposes.

- Added `writePromptMetadataArtifacts` to the `Task` class.
- Feature is enabled via the `CLINE_WRITE_PROMPT_ARTIFACTS` environment variable.
- Artifacts are saved to `.cline-prompt-artifacts` or a custom path defined by `CLINE_PROMPT_ARTIFACT_DIR`.
- Writes both a JSON manifest (containing task ID, model info, and timestamp) and the raw system prompt for every API request.
2026-02-07 15:21:41 -08:00
Saoud Rizwan 4d455ea015 fix(terminal): tune execute_command timeout strategy for long-running tasks (#9159)
* fix(terminal): tune managed timeout policy for long-running commands

* Reduce default command timeout from 120 to 30 seconds

* Update ExecuteCommandToolHandler.timeout.test.ts

---------

Co-authored-by: Ara <arafat.da.khan@gmail.com>
2026-02-07 14:47:26 -08:00
Saoud Rizwan 3daf24662e fix(prompt): add guidance to use -- for leading-dash positional args (#9161) 2026-02-07 14:18:55 -08:00
Saoud Rizwan 942fcf5762 fix(terminal): surface command exit codes in results (#9156) 2026-02-07 13:31:22 -08:00
ClineXDiego 70a99047ed fix: use vscode.env.asExternalUri for auth callback URLs in VS Code Web (#9144)
* fix: use vscode.env.asExternalUri for auth callback URLs in VS Code Web

The OAuth callback redirect was broken in VS Code Web (code serve-web)
environments because the callback URL used a raw vscode:// URI scheme,
which the OS would route to the local desktop VS Code app instead of
the web instance.

This change wraps both getCallbackUrl() and getIdeRedirectUri() with
vscode.env.asExternalUri() which properly transforms URIs based on the
environment:
- Desktop VS Code: unchanged (vscode://...)
- VS Code Remote SSH: adds remote authority for proper routing
- VS Code Web: transforms to HTTPS URL that routes through the web server

Fixes #5109 (remaining callback redirect issue)
Related: #2152

* fix: use HTTP-based auth callback for VS Code Web mode

In VS Code Web (code serve-web), vscode:// URIs redirect to the desktop
app instead of staying in the browser. This change uses AuthHandler
(local HTTP server) for the auth callback in web mode, matching how
CLI/standalone already handles auth.

- getCallbackUrl: use AuthHandler when UIKind.Web
- getIdeRedirectUri: return empty in web mode to avoid vscode:// redirect

* fix: add fallback for openExternal RPC for JetBrains compatibility

The openExternal host bridge RPC is not implemented in the JetBrains
plugin, causing sign-in to fail silently. This adds a fallback to the
'open' npm package when the host RPC fails with UNIMPLEMENTED.

Fixes #9164, #9137, #9138
2026-02-07 10:25:14 -08:00
Robin Newhouse 844038084c feat: add CLI build workflow for testing from any commit (#9131) 2026-02-07 05:23:22 -08:00
Robin Newhouse 0c6f77ea46 Remove accidentally committed implementation_plan.md (#9160) 2026-02-07 00:11:02 -06:00
Saoud Rizwan 9b70f94174 fix(prompt): require verification before completion (#9154)
* fix(prompt): require verification before completion

* fix(prompt): align gemini verification-first completion guidance
2026-02-06 19:22:40 -08:00
Saoud Rizwan 0a4f939ecb chore(ci): tag bot PR reviews with workflow footer (#9152) 2026-02-06 15:08:38 -08:00
Tomás Barreiro 095ee24288 Limit the CLI provider list to what's remotely configured (#9135)
* Limit the CLI provider list to what's remotely configured

* Refactor

* fix react
2026-02-06 09:14:29 -08:00
Saoud Rizwan 523dd9ef7d fix(ui): add loading indicator and fix api_req_started rendering (#9133)
The chat streaming UI refactor removed the loading indicator that
previously showed when an API request was in progress. This left users
staring at a frozen UI during the latency between sending a message
and receiving the first streamed content.

Changes:
- Add "Thinking..." shimmer in the Virtuoso Footer as the sole loading
  indicator, covering both pre-api_req_started (backend processing) and
  post-api_req_started (waiting for model response) states
- Filter out api_req_started messages that have no visible content
  (no error/cancel). These rows rendered as invisible padding since
  the PR removed the old API request accordion UI. Reasoning messages
  already render as their own standalone ChatRows.
- Thread footerActive flag to MessageRenderer so the last message skips
  pb-2.5 when the Footer is showing, keeping spacing consistent with
  the pt-2.5 on every ChatRow
2026-02-05 16:38:21 -08:00
Robin Newhouse 6d8fb8507b fix(cli): handle stdin redirection in CI environments (#9121)
- Add stdinIsTTY check to shouldUsePlainTextMode() - Ink requires raw mode on stdin
- Only error on empty stdin when no prompt is provided (allows: cline 'prompt' < /dev/null)
- Fixes crash in GitHub Actions and other CI environments
2026-02-05 13:32:52 -08:00
Max edc93f35f1 update changelog for 3.57.1 (#9130)
Co-authored-by: Max Paulus 🥪 <max@cline.bot>
2026-02-05 13:12:54 -08:00
ckrause 401358374f fix MCP OAuth: add missing scope parameter (#9117)
* fix MCP OAuth: add missing scope parameter

* Update src/services/mcp/McpOAuthManager.ts

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

* fix

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-05 13:06:01 -08:00
Max f8bcad16a5 update package-lock.json (#9127)
Co-authored-by: Max Paulus 🥪 <max@cline.bot>
2026-02-05 12:44:46 -08:00
AJ Juaire 26391c94e9 Correct Bedrock Opus 4.6 model id (#9126) 2026-02-05 12:21:37 -08:00
Ara 462438ece5 Update changelog wording (#9125) 2026-02-05 11:53:48 -08:00
github-actions[bot] 92521ed279 Release Notes for v3.57.0 (#8980)
- Cline CLI 2.0 now available. Install with `npm install -g cline`
- Anthopic Opus 4.6
- Minimax-2.1 and Kimi-k2.5 now available for free for a limited time promo
- Codex-5.3 through OpenAI Codex provider

- Fix read file tool to support reading large files
- Fix decimal input crash in OpenAI Compatible price fields (#8129)
- Fix build complete handlers when updating the api config
- Fixed missing provider from list
- Fixed Favorite Icon / Star from getting clipped in the task history view

- Make skills always enabled and remove feature toggle setting

Co-authored-by: Arafatkatze <arafat.da.khan@gmail.com>
2026-02-05 11:35:58 -08:00
Max 08aa81f798 add taskId flag to CLI (#9095)
- allows you to resume a session headlessly or interactively with a
taskId

Co-authored-by: Max Paulus 🥪 <max@cline.bot>
2026-02-05 11:32:54 -08:00
Tomás Barreiro e025995177 Revert LiteLLM model name change and use the rawModel name (#9123)
* Revert LiteLLM model name change and use the rawModel name

* Add both to the list
2026-02-05 11:15:17 -08:00
Saoud Rizwan ee361ef3ae feat: add GPT-5.3 Codex model for ChatGPT subscription users (#9122)
* feat: add GPT-5.3 Codex model for ChatGPT subscription users

OpenAI released GPT-5.3 Codex today. Adding it to the OpenAI Codex
provider (ChatGPT Plus/Pro subscription) model list and setting it
as the new default.

Changes:
- Add gpt-5.3-codex to openAiCodexModels with same specs as 5.2
- Update default model to gpt-5.3-codex
- Update featured models in CLI and webview OpenRouter picker

* revert: remove gpt-5.3-codex from OpenRouter featured models

GPT-5.3 Codex is only available via ChatGPT subscription, not through
the OpenAI API or OpenRouter. Reverting featured model changes.
2026-02-05 11:05:00 -08:00
Max c8ef342c19 cli multi label support. new featured model (#9118)
Co-authored-by: Max Paulus 🥪 <max@cline.bot>
2026-02-05 10:51:54 -08:00
Saoud Rizwan 7c8701799d feat: add Claude Opus 4.6 model support (#9119)
* feat: add Claude Opus 4.6 model support with 1M context window

Adds support for Claude Opus 4.6, Anthropic's latest model with:
- 200K base context window with optional 1M context variant
- Tiered pricing for >200K context (2x input/output pricing)
- Extended thinking/reasoning support
- Prompt caching support

Changes:
- Added model definitions for Anthropic, Bedrock, and Vertex providers
- Added OpenRouter 1M variant support
- Updated thinking models lists across all provider UIs
- Added context window switcher for Opus 4.6
- Updated JP cross-region inference models list

* feat: update featured model to Opus 4.6 in model picker

* chore: add changeset for Claude Opus 4.6

* fix: correct Opus 4.6 model IDs (no date suffix)

---------

Co-authored-by: Robin Newhouse <robin@cline.bot>
2026-02-05 10:38:54 -08:00
Ara fbcf63ad71 fixing large model (#9110) 2026-02-05 09:16:25 -08:00
679 changed files with 42175 additions and 30887 deletions
-5
View File
@@ -1,5 +0,0 @@
---
"cline": patch
---
Replace the LiteLLM model list with a selector
+5
View File
@@ -0,0 +1,5 @@
---
"cline": minor
---
Adds Messages API support to Oracle Code Assist, adding functionality for Claude models
-5
View File
@@ -1,5 +0,0 @@
---
"cline": patch
---
fix(cli): prevent hang when spawned without TTY
+5
View File
@@ -0,0 +1,5 @@
---
"cline": patch
---
Add /q command to quit CLI
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": patch
---
Fix decimal input crash in OpenAI Compatible price fields (#8129)
-5
View File
@@ -1,5 +0,0 @@
---
"cline": patch
---
Supports rendering markdown table in chat view.
-7
View File
@@ -1,7 +0,0 @@
---
"cline": patch
---
fix: use vscode.env.openExternal for auth in remote environments
Fixes OAuth authentication in VS Code Server and remote environments by routing browser URL opening through VS Code's native openExternal API instead of the npm 'open' package.
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": patch
---
fix: build complete handlers when upadting the api config
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": patch
---
Updating script documentation and removing unnecessary continue on error
+5
View File
@@ -0,0 +1,5 @@
---
"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.
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": patch
---
Fixed missing provider from list
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": patch
---
feat(skills): Make skills always enabled and remove feature toggle setting
+4
View File
@@ -0,0 +1,4 @@
"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 @@
---
"claude-dev": patch
---
Fixed Favorite Icon / Star from getting clipped in the task history view
+5
View File
@@ -0,0 +1,5 @@
---
"cline": patch
---
fix acp auth check so acp mode can be used with more providers
-5
View File
@@ -1,5 +0,0 @@
---
"cline": minor
---
Add Generate API Key on Hicap Provider selection
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
Update SambaNova Provider models list and add temperature for models
+20 -5
View File
@@ -147,14 +147,29 @@ When filling out the template:
### Create PR with gh CLI
**Use a temporary file for the PR body** to avoid shell escaping issues, newline problems, and other command-line flakiness:
1. Write the PR body to a temporary file:
```
/tmp/pr-body.md
```
2. Create the PR using the file:
```bash
gh pr create --title "PR_TITLE" --body-file /tmp/pr-body.md --base main
```
3. Clean up the temporary file:
```bash
rm /tmp/pr-body.md
```
For draft PRs:
```bash
gh pr create --title "PR_TITLE" --body "PR_BODY" --base main
gh pr create --title "PR_TITLE" --body-file /tmp/pr-body.md --base main --draft
```
Alternatively, create as draft if the user wants review before marking ready:
```bash
gh pr create --title "PR_TITLE" --body "PR_BODY" --base main --draft
```
**Why use a file?** Passing complex markdown with newlines, special characters, and checkboxes directly via `--body` is error-prone. The `--body-file` flag handles all content reliably.
## Post-Creation
+11
View File
@@ -147,6 +147,17 @@ Required steps:
Common mistake: Adding only the return value without the `context.globalState.get()` call. This compiles but the value is always `undefined` on load.
Settings plumbing gotcha: if a key is user-toggleable from settings, wire both controller update paths:
- `src/core/controller/state/updateSettings.ts` for webview `updateSetting(...)`
- `src/core/controller/state/updateSettingsCli.ts` for CLI/ACP settings updates
Missing one path causes a toggle to appear to change in one surface while the backend state stays unchanged.
Webview toggle gotcha: settings changes must also round-trip back in state payloads.
- Add the field to `UpdateSettingsRequest` in `proto/cline/state.proto` (for webview update requests), then run `npm run protos`
- Include the key in `Controller.getStateToPostToWebview()` (`src/core/controller/index.ts`)
- Ensure `ExtensionState` and webview defaults include the key (`src/shared/ExtensionMessage.ts`, `webview-ui/src/context/ExtensionStateContext.tsx`)
If this round-trip wiring is missing, the backend value can update but the toggle in webview appears stuck or reverts.
## StateManager Cache vs Direct globalState Access
StateManager uses an in-memory cache populated during `StateManager.initialize(context)` in `common.ts`. For most state, use `controller.stateManager.setGlobalState()`/`getGlobalStateKey()`.
+64
View File
@@ -0,0 +1,64 @@
# Storage Architecture
Global settings, secrets and workspace state are stored in **file-backed JSON stores** under `~/.cline/data/`. This is the shared storage layer used by VSCode, CLI, and JetBrains.
## Key Abstractions
### `StorageContext` (src/shared/storage/storage-context.ts)
The entry point. Created via `createStorageContext()` and passed to `StateManager.initialize()`. Contains three `ClineFileStorage` instances:
- `globalState``~/.cline/data/globalState.json`
- `secrets``~/.cline/data/secrets.json` (mode 0o600)
- `workspaceState``~/.cline/data/workspaces/<hash>/workspaceState.json`
### `ClineFileStorage` (src/shared/storage/ClineFileStorage.ts)
Synchronous JSON key-value store backed by a single file. Supports `get()`, `set()`, `setBatch()`, `delete()`. Writes are atomic (write-then-rename).
### `StateManager` (src/core/storage/StateManager.ts)
In-memory cache on top of `StorageContext`. All runtime reads hit the cache; writes update cache immediately and debounce-flush to disk.
## ⚠️ Do NOT Use VSCode's ExtensionContext for Storage
**Do not** read from or write to `context.globalState`, `context.workspaceState`, or `context.secrets` for persistent data. These are VSCode-specific and not available on CLI or JetBrains.
Instead, use:
```typescript
// Reading state
StateManager.get().getGlobalStateKey("myKey")
StateManager.get().getSecretKey("mySecretKey")
StateManager.get().getWorkspaceStateKey("myWsKey")
// Writing state
StateManager.get().setGlobalState("myKey", value)
StateManager.get().setSecret("mySecretKey", value)
StateManager.get().setWorkspaceState("myWsKey", value)
```
Remember that your data may be read by a different client than the one that wrote it. For example, a value written by Cline in JetBrains may be read by Cline CLI.
## VSCode Migration (src/hosts/vscode/vscode-to-file-migration.ts)
On VSCode startup, a migration copies data from VSCode's `ExtensionContext` storage into the file-backed stores. This runs in `src/common.ts` before `StateManager.initialize()`.
- **Sentinel**: `__vscodeMigrationVersion` key in global state and workspace state — prevents re-migration.
- **Merge strategy**: File store wins. Existing values are never overwritten.
- **Safe downgrade**: VSCode storage is NOT cleared, so older extension versions still work.
## Adding New Storage Keys
1. Add to `src/shared/storage/state-keys.ts` (see existing patterns)
2. Read/write via `StateManager` (NOT via `context.globalState`)
3. If adding a secret, add to `SecretKeys` array in `state-keys.ts`
## File Layout
```
~/.cline/
data/
globalState.json # Global settings & state
secrets.json # API keys (mode 0o600)
tasks/
taskHistory.json # Task history (separate file)
workspaces/
<hash>/
workspaceState.json # Per-workspace toggles
```
+49
View File
@@ -0,0 +1,49 @@
# THIS IS AUTOGENERATED. DO NOT EDIT MANUALLY
version = 1
name = "cline"
[setup]
script = '''
if [ ! -d "node_modules" ]; then
MAIN_WORKTREE="$(git worktree list | head -n1 | awk '{print $1}')"
ln -s "$MAIN_WORKTREE/node_modules" node_modules
ln -s "$MAIN_WORKTREE/webview-ui/node_modules" webview-ui/node_modules
fi
'''
[[actions]]
name = "VS Code"
icon = "run"
command = "chmod +x ./scripts/run-extension-host.sh && ./scripts/run-extension-host.sh production"
[[actions]]
name = "CLI"
icon = "run"
command = '''
npm run cli:build
npm run cli:run
'''
[[actions]]
name = "npm install"
icon = "tool"
command = '''
rm node_modules
rm webview-ui/node_modules
npm run install:all
'''
[[actions]]
name = "pull main"
icon = "tool"
command = '''
git fetch origin main
if ! git merge-base --is-ancestor main origin/main; then
echo "Local main has commits not on origin/main. Aborting..."
exit 1
fi
git update-ref refs/heads/main refs/remotes/origin/main
echo "main updated to $(git rev-parse --short main)"
'''
-173
View File
@@ -1,173 +0,0 @@
name: Claude Issue Triage
on:
issues:
types: [opened]
# Manual trigger for backfilling existing issues. Run from terminal:
# gh workflow run claude-issue-triage.yml -f issue_number=1234
# Or batch process:
# gh issue list --state open --limit 10 --json number --jq '.[].number' | while read num; do
# gh workflow run claude-issue-triage.yml -f issue_number=$num
# sleep 60
# done
workflow_dispatch:
inputs:
issue_number:
description: 'Issue number to triage'
required: true
type: string
jobs:
claude-issue-triage:
runs-on: ubuntu-latest
timeout-minutes: 120
# SECURITY: These permissions are intentionally restrictive.
# - contents: read -> Claude can read the codebase but CANNOT write/push any code
# - issues: write -> Claude can comment and add labels (the only write access needed)
# - pull-requests: read -> Claude can view PR context but CANNOT create PRs
# This ensures that even if a malicious user attempts prompt injection via issue content,
# Claude cannot modify repository code, create branches, or open PRs.
permissions:
contents: read
issues: write
pull-requests: read
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Run Issue Response & Triage
id: triage
uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
github_token: ${{ secrets.GITHUB_TOKEN }}
allowed_non_write_users: "*"
# Allow all tools - security is enforced by GitHub permissions above (contents: read, issues: write)
claude_args: --model claude-opus-4-5-20251101 --allowedTools "Bash,Read,Write,Edit,Glob,Grep,WebFetch,WebSearch"
prompt: |
You're a GitHub issue first responder for the open source Cline repository.
**Issue:** #${{ github.event.issue.number || inputs.issue_number }}
**Title:** ${{ github.event.issue.title || 'See issue details below' }}
**Author:** @${{ github.event.issue.user.login || 'See issue details below' }}
## Your job
Investigate this issue thoroughly, then post a single helpful comment that helps the user and gives maintainers the context they need.
## Investigation
Start by reading the full issue:
gh issue view ${{ github.event.issue.number || inputs.issue_number }}
### Search for duplicates and related issues
Search thoroughly for existing issues that match this one:
gh issue list --search "<keywords from the issue>" --state all --limit 30
gh issue list --search "<error messages>" --state all --limit 20
gh issue list --search "<affected feature/component>" --state all --limit 20
For each relevant issue you find, read it including its comments:
gh issue view <number> --comments
You're looking for:
- **Duplicates**: Issues describing the same problem. Link to them and explain why you think they're duplicates. If closed, check how they were resolved - the solution might apply here.
- **Related issues**: Similar problems or context that could help. Pull useful information from their comments (workarounds others found, debugging steps that helped, maintainer explanations). Link to them and explain the connection.
If there are closed issues with solutions, surface those solutions prominently - this might immediately solve the user's problem.
### Analyze recent changes (ALWAYS DO THIS)
Many issues are regressions from recent releases. **Always** check what changed recently:
gh release list --limit 10
gh pr list --state merged --limit 50 --json number,title,mergedAt,author,body
Look for PRs merged in the last few weeks that might correlate with the issue. If you find a likely connection:
gh pr view <number>
gh pr diff <number>
git log --since="1 month ago" --oneline -- <relevant paths>
git show <commit>
**Always include your findings in your comment:**
- If you find a regression, call it out explicitly: which PR/commit likely caused it, who authored it, what changed, and suggest a fix direction if you can see one.
- If you don't find anything related, still mention it: "I analyzed recent PRs and releases but didn't find any changes that seem related to this issue."
### Search the codebase
Find the relevant code:
- Use grep/find to locate code related to the issue
- Key areas: `src/api/` (providers/models), `src/core/prompts/` (tools/prompts), platform-specific code for VS Code vs JetBrains
### Find documentation
Cline docs are at **https://docs.cline.bot/** and built with Mintlify from the `docs/` directory.
The URL structure maps directly to the file structure:
- `docs/getting-started/selecting-your-model.mdx` → https://docs.cline.bot/getting-started/selecting-your-model
- `docs/troubleshooting.mdx` → https://docs.cline.bot/troubleshooting
- Headings become anchors: `## Which Model` → `#which-model`
Search the `docs/` directory to find relevant documentation, then construct URLs to link users to:
```bash
ls docs/
grep -r "keyword" docs/ --include="*.mdx" -l
```
### Identify subject matter experts
For issues that clearly need engineering attention:
git log --since="6 months ago" --format="%an" -- <relevant paths> | sort | uniq -c | sort -rn | head -5
Cross-reference with GitHub usernames. Include in your response (@mention, do NOT assign):
| SME | Reason |
|-----|--------|
| @username1 | Authored PR #X which modified this area |
| @username2 | Primary contributor to affected file |
## Weak model detection
Many issues are caused by users running small or non-frontier models that don't tool-call reliably. Signs include:
- Model failing to use tools correctly
- Nonsensical or malformed responses
- User is running a small/local model or older model version
If this looks like a weak model issue, kindly suggest they try reproducing with Claude Sonnet and report back if it persists. Link to https://docs.cline.bot/getting-started/selecting-your-model if helpful. Still label and triage normally.
## Your comment
Write a single comment as a helpful community member. Be conversational, not robotic. Include what's relevant:
- **Helpful response** - Answer their question, suggest a fix, provide a workaround. If you found solutions in related closed issues, surface those prominently.
- **Duplicates and related issues** - Link to any you found and explain why they're duplicates/related. Summarize useful context from their comments.
- **Regression analysis** - If this looks like a regression, explain what change likely caused it, link to the PR/commit, and tag the author.
- **Clarifying questions** - If you need more info, ask specific questions. Don't ask for things already provided.
- **SME table** - Include the table above if this needs engineering attention. Don't tag people for questions with obvious answers or weak-model issues.
- **Context for maintainers** - Relevant code paths, what you found. Keep it concise.
- **Docs links** - If there's relevant documentation, link to it naturally in your response as a recommendation (e.g., "For more details, check out [the Ollama setup guide](url)"). Do NOT add a "Sources" section at the end - integrate doc links into your response where they're helpful.
- **Possible Duplicates section** - ALWAYS include a "Possible Duplicates" section at the end of your comment listing issues that might be duplicates so maintainers can quickly close if appropriate. If none found, say "No obvious duplicates found."
## Labels
First, retrieve all available labels and read their descriptions to understand what each is for:
gh label list --json name,description --limit 100
Then apply the appropriate labels based on your analysis. Only use labels from the list above—do not create new labels.
gh issue edit ${{ github.event.issue.number || inputs.issue_number }} --add-label "label1,label2"
If your regression analysis found a likely culprit (a recent PR/commit that probably caused this issue), add the "Regression" label:
gh issue edit ${{ github.event.issue.number || inputs.issue_number }} --add-label "Regression"
IMPORTANT: After posting your comment, add the "Bot Responded" label to indicate this issue has received an automated response:
gh issue edit ${{ github.event.issue.number || inputs.issue_number }} --add-label "Bot Responded"
## Remember
- **This is a one-time automated response** - you will NOT see their reply or respond again. Never say things like "I can help you", "let me know", "once I have that info", or "I can give you more targeted help" - you won't be there to follow up. If you ask clarifying questions, frame them for the maintainers who will follow up, e.g., "If you can share X, that would help the maintainers diagnose this."
- Don't be formulaic. Respond to what the issue actually needs.
- Surface solutions from past issues - often the fastest path to helping.
- Connecting regressions to specific changes is extremely valuable.
- Link issues with #number so they're clickable.
-284
View File
@@ -1,284 +0,0 @@
name: Claude PR Review
on:
pull_request:
types: [opened, ready_for_review]
# Manual trigger for backfilling existing PRs. Run from terminal:
# gh workflow run claude-pr-review.yml -f pr_number=1234
# Or batch process open PRs:
# gh pr list --state open --limit 10 --json number --jq '.[].number' | while read num; do
# gh workflow run claude-pr-review.yml -f pr_number=$num
# sleep 60
# done
workflow_dispatch:
inputs:
pr_number:
description: 'PR number to review'
required: true
type: string
jobs:
claude-pr-review:
# Runs on PR opened/ready_for_review (skips drafts) or manual trigger for backfilling
if: |
(github.event_name == 'pull_request' && github.event.pull_request.draft == false) ||
github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
timeout-minutes: 120
# SECURITY: These permissions are intentionally restrictive.
# - contents: read -> Claude can read the codebase but CANNOT write/push any code
# - pull-requests: write -> Claude can post reviews and inline suggestions
# - issues: read -> Claude can search for related issues
# NOTE: Even with pull-requests: write, Claude CANNOT merge PRs because branch protection
# requires 1 approval from a Code Owner. The GITHUB_TOKEN cannot bypass this.
permissions:
contents: read
pull-requests: write
issues: read
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Print HEAD commit
run: |
echo "HEAD is at: $(git rev-parse HEAD)"
echo "Short: $(git rev-parse --short HEAD)"
git log -1 --format="Commit: %H%nAuthor: %an <%ae>%nDate: %ad%nMessage: %s"
- name: Get PR number
id: pr
run: |
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
echo "number=${{ inputs.pr_number }}" >> $GITHUB_OUTPUT
else
echo "number=${{ github.event.pull_request.number }}" >> $GITHUB_OUTPUT
fi
- name: Run PR Review
id: review
uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
github_token: ${{ secrets.GITHUB_TOKEN }}
allowed_non_write_users: "*"
claude_args: --model claude-opus-4-5-20251101 --allowedTools "Bash,Read,Write,Edit,Glob,Grep,WebFetch,WebSearch"
prompt: |
You're a GitHub PR reviewer for the open source Cline repository. Your goal is to give the PR author helpful feedback and give maintainers the context they need to review efficiently.
PR: #${{ steps.pr.outputs.number }}
## Gather context
```bash
# Get full PR details
gh pr view ${{ steps.pr.outputs.number }} --json number,title,body,author,createdAt,updatedAt,isDraft,labels,commits,files,additions,deletions,changedFiles,baseRefName,headRefName,mergeable,reviewDecision
# Get the diff
gh pr diff ${{ steps.pr.outputs.number }}
# Check CI status
gh pr checks ${{ steps.pr.outputs.number }}
# Get existing review comments (to understand context and your previous feedback)
gh api repos/${{ github.repository }}/pulls/${{ steps.pr.outputs.number }}/comments --jq '.[] | {user: .user.login, body: .body, path: .path, created_at: .created_at}'
# Get conversation comments
gh pr view ${{ steps.pr.outputs.number }} --comments
```
If this is a re-review (workflow_dispatch event):
Read your previous comments carefully. Understand what you asked for before.
Check if new commits or comments address your previous feedback.
## Check contributing guidelines
Flag (but don't block) if:
- Missing changeset - For user-facing changes, check if there's a `.changeset/` file:
```bash
gh pr diff ${{ steps.pr.outputs.number }} --name-only | grep '.changeset/' || echo "No changeset found"
```
If missing, ask them to run `npm run changeset`
- Missing tests - New features should have tests
## Find related issues and PRs
Search thoroughly for context that might help with the review:
```bash
# Find related issues for context
gh issue list --search "<keywords from the PR>" --state all --limit 30
gh issue list --search "<error messages or feature names>" --state all --limit 20
# Find similar PRs for reference
gh pr list --search "<keywords>" --state all --limit 30
```
For each relevant issue or PR you find, read it including comments:
```bash
gh issue view <number> --comments
gh pr view <number> --comments
```
Look for:
- Open issues this PR might fix that weren't linked in the description
- Similar PRs that went through review - what feedback did they get? What patterns did they follow?
- Context from maintainer discussions that could inform your review
## Find subject matter experts
For files changed in this PR, find who knows the code best:
```bash
# Get files changed
gh pr diff ${{ steps.pr.outputs.number }} --name-only
# For each relevant path, find contributors
git log --since="6 months ago" --format="%an" -- <path> | sort | uniq -c | sort -rn | head -5
```
Cross-reference git authors with GitHub usernames. Include an SME table in your response:
| SME | Reason |
|-----|--------|
| @username1 | Authored PR #X which modified this area |
| @username2 | Primary contributor to affected file (15 commits in 6 months) |
| @username3 | Reviewed similar PR #Y with extensive feedback |
## Deep code review
This is the most important part. Don't just look for syntax issues - understand what the PR is trying to achieve and whether the implementation is the right approach.
Step 1: Understand the intent
Read the PR description and understand what the author is trying to accomplish. What problem are they solving? What feature are they adding?
Step 2: Form your own opinion first
Before analyzing their code, think about how YOU would implement this feature or fix. What files would you touch? What patterns would you follow? What edge cases would you handle?
Step 3: Compare approaches
Now look at their implementation. How does it compare to what you would have done?
- Is their approach better in some ways? Note what they did well.
- Is their approach missing something? Be specific about what and why.
- Are there edge cases they haven't considered?
- Does it follow the patterns established in similar parts of the codebase?
Step 4: Look at the bigger picture
- What other files or systems does this change interact with?
- Could this break anything else?
- Is there additional work needed beyond this PR to complete the feature?
- Does this fit well with the overall architecture?
Step 5: Find reference implementations
Look for similar changes in the codebase:
```bash
git log --oneline --all --grep="<relevant keywords>" | head -20
git log --oneline -- <similar files> | head -20
```
If this is adding a new API provider, look at how other providers are implemented.
If this is adding a new feature, look at how similar features were added.
Note where their implementation aligns with or diverges from established patterns.
Step 6: Standard code review checks
- DRY: Is there duplicated code that could be extracted?
- Error handling: Are errors handled appropriately?
- Security: Any injection risks, credential exposure, unsafe dependencies?
- Performance: Any obvious inefficiencies, memory leaks, N+1 patterns?
- Types: Is TypeScript used correctly? Any unsafe type assertions?
- Naming: Are variables and functions named clearly?
- Comments: Is complex logic explained? Are there outdated comments?
## Inline code suggestions
For specific code improvements, use GitHub's suggestion syntax via `gh api`.
This creates suggestions the author can commit with one click.
Single-line suggestion:
```bash
gh api repos/${{ github.repository }}/pulls/${{ steps.pr.outputs.number }}/reviews \
-X POST \
-f commit_id="$(gh pr view ${{ steps.pr.outputs.number }} --json headRefOid -q .headRefOid)" \
-f event="COMMENT" \
-f body="" \
-F comments='[
{
"path": "src/example.ts",
"line": 42,
"body": "Consider simplifying:\n\n```suggestion\nconst result = items.filter(Boolean);\n```"
}
]'
```
Multi-line suggestion (replacing lines 40-45):
```bash
gh api repos/${{ github.repository }}/pulls/${{ steps.pr.outputs.number }}/reviews \
-X POST \
-f commit_id="$(gh pr view ${{ steps.pr.outputs.number }} --json headRefOid -q .headRefOid)" \
-f event="COMMENT" \
-f body="" \
-F comments='[
{
"path": "src/example.ts",
"start_line": 40,
"line": 45,
"body": "This can be simplified:\n\n```suggestion\nconst simplified = doThing();\n```"
}
]'
```
Use inline suggestions for concrete improvements. Use regular comments for questions or broader feedback.
## Post your review
After your investigation, post a single helpful comment that helps the author and gives maintainers context.
Start by noting the commit hash you reviewed:
```bash
git rev-parse --short HEAD
```
Include this at the top of your comment: "Reviewed at commit: <short hash>"
Then thank them for their contribution. Be conversational, not robotic.
Include what's relevant:
- In-depth explanation of what the PR does - Be comprehensive. A maintainer should be able to read this section and fully understand the author's intent, why they made the changes, how they implemented it, and what files/systems are affected. Don't just summarize - explain.
- Related issues/PRs you found that provide useful context (link to them)
- Your review findings (issues to address, suggestions, etc.)
- Clear next steps for the author
Include a "For Maintainers" section with:
- Anything else useful to help the maintainer resolve this PR
- Related issues/PRs with context on why they're relevant
- Open issues this PR might fix that weren't linked in the description
- Your recommendation: merge as-is, needs changes, needs discussion, close, etc.
- SME table - who should review this and why
For the SME table:
| SME | Reason |
|-----|--------|
| @username | Primary contributor to affected files |
## Update labels
Add appropriate labels based on your analysis:
```bash
gh label list --json name,description --limit 100
gh pr edit ${{ steps.pr.outputs.number }} --add-label "label1,label2"
```
When done, add the reviewed label:
```bash
gh pr edit ${{ steps.pr.outputs.number }} --add-label "Bot Reviewed"
```
## Remember
- This is a one-time automated response - you will NOT see their reply or respond again. Never say things like "let me know if you have questions", "I can help you with", or "feel free to ask" - you won't be there to follow up. Frame any questions for the maintainers who will follow up.
- Be helpful and welcoming - Many contributors are new to the project
- Be specific - Point to exact lines and suggest fixes, don't give vague feedback
- Think deeply - Don't just surface-level review, understand the intent and evaluate the approach
- Use inline suggestions - Make it easy for authors to accept changes
- You're a first-pass reviewer - A human maintainer will do final approval
@@ -0,0 +1,70 @@
name: Smoke Tests
on:
push:
branches: [main]
paths:
- 'src/core/**'
- 'src/shared/**'
- 'proto/**'
- 'evals/**'
- '.github/workflows/cline-evals-regression.yml'
pull_request:
paths:
- 'src/core/**'
- 'src/shared/**'
- 'proto/**'
- 'evals/**'
- '.github/workflows/cline-evals-regression.yml'
workflow_dispatch:
permissions:
contents: read
concurrency:
group: smoke-tests-${{ github.ref }}
cancel-in-progress: true
jobs:
smoke-tests:
name: Smoke Tests
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build and install CLI
run: |
npm run protos
cd cli && npm install && npm run build && npm link
echo "$(npm config get prefix)/bin" >> $GITHUB_PATH
- name: Verify CLI
run: cline --version
- name: Run smoke tests
env:
CLINE_API_KEY: ${{ secrets.CLINE_API_KEY }}
run: |
cline auth -p cline -k "$CLINE_API_KEY" -m "anthropic/claude-sonnet-4.5"
npx tsx evals/smoke-tests/run-smoke-tests.ts --trials 1 --parallel
- name: Generate summary
if: always()
run: cat evals/smoke-tests/results/latest/summary.md >> $GITHUB_STEP_SUMMARY
- name: Upload results
uses: actions/upload-artifact@v4
if: always()
with:
name: smoke-test-results-${{ github.run_id }}
path: evals/smoke-tests/results/latest/
retention-days: 30
-325
View File
@@ -1,325 +0,0 @@
name: Cline PR Code Review
on:
pull_request:
types:
[opened, ready_for_review]
# Manual trigger for backfilling existing PRs. Run from terminal:
# gh workflow run cline-pr-review.yml -f pr_number=1234
# Or batch process open PRs:
# gh pr list --state open --limit 10 --json number --jq '.[].number' | while read num; do
# gh workflow run cline-pr-review.yml -f pr_number=$num
# sleep 60
# done
workflow_dispatch:
inputs:
pr_number:
description: "PR number to review"
required: true
type: string
concurrency:
group: pr-review-${{ github.event.pull_request.number || inputs.pr_number }}
cancel-in-progress: true
jobs:
cline-pr-review:
# Runs on PR opened/ready_for_review (skips drafts) or manual trigger for backfilling
if: |
(github.event_name == 'pull_request' && github.event.pull_request.draft == false) ||
github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
timeout-minutes: 60
# SECURITY: These permissions are intentionally restrictive.
# - contents: read -> cline can read the codebase but CANNOT write/push any code
# - pull-requests: write -> cline can post reviews and inline suggestions
# - issues: read -> cline can search for related issues
# NOTE: Even with pull-requests: write, cline CANNOT merge PRs because branch protection
# requires 1 approval from a Code Owner. The GITHUB_TOKEN cannot bypass this.
permissions:
contents: read
pull-requests: write
issues: read
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Print HEAD commit
run: |
echo "HEAD is at: $(git rev-parse HEAD)"
echo "Short: $(git rev-parse --short HEAD)"
git log -1 --format="Commit: %H%nAuthor: %an <%ae>%nDate: %ad%nMessage: %s"
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: "npm"
- name: Install and Verify Cline CLI
run: |
npm install -g cline
cline version # verify installation
- name: Configure Cline with Anthropic
run: |
npx cline auth --provider anthropic \
--apikey "${{ secrets.ANTHROPIC_API_KEY }}" \
--modelid claude-opus-4-5-20251101
- name: Get PR number
id: pr
run: |
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
echo "number=${{ inputs.pr_number }}" >> $GITHUB_OUTPUT
else
echo "number=${{ github.event.pull_request.number }}" >> $GITHUB_OUTPUT
fi
- name: Review PR with Cline
env:
PR_NUMBER: ${{ steps.pr.outputs.number }}
GITHUB_REPO: ${{ github.repository }}
GH_TOKEN: ${{ github.token }}
CLINE_COMMAND_PERMISSIONS: |
{
"allow": [
"gh pr diff *",
"gh pr view *",
"gh pr checks *",
"gh pr list *",
"gh label list *",
"gh issue list *",
"gh issue view *",
"git log *",
"gh pr comment ${{ steps.pr.outputs.number }} *",
"gh pr edit ${{ steps.pr.outputs.number }} *",
"gh api repos/${{ github.repository }}/pulls/${{ steps.pr.outputs.number }}/comments *",
"gh api repos/${{ github.repository }}/pulls/${{ steps.pr.outputs.number }}/reviews *"
]
}
run: |
npx cline --yolo 'You'\''re a GitHub PR reviewer for the open source Cline repository. Your goal is to give the PR author helpful feedback and give maintainers the context they need to review efficiently.
PR: #'"${PR_NUMBER}"'
## Gather context
```bash
# Get full PR details
gh pr view '"${PR_NUMBER}"' --json number,title,body,author,createdAt,updatedAt,isDraft,labels,commits,files,additions,deletions,changedFiles,baseRefName,headRefName,mergeable,reviewDecision
# Get the diff
gh pr diff '"${PR_NUMBER}"'
# Check CI status
gh pr checks '"${PR_NUMBER}"'
# Get existing review comments (to understand context and your previous feedback)
gh api repos/'"${GITHUB_REPO}"'/pulls/'"${PR_NUMBER}"'/comments --jq '\''.[] | {user: .user.login, body: .body, path: .path, created_at: .created_at}'\''
# Get conversation comments
gh pr view '"${PR_NUMBER}"' --comments
```
If this is a re-review (workflow_dispatch event):
Read your previous comments carefully. Understand what you asked for before.
Check if new commits or comments address your previous feedback.
## Check contributing guidelines
Flag (but don'\''t block) if:
- Missing changeset - For user-facing changes, check if there'\''s a `.changeset/` file:
```bash
gh pr diff '"${PR_NUMBER}"' --name-only | grep '\''.changeset/'\'' || echo '\''No changeset found'\''
```
If missing, ask them to run `npm run changeset`
- Missing tests - New features should have tests
## Find related issues and PRs
Search thoroughly for context that might help with the review:
```bash
# Find related issues for context
gh issue list --search '\''<keywords from the PR>'\'' --state all --limit 30
gh issue list --search '\''<error messages or feature names>'\'' --state all --limit 20
# Find similar PRs for reference
gh pr list --search '\''<keywords>'\'' --state all --limit 30
```
For each relevant issue or PR you find, read it including comments:
```bash
gh issue view <number> --comments
gh pr view <number> --comments
```
Look for:
- Open issues this PR might fix that weren'\''t linked in the description
- Similar PRs that went through review - what feedback did they get? What patterns did they follow?
- Context from maintainer discussions that could inform your review
## Find subject matter experts
For files changed in this PR, find who knows the code best:
```bash
# Get files changed
gh pr diff '"${PR_NUMBER}"' --name-only
# For each relevant path, find contributors
git log --since='\''6 months ago'\'' --format='\''%an'\'' -- <path> | sort | uniq -c | sort -rn | head -5
```
Cross-reference git authors with GitHub usernames. Include an SME table in your response:
| SME | Reason |
|-----|--------|
| @username1 | Authored PR #X which modified this area |
| @username2 | Primary contributor to affected file (15 commits in 6 months) |
| @username3 | Reviewed similar PR #Y with extensive feedback |
## Bash command usage
Don'\''t use operators like `|`, `&&`, or `;` - run each command separately and analyze the output.
When referencing command outputs, quote them properly to avoid formatting issues.
## Deep code review
This is the most important part. Don'\''t just look for syntax issues - understand what the PR is trying to achieve and whether the implementation is the right approach.
Step 1: Understand the intent
Read the PR description and understand what the author is trying to accomplish. What problem are they solving? What feature are they adding?
Step 2: Form your own opinion first
Before analyzing their code, think about how YOU would implement this feature or fix. What files would you touch? What patterns would you follow? What edge cases would you handle?
Step 3: Compare approaches
Now look at their implementation. How does it compare to what you would have done?
- Is their approach better in some ways? Note what they did well.
- Is their approach missing something? Be specific about what and why.
- Are there edge cases they haven'\''t considered?
- Does it follow the patterns established in similar parts of the codebase?
Step 4: Look at the bigger picture
- What other files or systems does this change interact with?
- Could this break anything else?
- Is there additional work needed beyond this PR to complete the feature?
- Does this fit well with the overall architecture?
Step 5: Find reference implementations
Look for similar changes in the codebase:
```bash
git log --oneline --all --grep='\''<relevant keywords>'\'' | head -20
git log --oneline -- <similar files> | head -20
```
If this is adding a new API provider, look at how other providers are implemented.
If this is adding a new feature, look at how similar features were added.
Note where their implementation aligns with or diverges from established patterns.
Step 6: Standard code review checks
- DRY: Is there duplicated code that could be extracted?
- Error handling: Are errors handled appropriately?
- Security: Any injection risks, credential exposure, unsafe dependencies?
- Performance: Any obvious inefficiencies, memory leaks, N+1 patterns?
- Types: Is TypeScript used correctly? Any unsafe type assertions?
- Naming: Are variables and functions named clearly?
- Comments: Is complex logic explained? Are there outdated comments?
## Inline code suggestions
For specific code improvements, use GitHub'\''s suggestion syntax via `gh api`.
This creates suggestions the author can commit with one click.
Single-line suggestion:
```bash
gh api repos/'"${GITHUB_REPO}"'/pulls/'"${PR_NUMBER}"'/reviews \
-X POST \
-f commit_id="$(gh pr view '"${PR_NUMBER}"' --json headRefOid -q .headRefOid)" \
-f event='\''COMMENT'\'' \
-f body='\'''\'' \
-F comments='\''[
{
"path": "src/example.ts",
"line": 42,
"body": "Consider simplifying:\n\n```suggestion\nconst result = items.filter(Boolean);\n```"
}
]'\''
```
Multi-line suggestion (replacing lines 40-45):
```bash
gh api repos/'"${GITHUB_REPO}"'/pulls/'"${PR_NUMBER}"'/reviews \
-X POST \
-f commit_id="$(gh pr view '"${PR_NUMBER}"' --json headRefOid -q .headRefOid)" \
-f event='\''COMMENT'\'' \
-f body='\'''\'' \
-F comments='\''[
{
"path": "src/example.ts",
"start_line": 40,
"line": 45,
"body": "This can be simplified:\n\n```suggestion\nconst simplified = doThing();\n```"
}
]'\''
```
Use inline suggestions for concrete improvements. Use regular comments for questions or broader feedback.
## Post your review
After your investigation, post a single helpful comment that helps the author and gives maintainers context.
Start by noting the commit hash you reviewed:
```bash
git rev-parse --short HEAD
```
Include this at the top of your comment: "Reviewed at commit: <short hash>"
Then thank them for their contribution. Be conversational, not robotic.
Include what'\''s relevant:
- In-depth explanation of what the PR does - Be comprehensive. A maintainer should be able to read this section and fully understand the author'\''s intent, why they made the changes, how they implemented it, and what files/systems are affected. Don'\''t just summarize - explain.
- Related issues/PRs you found that provide useful context (link to them)
- Your review findings (issues to address, suggestions, etc.)
- Clear next steps for the author
Include a '\''For Maintainers'\'' section with:
- Anything else useful to help the maintainer resolve this PR
- Related issues/PRs with context on why they'\''re relevant
- Open issues this PR might fix that weren'\''t linked in the description
- Your recommendation: merge as-is, needs changes, needs discussion, close, etc.
- SME table - who should review this and why
For the SME table:
| SME | Reason |
|-----|--------|
| @username | Primary contributor to affected files |
## Update labels
Add appropriate labels based on your analysis:
```bash
gh label list --json name,description --limit 100
gh pr edit '"${PR_NUMBER}"' --add-label '\''label1,label2'\''
```
When done, add the reviewed label:
```bash
gh pr edit '"${PR_NUMBER}"' --add-label '\''Bot Reviewed'\''
```
## Remember
- This is a one-time automated response - you will NOT see their reply or respond again. Never say things like '\''let me know if you have questions'\'', '\''I can help you with'\'', or '\''feel free to ask'\'' - you won'\''t be there to follow up. Frame any questions for the maintainers who will follow up.
- Be helpful and welcoming - Many contributors are new to the project
- Be specific - Point to exact lines and suggest fixes, don'\''t give vague feedback
- Think deeply - Don'\''t just surface-level review, understand the intent and evaluate the approach
- Use inline suggestions - Make it easy for authors to accept changes
- You'\''re a first-pass reviewer - A human maintainer will do final approval'
+4 -14
View File
@@ -1,7 +1,7 @@
name: Publish NPM Release
on:
workflow_dispatch:
workflow_call:
inputs:
confirm_publish:
description: 'Type "publish" to confirm you want to publish to NPM'
@@ -10,6 +10,7 @@ on:
permissions:
contents: write # Required for pushing tags
id-token: write # Required for npm trusted publishing (OIDC)
checks: write # Required by test workflow
pull-requests: write # Required by test workflow
@@ -20,7 +21,7 @@ jobs:
publish-npm-release:
needs: test
name: Publish Cline CLI to NPM
if: github.repository == 'cline/cline' && github.ref == 'refs/heads/main' && github.event.inputs.confirm_publish == 'publish'
if: github.repository == 'cline/cline' && github.ref == 'refs/heads/main' && inputs.confirm_publish == 'publish'
runs-on: ubuntu-latest
steps:
@@ -30,19 +31,10 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20.x"
node-version: "24.x"
registry-url: "https://registry.npmjs.org"
# Cache root dependencies - only reuse if package-lock.json exactly matches
- name: Cache root dependencies
uses: actions/cache@v4
id: root-cache
with:
path: node_modules
key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json') }}
- name: Install root dependencies and CLI dependencies
if: steps.check_commits.outputs.skip != 'true'
run: npm ci --include=optional # this will also install cli deps because "cli" in included in root package.json workspaces field
- name: Generate Protos
@@ -81,8 +73,6 @@ jobs:
cat dist-standalone/package.json | grep version
- name: Publish to NPM with latest tag
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_RELEASE_TOKEN }}
run: |
echo "Publishing version ${{ steps.version.outputs.version }} to NPM with tag 'latest'..."
cd dist-standalone
+15 -15
View File
@@ -1,12 +1,17 @@
name: Publish NPM Nightly
on:
schedule:
- cron: "0 12 * * *" # 4 AM PST (UTC-8) = 12 UTC
workflow_dispatch:
workflow_call:
inputs:
force_publish:
description: "Force publish even if there are no commits in the last 24 hours"
required: false
type: boolean
default: false
permissions:
contents: read
id-token: write # Required for npm trusted publishing (OIDC)
checks: write # Required by test workflow
pull-requests: write # Required by test workflow
@@ -27,6 +32,12 @@ jobs:
- name: Check for recent commits
id: check_commits
run: |
if [ "${{ inputs.force_publish }}" = "true" ]; then
echo "force_publish enabled, proceeding with publish"
echo "skip=false" >> $GITHUB_OUTPUT
exit 0
fi
if [ $(git rev-list --count HEAD --since="24 hours ago") -eq 0 ]; then
echo "No commits in last 24 hours, skipping publish"
echo "skip=true" >> $GITHUB_OUTPUT
@@ -39,18 +50,9 @@ jobs:
if: steps.check_commits.outputs.skip != 'true'
uses: actions/setup-node@v4
with:
node-version: "20.x"
node-version: "24.x"
registry-url: "https://registry.npmjs.org"
# Cache root dependencies - only reuse if package-lock.json exactly matches
- name: Cache root dependencies
if: steps.check_commits.outputs.skip != 'true'
uses: actions/cache@v4
id: root-cache
with:
path: node_modules
key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json') }}
- name: Install root dependencies and CLI dependencies
if: steps.check_commits.outputs.skip != 'true'
run: npm ci --include=optional # this will also install cli deps because "cli" in included in root package.json workspaces field
@@ -118,8 +120,6 @@ jobs:
- name: Publish to NPM with nightly tag
if: steps.check_commits.outputs.skip != 'true'
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_RELEASE_TOKEN }}
run: |
echo "Publishing version ${{ steps.version.outputs.version }} to NPM with tag 'nightly'..."
cd dist-standalone
+215
View File
@@ -0,0 +1,215 @@
# Build and Pack CLI
#
# Builds a CLI tarball from any branch/commit and publishes it as a GitHub Release.
# Requires write access to the repository (maintainers/collaborators only).
#
# Security: Split into two jobs to isolate untrusted build code from write tokens.
# The build job runs arbitrary ref code with zero permissions. The release job
# only runs trusted GitHub Actions with write scope.
#
# Usage (helper script, auto-detects current branch):
# ./scripts/build-cli-artifact.sh
# ./scripts/build-cli-artifact.sh feature/my-changes
# ./scripts/build-cli-artifact.sh feature/my-changes 1234 # comments on PR
#
# Usage (gh CLI directly):
# gh workflow run pack-cli.yml -f ref=main
# gh workflow run pack-cli.yml -f ref=abc123 -f pr_number=1234
#
# Install the built CLI (no auth required):
# npm install -g https://github.com/cline/cline/releases/download/cli-build-<sha>/cline-<ver>.tgz
#
# Find releases:
# gh release list --limit 10
name: Build and Pack CLI
permissions:
contents: read
on:
workflow_dispatch:
inputs:
ref:
description: 'Branch, tag, or commit SHA to build (leave empty for default branch)'
required: false
type: string
pr_number:
description: 'PR number to comment on with install instructions (optional)'
required: false
type: number
jobs:
# ── Build job: runs untrusted ref code with ZERO permissions ──
build:
name: Build CLI
runs-on: ubuntu-latest
permissions: {}
outputs:
commit_sha: ${{ steps.commit.outputs.sha }}
tarball: ${{ steps.pack.outputs.tarball }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
ref: ${{ inputs.ref || github.ref }}
persist-credentials: false
- name: Get commit SHA
id: commit
run: |
COMMIT_SHA=$(git rev-parse --short HEAD)
echo "sha=$COMMIT_SHA" >> $GITHUB_OUTPUT
echo "Building from commit: $COMMIT_SHA"
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20.x"
- name: Install dependencies
run: npm ci --include=optional
- name: Generate Protos
run: npm run protos
- name: Build standalone package
run: node scripts/package-npm.mjs
- name: Create Tarball
id: pack
run: |
cd dist-standalone
TARBALL=$(npm pack)
echo "tarball=$TARBALL" >> $GITHUB_OUTPUT
echo "Created tarball: $TARBALL"
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: cli-tarball
path: dist-standalone/*.tgz
# ── Release job: only trusted Actions code, with write permissions ──
release:
name: Release CLI
needs: build
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
issues: write
steps:
- name: Download artifact
uses: actions/download-artifact@v4
with:
name: cli-tarball
path: dist-standalone
- name: Create GitHub Release
id: create_release
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const path = require('path');
const commit = '${{ needs.build.outputs.commit_sha }}';
const tarball = '${{ needs.build.outputs.tarball }}';
// Delete existing release/tag if re-running for the same commit
const tagName = `cli-build-${commit}`;
try {
const existing = await github.rest.repos.getReleaseByTag({
owner: context.repo.owner,
repo: context.repo.repo,
tag: tagName
});
await github.rest.repos.deleteRelease({
owner: context.repo.owner,
repo: context.repo.repo,
release_id: existing.data.id
});
await github.rest.git.deleteRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: `tags/${tagName}`
});
core.info(`Deleted existing release for ${tagName}`);
} catch (e) {
// Release doesn't exist yet, that's fine
}
// Create a release
const release = await github.rest.repos.createRelease({
owner: context.repo.owner,
repo: context.repo.repo,
tag_name: tagName,
name: `CLI Build (${commit})`,
body: `Automated CLI build from commit ${commit}\n\nInstall with:\n\`\`\`bash\nnpm install -g https://github.com/${context.repo.owner}/${context.repo.repo}/releases/download/${tagName}/${tarball}\n\`\`\``,
draft: false,
prerelease: true
});
// Upload the tarball as a release asset
const tarballPath = path.join('dist-standalone', tarball);
const tarballData = fs.readFileSync(tarballPath);
await github.rest.repos.uploadReleaseAsset({
owner: context.repo.owner,
repo: context.repo.repo,
release_id: release.data.id,
name: tarball,
data: tarballData
});
const downloadUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/releases/download/${tagName}/${tarball}`;
core.setOutput('release_url', release.data.html_url);
core.setOutput('download_url', downloadUrl);
- name: Comment on PR with download instructions
if: inputs.pr_number != ''
uses: actions/github-script@v7
with:
script: |
const commit = '${{ needs.build.outputs.commit_sha }}';
const releaseUrl = '${{ steps.create_release.outputs.release_url }}';
const downloadUrl = '${{ steps.create_release.outputs.download_url }}';
const prNumber = ${{ inputs.pr_number || 0 }};
if (!prNumber) return;
const comment = `## 📦 CLI Build Ready
A CLI build has been created for commit \`${commit}\`.
### Install Directly from URL (No Authentication Required!)
\`\`\`bash
npm install -g ${downloadUrl}
\`\`\`
### Alternative: Download and Install
\`\`\`bash
curl -L ${downloadUrl} -o cline.tgz
npm install -g ./cline.tgz
\`\`\`
📦 [View Release](${releaseUrl})
`;
await github.rest.issues.createComment({
issue_number: prNumber,
owner: context.repo.owner,
repo: context.repo.repo,
body: comment
});
- name: Summary
run: |
echo "✅ CLI build complete!"
echo ""
echo "📦 Release: ${{ steps.create_release.outputs.release_url }}"
echo "🔗 Download URL: ${{ steps.create_release.outputs.download_url }}"
echo ""
echo "Install from anywhere (no authentication required):"
echo " npm install -g ${{ steps.create_release.outputs.download_url }}"
@@ -0,0 +1,55 @@
name: Publish CLI (Trusted)
on:
schedule:
- cron: "0 12 * * *" # 4 AM PST (UTC-8) = 12 UTC
workflow_dispatch:
inputs:
publish_target:
description: "Which publish flow to run"
required: true
default: "main"
type: choice
options:
- main
- nightly
confirm_publish:
description: 'Required when publish_target=main. Type "publish" to confirm release publish.'
required: false
type: string
force_nightly_publish:
description: "Force nightly publish even with no commits in last 24h"
required: false
type: boolean
default: false
permissions:
id-token: write # Required for npm trusted publishing (OIDC)
contents: write # Required because npm-main creates/pushes git tags
checks: write # Required by nested reusable test workflow
pull-requests: write # Required by nested reusable test workflow
jobs:
publish-main:
if: |
github.repository == 'cline/cline' && (
github.event_name == 'workflow_dispatch' &&
github.event.inputs.publish_target == 'main' &&
github.event.inputs.confirm_publish == 'publish' &&
!endsWith(github.actor, '[bot]')
)
uses: ./.github/workflows/npm-main.yaml
secrets: inherit
with:
confirm_publish: ${{ github.event.inputs.confirm_publish }}
publish-nightly:
if: |
github.repository == 'cline/cline' && (
github.event_name == 'schedule' ||
(github.event_name == 'workflow_dispatch' && github.event.inputs.publish_target == 'nightly')
)
uses: ./.github/workflows/npm-nightly.yaml
secrets: inherit
with:
force_publish: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.force_nightly_publish == 'true' }}
+3 -17
View File
@@ -36,23 +36,9 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "lts/*"
# Cache root dependencies - only reuse if package-lock.json exactly matches
- name: Cache root dependencies
uses: actions/cache@v4
id: root-cache
with:
path: node_modules
key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json') }}
# Cache webview-ui dependencies - only reuse if package-lock.json exactly matches
- name: Cache webview-ui dependencies
uses: actions/cache@v4
id: webview-cache
with:
path: webview-ui/node_modules
key: ${{ runner.os }}-npm-webview-${{ hashFiles('webview-ui/package-lock.json') }}
# Keep publish environment aligned with test workflow/tooling lockfile expectations.
# Newer LTS (Node 24 / npm 11) can make `npm list` fail with ELSPROBLEMS during vsce packaging.
node-version: 22
- name: Install root dependencies
run: npm ci --include=optional
+1 -19
View File
@@ -42,30 +42,12 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "lts/*"
# Cache root dependencies - only reuse if package-lock.json exactly matches
- name: Cache root dependencies
uses: actions/cache@v4
id: root-cache
with:
path: node_modules
key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json') }}
# Cache webview-ui dependencies - only reuse if package-lock.json exactly matches
- name: Cache webview-ui dependencies
uses: actions/cache@v4
id: webview-cache
with:
path: webview-ui/node_modules
key: ${{ runner.os }}-npm-webview-${{ hashFiles('webview-ui/package-lock.json') }}
node-version: 22
- name: Install root dependencies
if: steps.root-cache.outputs.cache-hit != 'true'
run: npm install --include=optional
- name: Install webview-ui dependencies
if: steps.webview-cache.outputs.cache-hit != 'true'
run: cd webview-ui && npm install --include=optional
- name: Install Publishing Tools
+3
View File
@@ -48,3 +48,6 @@ test-results
.secrets
*.tsbuildinfo
# Smoke test results (generated)
evals/smoke-tests/results/
+3
View File
@@ -0,0 +1,3 @@
[submodule "evals/cline-bench"]
path = evals/cline-bench
url = https://github.com/cline/cline-bench.git
+2 -1
View File
@@ -16,7 +16,8 @@
"saoudrizwan.claude-dev", // Avoid conflicts with installed Cline
"--disable-extension",
"saoudrizwan.cline-nightly", // Avoid conflicts with installed Cline Nightly
"${workspaceFolder}"
"${workspaceFolder}",
"--disable-extensions"
],
"outFiles": [
"${workspaceFolder}/dist/**/*.js"
+126 -11
View File
@@ -1,26 +1,141 @@
# Changelog
## [3.66.0]
### Added
- Gemini-3.1 Pro Preview
## [3.65.0]
### Added
- Add /skills slash command to CLI for viewing and managing installed skills
### Fixed
- Fix aggressive context compaction caused by accidental clicks on the context window progress bar silently setting a very low auto-condense threshold
- Fix infinite retry loop when write_to_file fails with missing content parameter.
- Fixed default claude model
## [3.64.0]
### Added
- Added sonnet 4.6
## [3.63.0]
### Added
- added zai GLM 5 Free promo
### Fixed
- Restore reasoning trace visibility in chat and improve the thinking row UX so reasoning is visible, then collapsible after completion.
## [3.62.0]
### Fixed
- Banners now display immediately when opening the extension instead of requiring user interaction first
- Resolved 17 security vulnerabilities including high-severity DoS issues in dependencies (body-parser, axios, qs, tar, and others)
## [3.61.0]
- UI/UX fixes with minimax model family
## [3.60.0]
- Fixes for Minimax model family
## [3.59.0]
- Added Minimax 2.5 Free Promo
- Fixed Response chaining for OpenAI's Responses API
## [3.58.0]
### Added
- Subagent: replace legacy subagents with the native `use_subagents` tool
- Bundle `endpoints.json` support so packaged distributions can ship required endpoints out-of-the-box
- Amazon Bedrock: support parallel tool calling
- New "double-check completion" experimental feature to verify work before marking tasks complete
- CLI: new task controls/flags including custom `--thinking` token budget and `--max-consecutive-mistakes` for yolo runs
- Remote config: new UI/options (including connection/test buttons) and support for syncing deletion of remotely configured MCP servers
- Vertex / Claude Code: add 1M context model options for Claude Opus 4.6
- ZAI/GLM: add GLM-5
### Fixed
- CLI: handle stdin redirection correctly in CI/headless environments
- CLI: preserve OAuth callback paths during auth redirects
- VS Code Web: generate auth callback URLs via `vscode.env.asExternalUri` (OAuth callback reliability)
- Terminal: surface command exit codes in results and improve long-running `execute_command` timeout behavior
- UI: add loading indicator and fix `api_req_started` rendering
- Task streaming: prevent duplicate streamed text rows after completion
- API: preserve selected Vercel model when model metadata is missing
- Telemetry: route PostHog networking through proxy-aware shared fetch and ensure telemetry flushes on shutdown
- CI: increase Windows E2E test timeout to reduce flakiness
### Changed
- Settings/model UX: move "reasoning effort" into model configuration and expose it in settings
- CLI provider selection: limit provider list to those remotely configured
- UI: consolidate ViewHeader component/styling across views
- Tools: add auto-approval support for `attempt_completion` commands
- Remotely configured MCP server schema now supports custom headers
## [3.57.1]
### Fixed
- Fixed Opus 4.6 for bedrock provider
## [3.57.0]
### Added
- Cline CLI 2.0 now available. Install with `npm install -g cline`
- Anthopic Opus 4.6
- Minimax-2.1 and Kimi-k2.5 now available for free for a limited time promo
- Codex-5.3 through ChatGPT subscription
### Fixed
- Fix read file tool to support reading large files
- Fix decimal input crash in OpenAI Compatible price fields (#8129)
- Fix build complete handlers when updating the api config
- Fixed missing provider from list
- Fixed Favorite Icon / Star from getting clipped in the task history view
### Changed
- Make skills always enabled and remove feature toggle setting
## [3.56.0]
### Added
- __CLI authentication:__ Added Vercel AI Gateway and Cline API key provider support for headless CI/automation workflows
- __New model:__ Added Kimi-K2.5 model to Moonshot provider (262K context, image support, prompt caching)
- __Prompt variant:__ Added Trinity Large prompt variant for improved tool-calling support
- __OpenTelemetry:__ Added support for custom headers on metrics and logs endpoints
- __Social links:__ Added community icons (X, Discord, GitHub, Reddit, LinkedIn) to the What's New modal
- **CLI authentication:** Added Vercel AI Gateway and Cline API key provider support for headless CI/automation workflows
- **New model:** Added Kimi-K2.5 model to Moonshot provider (262K context, image support, prompt caching)
- **Prompt variant:** Added Trinity Large prompt variant for improved tool-calling support
- **OpenTelemetry:** Added support for custom headers on metrics and logs endpoints
- **Social links:** Added community icons (X, Discord, GitHub, Reddit, LinkedIn) to the What's New modal
### Fixed
- __LiteLLM:__ Fixed thinking configuration not appearing for reasoning-capable models
- __OpenTelemetry:__ Fixed endpoint path handling (no longer incorrectly appends `/v1/logs` or `/v1/metrics`) and ensured logs are sent regardless of VSCode telemetry settings
- __CLI auth:__ Fixed `cline auth` displaying incorrect provider information after configuration
- **LiteLLM:** Fixed thinking configuration not appearing for reasoning-capable models
- **OpenTelemetry:** Fixed endpoint path handling (no longer incorrectly appends `/v1/logs` or `/v1/metrics`) and ensured logs are sent regardless of VSCode telemetry settings
- **CLI auth:** Fixed `cline auth` displaying incorrect provider information after configuration
### Changed
- __Hooks:__ Hook scripts now run from the workspace repository root instead of filesystem root
- __Default settings:__ Enabled multi-root workspaces, parallel tool calling, and skills by default; disabled strict plan mode by default
- __Settings UI:__ Refreshed feature settings section with collapsible design
- **Hooks:** Hook scripts now run from the workspace repository root instead of filesystem root
- **Default settings:** Enabled multi-root workspaces, parallel tool calling, and skills by default; disabled strict plan mode by default
- **Settings UI:** Refreshed feature settings section with collapsible design
## [3.55.0]
+27
View File
@@ -0,0 +1,27 @@
# Security Policy
## Supported Versions
We actively patch only the most recent minor release of Cline. Older versions receive fixes at our discretion.
## Reporting a Vulnerability
We appreciate your efforts to responsibly disclose your findings and will make every effort to acknowledge your contributions.
To report a security issue, please use the GitHub Security Advisory ["Report a Vulnerability"](https://github.com/cline/cline/security/advisories/new) tab.
The team will send a response indicating the next steps in handling your report. After the initial reply, the security team will keep you informed of the progress towards a fix and full announcement, and may ask for additional information or guidance.
When reporting, please include:
- A short summary of the issue
- Steps to reproduce or a proof of concept
- Any logs, stack traces, or screenshots that might help us understand the problem
We acknowledge reports within 48 hours and aim to release a fix or mitigation within 30 days. While we work on a resolution, please keep the details private.
## Escalation
If you do not receive an acknowledgement of your report within 5 business days, you may send an email to security@cline.bot.
Thank you for helping us keep Cline users safe.
+1 -1
View File
@@ -43,7 +43,7 @@
"a11y": "info",
"style": {
"useNodejsImportProtocol": "off",
"useImportType": "info",
"useImportType": "off",
"useBlockStatements": "off",
"useNamingConvention": "off",
"useThrowOnlyError": "info",
+105
View File
@@ -0,0 +1,105 @@
# cline
## 2.4.2
### Added
- Gemini-3.1 Pro Preview
### Patch Changes
- VSCode uses shared files for global, workspace and secret state.
## [2.4.1]
### Fixed
- Fix infinite retry loop when write_to_file fails with missing content parameter. Provides progressive guidance to the model, escalating from suggestions to hard stops, with context window awareness to break the loop.
## [2.4.0]
### Added
- Adding Anthropic Sonnet 4.6
- Allows users to enter custom aws region when selecting bedrock as a provider in CLI
- Keep reasoning rows visible when low-stakes tool groups start immediately after reasoning.
- Restore reasoning trace visibility in chat and improve the thinking row UX so streamed reasoning is visible, then collapsible after completion.
### Fixed
- Banners now display immediately when opening the extension instead of requiring user interaction first
- Resolved 17 security vulnerabilities including high-severity DoS issues in dependencies (body-parser, axios, qs, tar, and others)
## [2.2.2]
- Allows users to enter custom aws region when selecting bedrock as a provider
- Prevent Parent Container Scrolling In Dropdowns
## [2.2.1]
- Added Minimax 2.5 Free Promo
- Fixed Response chaining for OpenAI's Responses API
## [2.2.0]
### Added
- Subagent: replace legacy subagents with the native `use_subagents` tool
- Bundle `endpoints.json` support so packaged distributions can ship required endpoints out-of-the-box
- Amazon Bedrock: support parallel tool calling
- New "double-check completion" experimental feature to verify work before marking tasks complete
- CLI: new task controls/flags including custom `--thinking` token budget and `--max-consecutive-mistakes` for yolo runs
- Remote config: new UI/options (including connection/test buttons) and support for syncing deletion of remotely configured MCP servers
- Vertex / Claude Code: add 1M context model options for Claude Opus 4.6
- ZAI/GLM: add GLM-5
### Fixed
- CLI: handle stdin redirection correctly in CI/headless environments
- CLI: preserve OAuth callback paths during auth redirects
- VS Code Web: generate auth callback URLs via `vscode.env.asExternalUri` (OAuth callback reliability)
- Terminal: surface command exit codes in results and improve long-running `execute_command` timeout behavior
- UI: add loading indicator and fix `api_req_started` rendering
- Task streaming: prevent duplicate streamed text rows after completion
- API: preserve selected Vercel model when model metadata is missing
- Telemetry: route PostHog networking through proxy-aware shared fetch and ensure telemetry flushes on shutdown
- CI: increase Windows E2E test timeout to reduce flakiness
### Changed
- Settings/model UX: move "reasoning effort" into model configuration and expose it in settings
- CLI provider selection: limit provider list to those remotely configured
- UI: consolidate ViewHeader component/styling across views
- Tools: add auto-approval support for `attempt_completion` commands
- Remotely configured MCP server schema now supports custom headers
## [2.1.0]
### Minor Changes
- 42ce100: Add Generate API Key on Hicap Provider selection
### Patch Changes
- 195294f: Add support for bundled endpoints.json in enterprise distributions. Extensions can now include a pre-configured endpoints.json file that automatically switches Cline to self-hosted mode. Includes packaging scripts for VSIX, NPM, and JetBrains plugins.
- a1f2601: Replace the LiteLLM model list with a selector
- 739d75a: Add Claude Code provider support for Claude Opus 4.6 and Sonnet 4.5 1M variants via both full model names and aliases (`opus[1m]`, `sonnet[1m]`), and align the `opus` alias with Opus 4.6.
- 8440380: Add GitHub Actions workflow to build CLI from any commit for testing
- b1a8db2: fix(cli): prevent hang when spawned without TTY
- 7c87017: Add Claude Opus 4.6 model support
- d116ac5: Supports rendering markdown table in chat view.
- 6d8fb85: Fix CLI crashing in CI environments and with stdin redirection (e.g., `cline "prompt" < /dev/null`). Now checks both stdin and stdout TTY status before using Ink, and only errors on empty stdin when no prompt is provided.
- 70a9904: Fix JetBrains sign-in regression by adding fallback for openExternal RPC
- f440f3a: fix: use vscode.env.openExternal for auth in remote environments
Fixes OAuth authentication in VS Code Server and remote environments by routing browser URL opening through VS Code's native openExternal API instead of the npm 'open' package.
- 70a9904: fix: use vscode.env.asExternalUri for auth callback URLs only in VS Code Web
Fixes OAuth callback redirect in VS Code Web (`code serve-web`, Codespaces) by using `vscode.env.asExternalUri()` to resolve the callback URI. This is gated behind a `vscode.env.uiKind === UIKind.Web` check so regular desktop VS Code continues to use the `vscode://` URI directly. The `getCallbackUrl` API now accepts a `path` parameter so the full callback URI (including route) is resolved correctly, and callers pass their path directly instead of appending after.
- 5308ded: Updating script documentation and removing unnecessary continue on error
- b514f18: Prevent duplicate streamed text rows when a partial text update arrives after the same text was already finalized.
- 26391c9: Fix Bedrock model id
- d19a877: Unify ViewHeader Styles Across All Views
- 5dcaa8c: Add Vertex Claude Opus 4.6 1M model option and global endpoint support, and pass the 1M beta header for Vertex Claude requests.
+1 -2
View File
@@ -45,7 +45,7 @@ cline
### Use any API and Model
Cline supports API providers like ChatGPT, Anthropic, OpenAI, Google Gemini, AWS Bedrock, Azure, GCP Vertex, Cerebras and Groq. You can also configure any OpenAI compatible API, or use a local model through LM Studio/Ollama. If you're using a Cline Account, you'll always have access to the newest models as soon as they're available.
Cline supports API providers like ChatGPT, Anthropic, OpenAI, Google Gemini, AWS Bedrock, Azure, GCP Vertex, Cerebras, Groq, and Moonshot. You can also configure any OpenAI compatible API, or use a local model through LM Studio/Ollama. If you're using a Cline Account, you'll always have access to the newest models as soon as they're available.
<!-- Transparent pixel to create line break after floating image -->
@@ -79,4 +79,3 @@ Get the same Cline experience with enterprise-grade controls: SSO (SAML/OIDC), g
## License
[Apache 2.0 © 2026 Cline Bot Inc.](./LICENSE)
+43 -15
View File
@@ -88,6 +88,10 @@ directory
\f[B]\-\-thinking\f[R] : Enable extended thinking (1024 token budget)
.PP
\f[B]\-\-json\f[R] : Output messages as JSON instead of styled text
.PP
\f[B]\-T\f[R], \f[B]\-\-taskId\f[R] \f[I]id\f[R] : Resume an existing
task by ID.
The prompt argument becomes an optional follow\-up message.
.SS history (alias: h)
List task history with pagination.
.PP
@@ -121,13 +125,13 @@ authentication wizard, or use quick setup flags.
Options:
.PP
\f[B]\-p\f[R], \f[B]\-\-provider\f[R] \f[I]id\f[R] : Provider ID for
quick setup (e.g., openai\-native, anthropic, openrouter)
quick setup (e.g., openai\-native, anthropic, openrouter, moonshot)
.PP
\f[B]\-k\f[R], \f[B]\-\-apikey\f[R] \f[I]key\f[R] : API key for the
provider
.PP
\f[B]\-m\f[R], \f[B]\-\-modelid\f[R] \f[I]id\f[R] : Model ID to
configure (e.g., gpt\-4o, claude\-sonnet\-4\-5\-20250929)
configure (e.g., gpt\-4o, claude\-sonnet\-4\-5\-20250929, kimi\-k2.5)
.PP
\f[B]\-b\f[R], \f[B]\-\-baseurl\f[R] \f[I]url\f[R] : Base URL (optional,
for OpenAI\-compatible providers)
@@ -179,6 +183,10 @@ the task
.PP
\f[B]\-\-json\f[R] : Output messages as JSON instead of styled text.
Forces plain text mode.
.PP
\f[B]\-T\f[R], \f[B]\-\-taskId\f[R] \f[I]id\f[R] : Resume an existing
task by ID instead of starting a new one.
The prompt becomes an optional follow\-up message.
.SH JSON OUTPUT FORMAT
When using \f[B]\-\-json\f[R], each message is output as a JSON object
with these fields:
@@ -234,6 +242,9 @@ cline \-m claude\-sonnet\-4\-5\-20250929 \(dqRefactor this function\(dq
\f[I]# Quick auth setup with model\f[R]
cline auth \-p anthropic \-k sk\-ant\-xxxxx \-m claude\-sonnet\-4\-5\-20250929
\f[I]# Quick auth setup for Moonshot\f[R]
cline auth \-p moonshot \-k sk\-xxxxx \-m kimi\-k2.5
.EE
.SS Including Images
.IP
@@ -274,6 +285,21 @@ cline history
\f[I]# Show more tasks with pagination\f[R]
cline history \-n 20 \-p 2
.EE
.SS Resuming Tasks
.IP
.EX
\f[I]# Resume a task by ID (get IDs from cline history)\f[R]
cline \-T abc123def
\f[I]# Resume a task with a follow\-up message\f[R]
cline \-T abc123def \(dqNow add unit tests for the changes\(dq
\f[I]# Resume in plan mode to review before continuing\f[R]
cline \-T abc123def \-p \(dqWhat\(aqs left to do?\(dq
\f[I]# Resume with yolo mode for automated continuation\f[R]
cline \-T abc123def \-y \(dqContinue with the implementation\(dq
.EE
.SS Authentication
.IP
.EX
@@ -286,6 +312,9 @@ cline auth \-p anthropic \-k sk\-ant\-api\-xxxxx
\f[I]# Quick setup for OpenAI\f[R]
cline auth \-p openai\-native \-k sk\-xxxxx \-m gpt\-4o
\f[I]# Quick setup for Moonshot\f[R]
cline auth \-p moonshot \-k sk\-xxxxx \-m kimi\-k2.5
\f[I]# OpenAI\-compatible provider with custom base URL\f[R]
cline auth \-p openai \-k your\-api\-key \-b https://api.example.com/v1
.EE
@@ -348,20 +377,19 @@ export CLINE_COMMAND_PERMISSIONS=\(aq{\(dqallow\(dq: [\(dqnpm *\(dq, \(dqgit *\(
\f[I]# Allow file operations with redirects\f[R]
export CLINE_COMMAND_PERMISSIONS=\(aq{\(dqallow\(dq: [\(dqcat *\(dq, \(dqecho *\(dq], \(dqallowRedirects\(dq: true}\(aq
.EE
.SH FILES
\f[B]\(ti/.cline/data/\f[R] : Default configuration directory
containing:
.SH CONFIGURATION FILES
.IP
.EX
\(ti/.cline/
├── data/ # Default configuration directory
│ ├── globalState.json # Global settings and state
│ ├── secrets.json # API keys and secrets (stored securely)
│ ├── workspace/ # Workspace\-specific state
│ └── tasks/ # Task history and conversation data
└── log/ # Log files for debugging
.EE
.PP
\f[B]globalState.json\f[R] : Global settings and state
.PP
\f[B]secrets.json\f[R] : API keys and secrets (stored securely)
.PP
\f[B]workspace/\f[R] : Workspace\-specific state
.PP
\f[B]tasks/\f[R] : Task history and conversation data
.PP
\f[B]\(ti/.cline/log/\f[R] : Log files for debugging.
View with \f[CR]cline dev log\f[R].
View logs with \f[CR]cline dev log\f[R].
.SH BUGS
Report bugs at: \c
.UR https://github.com/cline/cline/issues
+24
View File
@@ -56,6 +56,8 @@ Run a new task with a prompt.
**-y**, **\--yolo** : Enable yolo/yes mode (auto-approve all actions, output in plain mode, exit process automatically when task complete)
**-t**, **\--timeout** *seconds* : Optional timeout in seconds. Only applied when explicitly provided.
**-m**, **\--model** *model* : Model to use for the task
**-i**, **\--images** *paths...* : Image file paths to include with the task
@@ -70,6 +72,8 @@ Run a new task with a prompt.
**\--json** : Output messages as JSON instead of styled text
**-T**, **\--taskId** *id* : Resume an existing task by ID. The prompt argument becomes an optional follow-up message.
## history (alias: h)
List task history with pagination.
@@ -142,6 +146,8 @@ When running **cline** with just a prompt (no subcommand), these options are ava
**-y**, **\--yolo** : Enable yolo mode (auto-approve all actions). Also forces plain text output mode.
**-t**, **\--timeout** *seconds* : Optional timeout in seconds. Only applied when explicitly provided.
**-m**, **\--model** *model* : Model to use for the task
**-v**, **\--verbose** : Show verbose output
@@ -154,6 +160,8 @@ When running **cline** with just a prompt (no subcommand), these options are ava
**\--json** : Output messages as JSON instead of styled text. Forces plain text mode.
**-T**, **\--taskId** *id* : Resume an existing task by ID instead of starting a new one. The prompt becomes an optional follow-up message.
# JSON OUTPUT FORMAT
When using **\--json**, each message is output as a JSON object with these fields:
@@ -251,6 +259,22 @@ cline history
cline history -n 20 -p 2
```
## Resuming Tasks
```bash
# Resume a task by ID (get IDs from cline history)
cline -T abc123def
# Resume a task with a follow-up message
cline -T abc123def "Now add unit tests for the changes"
# Resume in plan mode to review before continuing
cline -T abc123def -p "What's left to do?"
# Resume with yolo mode for automated continuation
cline -T abc123def -y "Continue with the implementation"
```
## Authentication
```bash
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "cline",
"version": "2.0.4",
"version": "2.4.2",
"description": "Autonomous coding agent CLI - capable of creating/editing files, running commands, using the browser, and more",
"main": "dist/cli.mjs",
"bin": {
+10 -28
View File
@@ -12,11 +12,7 @@
import type * as acp from "@agentclientprotocol/sdk"
import type { TerminalHandle } from "@agentclientprotocol/sdk"
import {
DEFAULT_SUBAGENT_TERMINAL_OUTPUT_LINE_LIMIT,
DEFAULT_TERMINAL_OUTPUT_LINE_LIMIT,
PROCESS_HOT_TIMEOUT_NORMAL,
} from "@integrations/terminal/constants"
import { DEFAULT_TERMINAL_OUTPUT_LINE_LIMIT, PROCESS_HOT_TIMEOUT_NORMAL } from "@integrations/terminal/constants"
import type {
ITerminal,
ITerminalManager,
@@ -142,12 +138,12 @@ export interface ManagedTerminal {
* Wraps ACP terminal operations and emits events compatible with ITerminalProcess.
*/
class AcpTerminalProcess extends EventEmitter<TerminalProcessEvents> implements ITerminalProcess {
isHot: boolean = false
waitForShellIntegration: boolean = false
isHot = false
waitForShellIntegration = false
private _unretrievedOutput: string = ""
private _continued: boolean = false
private _completed: boolean = false
private _unretrievedOutput = ""
private _continued = false
private _completed = false
private _hotTimeout: NodeJS.Timeout | null = null
private _exitWaitTimeout: NodeJS.Timeout | null = null
private readonly manager: AcpTerminalManager
@@ -397,7 +393,7 @@ export class AcpTerminalManager implements ITerminalManager {
private readonly numericIdToStringId: Map<number, string> = new Map()
/** Next numeric ID to assign */
private nextNumericId: number = 1
private nextNumericId = 1
/** Active processes indexed by numeric terminal ID */
private readonly processes: Map<number, AcpTerminalProcess> = new Map()
@@ -406,9 +402,8 @@ export class AcpTerminalManager implements ITerminalManager {
private readonly terminalInfos: Map<number, TerminalInfo> = new Map()
// Configuration options for ITerminalManager
private terminalReuseEnabled: boolean = true
private terminalReuseEnabled = true
private terminalOutputLineLimit: number = DEFAULT_TERMINAL_OUTPUT_LINE_LIMIT
private subagentTerminalOutputLineLimit: number = DEFAULT_SUBAGENT_TERMINAL_OUTPUT_LINE_LIMIT
/**
* Creates a new AcpTerminalManager.
@@ -667,14 +662,6 @@ export class AcpTerminalManager implements ITerminalManager {
this.terminalOutputLineLimit = limit
}
/**
* Set the maximum number of output lines for subagent commands.
* @param limit Maximum number of lines
*/
setSubagentTerminalOutputLineLimit(limit: number): void {
this.subagentTerminalOutputLineLimit = limit
}
/**
* Set the default terminal profile.
* @param profile The profile identifier
@@ -687,15 +674,10 @@ export class AcpTerminalManager implements ITerminalManager {
* Process output lines, potentially truncating if over limit.
* @param outputLines Array of output lines
* @param overrideLimit Optional limit override
* @param isSubagentCommand Whether this is a subagent command
* @returns Processed output string
*/
processOutput(outputLines: string[], overrideLimit?: number, isSubagentCommand?: boolean): string {
const limit = isSubagentCommand
? overrideLimit !== undefined
? overrideLimit
: this.subagentTerminalOutputLineLimit
: this.terminalOutputLineLimit
processOutput(outputLines: string[], overrideLimit?: number): string {
const limit = overrideLimit !== undefined ? overrideLimit : this.terminalOutputLineLimit
if (outputLines.length > limit) {
const halfLimit = Math.floor(limit / 2)
+13 -55
View File
@@ -28,6 +28,8 @@ import {
groqModels,
mistralDefaultModelId,
mistralModels,
moonshotDefaultModelId,
moonshotModels,
openAiCodexDefaultModelId,
openAiNativeDefaultModelId,
openAiNativeModels,
@@ -36,7 +38,6 @@ import {
} from "@shared/api"
import type { ClineAsk, ClineMessage as ClineMessageType } from "@shared/ExtensionMessage"
import { CLI_ONLY_COMMANDS, VSCODE_ONLY_COMMANDS } from "@shared/slashCommands"
import { ProviderToApiKeyMap } from "@shared/storage"
import { getProviderModelIdKey } from "@shared/storage/provider-keys"
import { ClineEndpoint } from "@/config.js"
import { Controller } from "@/core/controller"
@@ -51,12 +52,12 @@ import { openAiCodexOAuthManager } from "@/integrations/openai-codex/oauth"
import { StandaloneTerminalManager } from "@/integrations/terminal/index.js"
import { AuthService } from "@/services/auth/AuthService.js"
import { Logger } from "@/shared/services/Logger.js"
import { secretStorage } from "@/shared/storage/ClineSecretStorage"
import type { Mode } from "@/shared/storage/types"
import { openExternal } from "@/utils/env"
import { ACPDiffViewProvider } from "../acp/ACPDiffViewProvider.js"
import { ACPHostBridgeClientProvider } from "../acp/ACPHostBridgeClientProvider.js"
import { AcpTerminalManager } from "../acp/AcpTerminalManager.js"
import { isAuthConfigured } from "../index.js"
import { fetchOpenRouterModels, usesOpenRouterModels } from "../utils/openrouter-models"
import { CliContextResult, initializeCliContext } from "../vscode-context.js"
import { ClineSessionEmitter } from "./ClineSessionEmitter.js"
@@ -72,6 +73,7 @@ const providerModels: Record<string, { models: Record<string, unknown>; defaultI
bedrock: { models: bedrockModels, defaultId: bedrockDefaultModelId },
deepseek: { models: deepSeekModels, defaultId: deepSeekDefaultModelId },
mistral: { models: mistralModels, defaultId: mistralDefaultModelId },
moonshot: { models: moonshotModels, defaultId: moonshotDefaultModelId },
groq: { models: groqModels, defaultId: groqDefaultModelId },
xai: { models: xaiModels, defaultId: xaiDefaultModelId },
}
@@ -173,8 +175,8 @@ export class ClineAgent implements acp.Agent {
async initialize(params: acp.InitializeRequest, connection?: acp.AgentSideConnection): Promise<acp.InitializeResponse> {
this.clientCapabilities = params.clientCapabilities
this.initializeHostProvider(this.clientCapabilities, connection)
await ClineEndpoint.initialize()
await StateManager.initialize(this.ctx.extensionContext)
await ClineEndpoint.initialize(this.ctx.EXTENSION_DIR)
await StateManager.initialize(this.ctx.storageContext)
return {
protocolVersion: PROTOCOL_VERSION,
@@ -246,8 +248,8 @@ export class ClineAgent implements acp.Agent {
},
hostBridgeClientProvider,
(message: string) => Logger.info(message),
async () => {
return AuthHandler.getInstance().getCallbackUrl()
async (path: string) => {
return AuthHandler.getInstance().getCallbackUrl(path)
},
async () => "", // get binary location not needed in ACP mode
this.ctx.EXTENSION_DIR,
@@ -263,7 +265,7 @@ export class ClineAgent implements acp.Agent {
*/
async newSession(params: acp.NewSessionRequest): Promise<acp.NewSessionResponse> {
// Check if authentication is required
const isAuthenticated = await this.isAuthConfigured()
const isAuthenticated = await isAuthConfigured()
if (!isAuthenticated) {
throw RequestError.authRequired()
}
@@ -973,7 +975,7 @@ export class ClineAgent implements acp.Agent {
// Get the callback URL first to ensure the server is ready
let callbackUrl: string
try {
callbackUrl = await authHandler.getCallbackUrl()
callbackUrl = await authHandler.getCallbackUrl("/auth")
Logger.debug("[ClineAgent] Callback URL ready:", callbackUrl)
} catch (error) {
Logger.error("[ClineAgent] Failed to get callback URL:", error)
@@ -1004,13 +1006,14 @@ export class ClineAgent implements acp.Agent {
const startTime = Date.now()
while (Date.now() - startTime < AUTH_TIMEOUT_MS) {
const stateManager = StateManager.get()
// Check if auth data has been stored
const authData = await secretStorage.get("cline:clineAccountId")
const authData = stateManager.getSecretKey("cline:clineAccountId")
if (authData) {
Logger.debug("[ClineAgent] Authentication successful")
// Set up the provider configuration for cline
const stateManager = StateManager.get()
stateManager.setGlobalState("actModeApiProvider", "cline")
stateManager.setGlobalState("planModeApiProvider", "cline")
await stateManager.flushPendingState()
@@ -1143,48 +1146,6 @@ export class ClineAgent implements acp.Agent {
}
}
/**
* Check if the user has authentication configured.
* Returns true if they have either:
* - Cline provider with stored auth data
* - OpenAI Codex provider with OAuth credentials
* - BYO provider with an API key configured
*/
private async isAuthConfigured(): Promise<boolean> {
const stateManager = StateManager.get()
const mode = stateManager.getGlobalSettingsKey("mode") as string
const providerKey = mode === "act" ? "actModeApiProvider" : "planModeApiProvider"
const currentProvider = (stateManager.getGlobalSettingsKey(providerKey) as string) || "cline"
if (currentProvider === "cline") {
// For Cline provider, check if we have stored auth data
const values = await Promise.all(["clineApiKey", "clineAccountId"].map((key) => secretStorage.get(key)))
return values.some(Boolean)
}
// For OpenAI Codex provider, check OAuth credentials
if (currentProvider === "openai-codex") {
openAiCodexOAuthManager.initialize(this.ctx.extensionContext)
return await openAiCodexOAuthManager.isAuthenticated()
}
// For BYO providers, check if the API key is configured
const keyField = ProviderToApiKeyMap[currentProvider as keyof typeof ProviderToApiKeyMap]
if (!keyField) {
return false
}
const fields = Array.isArray(keyField) ? keyField : [keyField]
for (const field of fields) {
const value = await secretStorage.get(field)
if (value) {
return true
}
}
return false
}
/**
* Handle OpenAI Codex OAuth authentication flow.
*
@@ -1198,9 +1159,6 @@ export class ClineAgent implements acp.Agent {
Logger.debug("[ClineAgent] Starting OpenAI Codex OAuth flow...")
try {
// Initialize the OAuth manager with extension context
openAiCodexOAuthManager.initialize(this.ctx.extensionContext)
// Get the authorization URL and start the callback server
const authUrl = openAiCodexOAuthManager.startAuthorizationFlow()
+4
View File
@@ -312,6 +312,10 @@ function translateSayMessage(
// API request finished - no specific update needed
break
case "subagent_usage":
// Hidden aggregate metrics event used for task-level accounting.
break
case "task":
// Task started - don't echo the user's prompt back to them
// The ACP client already knows what they typed
+88 -28
View File
@@ -6,21 +6,24 @@
import { Box, Text, useApp, useInput } from "ink"
import Spinner from "ink-spinner"
import React, { useCallback, useEffect, useMemo, useState } from "react"
import { refreshOcaModels } from "@/core/controller/models/refreshOcaModels"
import { StateManager } from "@/core/storage/StateManager"
import { openAiCodexOAuthManager } from "@/integrations/openai-codex/oauth"
import { AuthService } from "@/services/auth/AuthService"
import { liteLlmDefaultModelId, openAiCodexDefaultModelId, openRouterDefaultModelId } from "@/shared/api"
import { openAiCodexDefaultModelId, openRouterDefaultModelId } from "@/shared/api"
import { StringRequest } from "@/shared/proto/cline/common"
import { openExternal } from "@/utils/env"
import { COLORS } from "../constants/colors"
import { getAllFeaturedModels } from "../constants/featured-models"
import { useStdinContext } from "../context/StdinContext"
import { useOcaAuth } from "../hooks/useOcaAuth"
import { useScrollableList } from "../hooks/useScrollableList"
import { type DetectedSources, detectImportSources, type ImportSource } from "../utils/import-configs"
import { isMouseEscapeSequence } from "../utils/input"
import { applyBedrockConfig, applyProviderConfig } from "../utils/provider-config"
import { useValidProviders } from "../utils/providers"
import { ApiKeyInput } from "./ApiKeyInput"
import { StaticRobotFrame } from "./AsciiMotionCli"
import { BedrockCustomModelFlow } from "./BedrockCustomModelFlow"
import { type BedrockConfig, BedrockSetup } from "./BedrockSetup"
import {
FeaturedModelPicker,
@@ -29,8 +32,9 @@ import {
isBrowseAllSelected,
} from "./FeaturedModelPicker"
import { ImportView } from "./ImportView"
import { getDefaultModelId, hasModelPicker, ModelPicker } from "./ModelPicker"
import { CLI_EXCLUDED_PROVIDERS, getProviderLabel, getProviderOrder } from "./ProviderPicker"
import { CUSTOM_MODEL_ID, getDefaultModelId, hasModelPicker, ModelPicker } from "./ModelPicker"
import { OcaEmployeeCheck } from "./OcaEmployeeCheck"
import { getProviderLabel } from "./ProviderPicker"
type AuthStep =
| "menu"
@@ -42,14 +46,13 @@ type AuthStep =
| "success"
| "error"
| "cline_auth"
| "oca_employee_check"
| "oca_auth"
| "cline_model"
| "openai_codex_auth"
| "bedrock"
| "import"
// Featured models loaded from shared constants
const featuredModels = getAllFeaturedModels()
| "bedrock_custom"
interface AuthViewProps {
controller: any
@@ -75,7 +78,7 @@ const Select: React.FC<{
const [selectedIndex, setSelectedIndex] = useState(0)
useInput(
(input, key) => {
(_, key) => {
if (key.upArrow) {
setSelectedIndex((prev) => (prev > 0 ? prev - 1 : items.length - 1))
} else if (key.downArrow) {
@@ -141,7 +144,11 @@ const TextInput: React.FC<{
return (
<Box>
<Text color="white">{displayValue || placeholder || ""}</Text>
{!displayValue && placeholder ? (
<Text color="gray">e.g. {placeholder}</Text>
) : (
<Text color="white">{displayValue || ""}</Text>
)}
<Text inverse> </Text>
</Box>
)
@@ -149,6 +156,9 @@ const TextInput: React.FC<{
export const AuthView: React.FC<AuthViewProps> = ({ controller, onComplete, onError, onNavigateToWelcome }) => {
const { exit } = useApp()
const providers = useValidProviders()
const [step, setStep] = useState<AuthStep>("menu")
const [selectedProvider, setSelectedProvider] = useState<string>(
StateManager.get().getApiConfiguration().actModeApiProvider ||
@@ -159,7 +169,6 @@ export const AuthView: React.FC<AuthViewProps> = ({ controller, onComplete, onEr
const [modelId, setModelId] = useState("")
const [baseUrl, setBaseUrl] = useState("")
const [errorMessage, setErrorMessage] = useState("")
const [authStatus, setAuthStatus] = useState<string>("")
const [providerSearch, setProviderSearch] = useState("")
const [providerIndex, setProviderIndex] = useState(0)
const [clineModelIndex, setClineModelIndex] = useState(0)
@@ -170,11 +179,14 @@ export const AuthView: React.FC<AuthViewProps> = ({ controller, onComplete, onEr
// OCA auth hook - enabled when step is oca_auth
const handleOcaAuthSuccess = useCallback(async () => {
await applyProviderConfig({ providerId: "oca", controller })
// Fetch OCA models from the API - this sets actModeOcaModelId/planModeOcaModelId in state
await refreshOcaModels(controller, StringRequest.create({ value: "" }))
const stateManager = StateManager.get()
stateManager.setGlobalState("welcomeViewCompleted", true)
await stateManager.flushPendingState()
setSelectedProvider("oca")
setModelId(liteLlmDefaultModelId)
const actModelId = stateManager.getGlobalSettingsKey("actModeOcaModelId") || ""
setModelId(actModelId)
setStep("success")
}, [controller])
@@ -190,11 +202,6 @@ export const AuthView: React.FC<AuthViewProps> = ({ controller, onComplete, onEr
onError: handleOcaAuthError,
})
// Use providers.json order, filtered to exclude CLI-incompatible providers
const sortedProviders = useMemo(() => {
return getProviderOrder().filter((p) => !CLI_EXCLUDED_PROVIDERS.has(p))
}, [])
// Main menu items - conditionally include import options
const mainMenuItems: SelectItem[] = useMemo(() => {
const items: SelectItem[] = [{ label: "Sign in with Cline", value: "cline_auth" }]
@@ -220,15 +227,13 @@ export const AuthView: React.FC<AuthViewProps> = ({ controller, onComplete, onEr
const providerItems: SelectItem[] = useMemo(() => {
const search = providerSearch.toLowerCase()
const filtered = providerSearch
? sortedProviders.filter(
(p) => p.toLowerCase().includes(search) || getProviderLabel(p).toLowerCase().includes(search),
)
: sortedProviders
? providers.filter((p) => p.toLowerCase().includes(search) || getProviderLabel(p).toLowerCase().includes(search))
: providers
return filtered.map((p: string) => ({
label: getProviderLabel(p),
value: p,
}))
}, [sortedProviders, providerSearch])
}, [providers, providerSearch])
// Use shared scrollable list hook for provider windowing
const TOTAL_PROVIDER_ROWS = 8
@@ -249,6 +254,7 @@ export const AuthView: React.FC<AuthViewProps> = ({ controller, onComplete, onEr
}, [])
// Reset provider index when search changes
// biome-ignore lint/correctness/useExhaustiveDependencies: we want to reset here
useEffect(() => {
setProviderIndex(0)
}, [providerSearch])
@@ -274,7 +280,7 @@ export const AuthView: React.FC<AuthViewProps> = ({ controller, onComplete, onEr
return
}
if (authState.user && authState.user.email) {
if (authState.user?.email) {
// Auth succeeded - save configuration and transition to model selection
await applyProviderConfig({ providerId: "cline", controller })
setSelectedProvider("cline")
@@ -323,7 +329,6 @@ export const AuthView: React.FC<AuthViewProps> = ({ controller, onComplete, onEr
const startClineAuth = useCallback(async () => {
try {
setStep("cline_auth")
setAuthStatus("Starting authentication...")
await AuthService.getInstance(controller).createAuthRequest()
} catch (error) {
setErrorMessage(error instanceof Error ? error.message : String(error))
@@ -333,7 +338,6 @@ export const AuthView: React.FC<AuthViewProps> = ({ controller, onComplete, onEr
const startOcaAuth = useCallback(() => {
setStep("oca_auth")
setAuthStatus("Starting authentication...")
initiateOcaAuth()
}, [initiateOcaAuth])
@@ -364,7 +368,8 @@ export const AuthView: React.FC<AuthViewProps> = ({ controller, onComplete, onEr
(value: string) => {
setSelectedProvider(value)
if (value === "oca") {
startOcaAuth()
// Show employee check screen before starting auth
setStep("oca_employee_check")
} else if (value === "openai-codex") {
setStep("openai_codex_auth")
startOpenAiCodexAuth()
@@ -391,6 +396,33 @@ export const AuthView: React.FC<AuthViewProps> = ({ controller, onComplete, onEr
[selectedProvider],
)
// Save custom Bedrock ARN configuration with base model for capability detection
const saveCustomBedrockConfiguration = useCallback(
async (arn: string, baseModelId: string) => {
try {
if (!bedrockConfig) {
throw new Error("Bedrock configuration is missing")
}
await applyBedrockConfig({
bedrockConfig,
modelId: arn,
customModelBaseId: baseModelId,
controller,
})
const stateManager = StateManager.get()
stateManager.setGlobalState("welcomeViewCompleted", true)
await stateManager.flushPendingState()
setStep("success")
} catch (error) {
setErrorMessage(error instanceof Error ? error.message : String(error))
setStep("error")
}
},
[bedrockConfig, controller],
)
const saveConfiguration = useCallback(
async (model: string, base: string) => {
try {
@@ -425,6 +457,12 @@ export const AuthView: React.FC<AuthViewProps> = ({ controller, onComplete, onEr
const handleModelIdSubmit = useCallback(
(value: string) => {
// Intercept "Custom" selection for Bedrock — redirect to custom ARN input flow
if (value === CUSTOM_MODEL_ID && selectedProvider === "bedrock") {
setStep("bedrock_custom")
return
}
if (value.trim()) {
setModelId(value)
}
@@ -532,6 +570,9 @@ export const AuthView: React.FC<AuthViewProps> = ({ controller, onComplete, onEr
// Go back to cline_model if we came from there (Cline provider)
if (selectedProvider === "cline") {
setStep("cline_model")
} else if (selectedProvider === "bedrock") {
// Bedrock skips the API key step — go back to Bedrock setup
setStep("bedrock")
} else {
setStep("apikey")
}
@@ -540,9 +581,12 @@ export const AuthView: React.FC<AuthViewProps> = ({ controller, onComplete, onEr
setBaseUrl("")
setStep("modelid")
break
case "oca_auth":
case "oca_employee_check":
setStep("provider")
break
case "oca_auth":
setStep("oca_employee_check")
break
case "cline_auth":
setStep("menu")
break
@@ -645,7 +689,7 @@ export const AuthView: React.FC<AuthViewProps> = ({ controller, onComplete, onEr
<Box flexDirection="column">
<Text color="white">Model ID</Text>
<Text> </Text>
<Text color="gray">e.g., claude-sonnet-4-20250514, gpt-4o</Text>
<Text color="gray">e.g., claude-sonnet-4-6, gpt-4o</Text>
<Text> </Text>
<TextInput onChange={setModelId} onSubmit={handleModelIdSubmit} placeholder="model-id" value={modelId} />
<Text> </Text>
@@ -681,6 +725,9 @@ export const AuthView: React.FC<AuthViewProps> = ({ controller, onComplete, onEr
</Box>
)
case "oca_employee_check":
return <OcaEmployeeCheck isActive={step === "oca_employee_check"} onCancel={goBack} onSignIn={startOcaAuth} />
case "oca_auth":
case "cline_auth":
return (
@@ -737,6 +784,18 @@ export const AuthView: React.FC<AuthViewProps> = ({ controller, onComplete, onEr
/>
)
case "bedrock_custom":
return (
<BedrockCustomModelFlow
isActive={step === "bedrock_custom"}
onCancel={() => setStep("modelid")}
onComplete={(arn, baseModelId) => {
setStep("saving")
saveCustomBedrockConfiguration(arn, baseModelId)
}}
/>
)
case "import":
if (!importSource) {
return null
@@ -766,6 +825,7 @@ export const AuthView: React.FC<AuthViewProps> = ({ controller, onComplete, onEr
const [menuIndex, setMenuIndex] = useState(0)
// Steps that allow going back with escape (apikey handled by ApiKeyInput component)
// OcaEmployeeCheck handles its own escape key, so oca_employee_check is not in this list
const canGoBack = [
"provider",
"modelid",
@@ -864,7 +924,7 @@ export const AuthView: React.FC<AuthViewProps> = ({ controller, onComplete, onEr
{index === menuIndex ? " " : " "}
{item.label}
</Text>
{item.value === "cline_auth" && <Text color="yellow"> (try Kimi K2.5 free!)</Text>}
{item.value === "cline_auth" && <Text color="yellow"> (try Opus 4.6!)</Text>}
</Text>
</Box>
))}
@@ -0,0 +1,111 @@
/**
* Bedrock Custom Model Flow component
* Two-step flow: ARN/custom model ID input → base model selection for capability detection.
* Used by both AuthView (onboarding) and SettingsPanelContent (/settings).
*/
import { Box, Text, useInput } from "ink"
// biome-ignore lint/correctness/noUnusedImports: React is needed for JSX at runtime
import React, { useCallback, useState } from "react"
import { COLORS } from "../constants/colors"
import { useStdinContext } from "../context/StdinContext"
import { getModelList } from "./ModelPicker"
import { SearchableList } from "./SearchableList"
type FlowStep = "arn_input" | "base_model"
interface BedrockCustomModelFlowProps {
/** Whether this component should capture keyboard input */
isActive: boolean
/** Called when the user completes both steps (ARN + base model selection) */
onComplete: (arn: string, baseModelId: string) => void
/** Called when the user presses Escape on the first step (ARN input) */
onCancel: () => void
}
export const BedrockCustomModelFlow: React.FC<BedrockCustomModelFlowProps> = ({ isActive, onComplete, onCancel }) => {
const { isRawModeSupported } = useStdinContext()
const [step, setStep] = useState<FlowStep>("arn_input")
const [customArn, setCustomArn] = useState("")
const handleArnSubmit = useCallback(() => {
if (customArn.trim()) {
setStep("base_model")
}
}, [customArn])
const handleBaseModelCancel = useCallback(() => {
setStep("arn_input")
}, [])
useInput(
(input, key) => {
if (step === "arn_input") {
if (key.escape) {
onCancel()
} else if (key.return) {
handleArnSubmit()
} else if (key.backspace || key.delete) {
setCustomArn((prev) => prev.slice(0, -1))
} else if (input && !key.ctrl && !key.meta) {
setCustomArn((prev) => prev + input)
}
return
}
if (step === "base_model") {
if (key.escape) {
handleBaseModelCancel()
}
// Other input is handled by SearchableList
}
},
{ isActive: isActive && isRawModeSupported },
)
if (step === "arn_input") {
return (
<Box flexDirection="column">
<Text bold color={COLORS.primaryBlue}>
Custom Model ID
</Text>
<Box marginTop={1}>
<Text color="gray">Enter your Application Inference Profile ARN or custom model ID</Text>
</Box>
<Box marginTop={1}>
{customArn ? (
<Text color="white">{customArn}</Text>
) : (
<Text color="gray">e.g. arn:aws:bedrock:region:account:application-inference-profile/...</Text>
)}
<Text inverse> </Text>
</Box>
<Box marginTop={1}>
<Text color="gray">Enter to continue, Esc to go back</Text>
</Box>
</Box>
)
}
// step === "base_model"
return (
<Box flexDirection="column">
<Text bold color={COLORS.primaryBlue}>
Base Inference Model
</Text>
<Text color="gray">Select the base model your inference profile uses (for capability detection)</Text>
<Box marginTop={1}>
<SearchableList
isActive={isActive && step === "base_model"}
items={getModelList("bedrock").map((id) => ({ id, label: id }))}
onSelect={(item) => {
onComplete(customArn, item.id)
}}
/>
</Box>
<Box marginTop={1}>
<Text color="gray">Type to search, arrows to navigate, Enter to select, Esc to go back</Text>
</Box>
</Box>
)
}
+19 -9
View File
@@ -114,8 +114,11 @@ export const BedrockSetup: React.FC<BedrockSetupProps> = ({ isActive, onComplete
// Filtered regions
const filteredRegions = useMemo(() => {
const search = regionSearch.toLowerCase()
return search ? AWS_REGIONS.filter((r) => r.includes(search)) : AWS_REGIONS
const search = regionSearch.toLowerCase().trim()
if (!search) {
return AWS_REGIONS
}
return AWS_REGIONS.filter((r) => r.toLowerCase().includes(search))
}, [regionSearch])
const {
@@ -170,10 +173,18 @@ export const BedrockSetup: React.FC<BedrockSetupProps> = ({ isActive, onComplete
}
}, [step, authMethod, onCancel])
const getSelectedRegion = useCallback(() => {
if (filteredRegions.length > 0 && regionIndex >= 0 && regionIndex < filteredRegions.length) {
return filteredRegions[regionIndex]
}
// If no matches, use the search term as custom region
return regionSearch.trim() || "us-east-1"
}, [filteredRegions, regionIndex, regionSearch])
const finish = useCallback(() => {
const config: BedrockConfig = {
awsAuthentication: authMethod === "default" ? "credentials" : authMethod,
awsRegion: filteredRegions[regionIndex] || "us-east-1",
awsRegion: getSelectedRegion(),
awsUseCrossRegionInference: crossRegion,
}
if (authMethod === "profile") {
@@ -184,7 +195,7 @@ export const BedrockSetup: React.FC<BedrockSetupProps> = ({ isActive, onComplete
if (sessionToken) config.awsSessionToken = sessionToken
}
onComplete(config)
}, [authMethod, profileName, accessKey, secretKey, sessionToken, filteredRegions, regionIndex, crossRegion, onComplete])
}, [authMethod, profileName, accessKey, secretKey, sessionToken, getSelectedRegion, crossRegion, onComplete])
// Handle input for auth_method, region, and options steps
useInput(
@@ -204,11 +215,11 @@ export const BedrockSetup: React.FC<BedrockSetupProps> = ({ isActive, onComplete
} else if (step === "region") {
if (key.escape) {
goBack()
} else if (key.upArrow) {
} else if (key.upArrow && filteredRegions.length > 0) {
setRegionIndex((prev) => (prev > 0 ? prev - 1 : filteredRegions.length - 1))
} else if (key.downArrow) {
} else if (key.downArrow && filteredRegions.length > 0) {
setRegionIndex((prev) => (prev < filteredRegions.length - 1 ? prev + 1 : 0))
} else if (key.return && filteredRegions.length > 0) {
} else if (key.return && (filteredRegions.length > 0 || regionSearch.trim())) {
setStep("options")
} else if (key.backspace || key.delete) {
setRegionSearch((prev) => prev.slice(0, -1))
@@ -330,7 +341,7 @@ export const BedrockSetup: React.FC<BedrockSetupProps> = ({ isActive, onComplete
<Text color="white">AWS Region</Text>
<Text> </Text>
<Box>
<Text color="gray">Search: </Text>
<Text color="gray">Search or enter custom region: </Text>
<Text color="white">{regionSearch}</Text>
<Text inverse> </Text>
</Box>
@@ -350,7 +361,6 @@ export const BedrockSetup: React.FC<BedrockSetupProps> = ({ isActive, onComplete
{showRegionBottom && (
<Text color="gray">... {filteredRegions.length - regionVisibleStart - regionVisibleCount} more below</Text>
)}
{filteredRegions.length === 0 && <Text color="gray">No regions match "{regionSearch}"</Text>}
<Text> </Text>
<Text color="gray">Type to search, arrows to navigate, Enter to select, Esc to go back</Text>
</Box>
+106
View File
@@ -0,0 +1,106 @@
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 subagent rendering", () => {
it("renders subagent approval prompts as a tree", () => {
const message: ClineMessage = {
ts: Date.now(),
type: "ask",
ask: "use_subagents",
text: JSON.stringify({
prompts: [
"Find codebase stats and size",
"Find funny comments and easter eggs",
"Find unusual patterns and history",
],
}),
}
const { lastFrame } = render(React.createElement(ChatMessage, { message, mode: "act" }))
const frame = lastFrame() || ""
expect(frame).toContain("Cline wants to run subagents")
expect(frame).toContain("├─ Find codebase stats and size")
expect(frame).toContain("├─ Find funny comments and easter eggs")
expect(frame).toContain("└─ Find unusual patterns and history")
})
it("renders subagent progress rows with compact token stats and completion checks", () => {
const message: ClineMessage = {
ts: Date.now(),
type: "say",
say: "subagent",
text: JSON.stringify({
status: "running",
total: 3,
completed: 1,
successes: 1,
failures: 0,
toolCalls: 21,
inputTokens: 0,
outputTokens: 0,
contextWindow: 0,
maxContextTokens: 0,
maxContextUsagePercentage: 0,
items: [
{
index: 1,
prompt: "Find codebase stats and size",
status: "completed",
toolCalls: 5,
inputTokens: 0,
outputTokens: 0,
totalCost: 0.034,
contextTokens: 24400,
contextWindow: 200000,
contextUsagePercentage: 12.2,
},
{
index: 2,
prompt: "Find funny comments and easter eggs",
status: "running",
toolCalls: 11,
inputTokens: 0,
outputTokens: 0,
totalCost: 0.056,
contextTokens: 31600,
contextWindow: 200000,
contextUsagePercentage: 15.8,
},
{
index: 3,
prompt: "Find unusual patterns and history",
status: "pending",
toolCalls: 5,
inputTokens: 0,
outputTokens: 0,
totalCost: 0,
contextTokens: 28900,
contextWindow: 200000,
contextUsagePercentage: 14.4,
},
],
}),
}
const { lastFrame } = render(React.createElement(ChatMessage, { isStreaming: true, message, mode: "act" }))
const frame = lastFrame() || ""
expect(frame).toContain("Cline is running subagents")
expect(frame).toContain("✓ Find codebase stats and size")
expect(frame).toContain("5 tool uses · 24.4k tokens · $0.03")
expect(frame).toContain("11 tool uses · 31.6k tokens · $0.06")
expect(frame).toContain("5 tool uses · 28.9k tokens · $0.00")
})
})
+16 -7
View File
@@ -17,6 +17,7 @@ import { useTerminalSize } from "../hooks/useTerminalSize"
import { jsonParseSafe } from "../utils/parser"
import { getToolDescription, isFileEditTool, parseToolFromMessage } from "../utils/tools"
import { DiffView } from "./DiffView"
import { SubagentMessage } from "./SubagentMessage"
/**
* Add "(Tab)" hint after "Act mode" mentions.
@@ -24,7 +25,7 @@ import { DiffView } from "./DiffView"
* 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): React.ReactNode[] {
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)
@@ -41,7 +42,7 @@ function addActModeHint(text: string): React.ReactNode[] {
}
if (matches[i]) {
nodes.push(
<React.Fragment key={`act-mode-${i}`}>
<React.Fragment key={`${keyPrefix}-act-mode-${i}`}>
{matches[i]}
<Text color="gray"> (Tab)</Text>
</React.Fragment>,
@@ -59,6 +60,8 @@ function addActModeHint(text: string): React.ReactNode[] {
*/
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
@@ -68,7 +71,7 @@ function renderInlineMarkdown(text: string): React.ReactNode[] {
// Add text before match (with Act Mode hint processing)
if (match.index > lastIndex) {
const beforeText = text.slice(lastIndex, match.index)
nodes.push(...addActModeHint(beforeText))
nodes.push(...addHintedText(beforeText))
}
const fullMatch = match[0]
@@ -77,7 +80,7 @@ function renderInlineMarkdown(text: string): React.ReactNode[] {
if (fullMatch.startsWith("**") && fullMatch.endsWith("**")) {
// Bold - also process for Act Mode hints inside bold text
const boldContent = fullMatch.slice(2, -2)
const hintedContent = addActModeHint(boldContent)
const hintedContent = addHintedText(boldContent)
nodes.push(
<Text bold key={key}>
{hintedContent}
@@ -100,10 +103,10 @@ function renderInlineMarkdown(text: string): React.ReactNode[] {
// Add remaining text (with Act Mode hint processing)
if (lastIndex < text.length) {
nodes.push(...addActModeHint(text.slice(lastIndex)))
nodes.push(...addHintedText(text.slice(lastIndex)))
}
return nodes.length > 0 ? nodes : addActModeHint(text)
return nodes.length > 0 ? nodes : addHintedText(text)
}
/**
@@ -224,7 +227,7 @@ function truncate(text: string, maxLength: number): string {
/**
* Format tool result for display
*/
function formatToolResult(result: string, maxLines: number = 5): string[] {
function formatToolResult(result: string, maxLines = 5): string[] {
const lines = result.split("\n")
if (lines.length <= maxLines) {
return lines
@@ -446,6 +449,10 @@ export const ChatMessage: React.FC<ChatMessageProps> = ({ message, mode, isStrea
)
}
if ((type === "ask" && ask === "use_subagents") || say === "use_subagents" || say === "subagent") {
return <SubagentMessage isStreaming={isStreaming} message={message} mode={mode} />
}
// MCP response
if (say === "mcp_server_response" && text) {
const lines = formatToolResult(text, 8)
@@ -800,6 +807,8 @@ export const ChatMessageList: React.FC<ChatMessageListProps> = ({ messages, maxM
const displayMessages = messages.filter((m) => {
// Skip api_req_finished, they're just markers
if (m.say === "api_req_finished") return false
// Skip hidden aggregated usage messages
if (m.say === "subagent_usage") return false
// Skip empty text messages
if (m.say === "text" && !m.text?.trim()) return false
// Skip checkpoint messages
+110 -8
View File
@@ -138,6 +138,7 @@ import {
import { isMouseEscapeSequence } from "../utils/input"
import { jsonParseSafe, parseImagesFromInput } from "../utils/parser"
import { extractSlashQuery, filterCommands, insertSlashCommand, sortCommandsWorkflowsFirst } from "../utils/slash-commands"
import { waitFor } from "../utils/timeout"
import { isFileEditTool, parseToolFromMessage } from "../utils/tools"
import { shutdownEvent } from "../vscode-shim"
import { ActionButtons, type ButtonActionType, getButtonConfig, getVisibleButtons } from "./ActionButtons"
@@ -149,9 +150,28 @@ import { HighlightedInput } from "./HighlightedInput"
import { HistoryPanelContent } from "./HistoryPanelContent"
import { providerModels } from "./ModelPicker"
import { SettingsPanelContent } from "./SettingsPanelContent"
import { SkillsPanelContent } from "./SkillsPanelContent"
import { SlashCommandMenu } from "./SlashCommandMenu"
import { ThinkingIndicator } from "./ThinkingIndicator"
/**
* Persistent input storage that survives React remounts (e.g., during terminal resize).
* Keyed by a stable identifier so each task/session maintains its own input state.
*/
interface PersistedInputState {
text: string
cursorPos: number
pastedTexts: Map<number, string>
pasteCounter: number
}
const inputStateStorage = new Map<string, PersistedInputState>()
function getInputStorageKey(controller: any, taskId?: string): string {
// Use taskId if available, otherwise fall back to controller instance
return taskId || (controller?.task?.taskId ?? "default")
}
interface ChatViewProps {
controller?: any
onExit?: () => void
@@ -350,6 +370,9 @@ export const ChatView: React.FC<ChatViewProps> = ({
insertText: insertTextAtCursor,
} = useTextInput()
// Get storage key for persisting input across remounts
const storageKey = useMemo(() => getInputStorageKey(ctrl, taskId), [ctrl, taskId])
// Refs for text input and cursor position (used by useHomeEndKeys and to avoid stale closures in useInput)
const textInputRef = useRef(textInput)
textInputRef.current = textInput
@@ -366,8 +389,10 @@ export const ChatView: React.FC<ChatViewProps> = ({
const [userScrolled, setUserScrolled] = useState(false)
// Pasted text storage - maps placeholder number to full pasted content
const [pastedTexts, setPastedTexts] = useState<Map<number, string>>(new Map())
const pasteCounterRef = useRef(0)
const [pastedTexts, setPastedTexts] = useState<Map<number, string>>(() => {
return inputStateStorage.get(storageKey)?.pastedTexts ?? new Map()
})
const pasteCounterRef = useRef<number>(inputStateStorage.get(storageKey)?.pasteCounter ?? 0)
// Track paste timing to combine chunks that arrive in rapid succession
const lastPasteTimeRef = useRef<number>(0)
const activePasteNumRef = useRef<number>(0)
@@ -388,6 +413,7 @@ export const ChatView: React.FC<ChatViewProps> = ({
| { type: "settings"; initialMode?: "model-picker" | "featured-models"; initialModelKey?: "actModelId" | "planModelId" }
| { type: "history" }
| { type: "help" }
| { type: "skills" }
| null
>(null)
@@ -401,6 +427,29 @@ export const ChatView: React.FC<ChatViewProps> = ({
// Track when we're exiting to hide UI elements before exit
const [isExiting, setIsExiting] = useState(false)
// Restore input state from storage on mount (after resize remount)
useEffect(() => {
const stored = inputStateStorage.get(storageKey)
if (stored) {
setTextInput(stored.text)
setCursorPos(stored.cursorPos)
setPastedTexts(stored.pastedTexts)
pasteCounterRef.current = stored.pasteCounter
}
}, [storageKey, setTextInput, setCursorPos])
// Persist input state to storage whenever it changes (survives remount)
useEffect(() => {
if (textInput || pastedTexts.size > 0) {
inputStateStorage.set(storageKey, {
text: textInput,
cursorPos,
pastedTexts: new Map(pastedTexts),
pasteCounter: pasteCounterRef.current,
})
}
}, [storageKey, textInput, cursorPos, pastedTexts])
// Task switch handling: when switching tasks via /history, we clear the terminal and
// increment a counter used as the root Box's key. This forces React to remount the tree,
// giving us a fresh Static instance. Mirrors how App.tsx handles resize with resizeKey.
@@ -493,12 +542,14 @@ export const ChatView: React.FC<ChatViewProps> = ({
clearState() // Force clear React state (bypasses empty messages check)
setTextInput("")
setCursorPos(0)
// Clear persisted state
inputStateStorage.delete(storageKey)
// Post the now-empty state
if (ctrl) {
ctrl.postStateToWebview()
}
}, [ctrl, clearState])
}, [ctrl, clearState, storageKey])
const refs = useRef({
searchTimeout: null as NodeJS.Timeout | null,
@@ -759,6 +810,8 @@ export const ChatView: React.FC<ChatViewProps> = ({
setCursorPos(0)
setPastedTexts(new Map()) // Clear stored pastes
pasteCounterRef.current = 0
// Clear persisted state
inputStateStorage.delete(storageKey)
try {
await ctrl.task.handleWebviewAskResponse(responseType, expandedText)
@@ -766,7 +819,7 @@ export const ChatView: React.FC<ChatViewProps> = ({
// Controller may be disposed
}
},
[ctrl, pendingAsk, pastedTexts],
[ctrl, pendingAsk, pastedTexts, storageKey],
)
// Handle cancel/interrupt
@@ -857,6 +910,8 @@ export const ChatView: React.FC<ChatViewProps> = ({
setCursorPos(0)
setPastedTexts(new Map()) // Clear stored pastes
pasteCounterRef.current = 0
// Clear persisted state
inputStateStorage.delete(storageKey)
try {
// Convert image paths to data URLs if needed
@@ -884,10 +939,12 @@ export const ChatView: React.FC<ChatViewProps> = ({
onError?.()
}
},
[ctrl, onError, pastedTexts],
[ctrl, onError, pastedTexts, storageKey],
)
// Auto-submit initial prompt if provided
// When taskId is also provided, this sends the prompt to resume the existing task
// When no taskId, this creates a new task with the prompt
useEffect(() => {
const autoSubmit = async () => {
if (!initialPrompt && (!initialImages || initialImages.length === 0)) {
@@ -908,8 +965,32 @@ export const ChatView: React.FC<ChatViewProps> = ({
if (initialPrompt) {
setTerminalTitle(initialPrompt)
}
// initialImages are already data URLs from index.ts processing
await ctrl.initTask(initialPrompt || "", initialImages && initialImages.length > 0 ? initialImages : undefined)
if (taskId) {
// Resuming an existing task with a prompt - wait for task to load first
// The task loading happens in the other useEffect via showTaskWithId
// We need to wait for it to complete before sending the resume message
const task = await waitFor(() => ctrl.task, 5000)
if (task) {
// Send the prompt as a message to resume the task
await task.handleWebviewAskResponse("messageResponse", initialPrompt || "")
} else {
// Task failed to load, fall back to creating new task
Logger.error(`Failed to load task ${taskId} for resume, creating new task instead`)
await ctrl.initTask(
initialPrompt || "",
initialImages && initialImages.length > 0 ? initialImages : undefined,
)
}
} else {
// New task - use initTask
// initialImages are already data URLs from index.ts processing
await ctrl.initTask(
initialPrompt || "",
initialImages && initialImages.length > 0 ? initialImages : undefined,
)
}
} catch (_error) {
onError?.()
}
@@ -1077,13 +1158,21 @@ export const ChatView: React.FC<ChatViewProps> = ({
setSlashMenuDismissed(true)
return
}
if (cmd.name === "skills") {
setActivePanel({ type: "skills" })
setTextInput("")
setCursorPos(0)
setSelectedSlashIndex(0)
setSlashMenuDismissed(true)
return
}
if (cmd.name === "clear") {
clearViewAndResetTask()
setSelectedSlashIndex(0)
setSlashMenuDismissed(true)
return
}
if (cmd.name === "exit") {
if (cmd.name === "exit" || cmd.name === "q") {
handleExit()
return
}
@@ -1466,6 +1555,19 @@ export const ChatView: React.FC<ChatViewProps> = ({
{/* Help panel */}
{activePanel?.type === "help" && <HelpPanelContent onClose={() => setActivePanel(null)} />}
{/* Skills panel */}
{activePanel?.type === "skills" && ctrl && (
<SkillsPanelContent
controller={ctrl}
onClose={() => setActivePanel(null)}
onUseSkill={(skillPath) => {
setActivePanel(null)
setTextInput(`@${skillPath} `)
setCursorPos(skillPath.length + 2)
}}
/>
)}
{/* Slash command menu - below input (takes priority over file menu) */}
{showSlashMenu && !activePanel && (
<Box paddingLeft={1} paddingRight={1}>
+124 -18
View File
@@ -13,6 +13,7 @@ import {
import { Box, Text, useApp, useInput } from "ink"
import React, { useMemo, useState } from "react"
import { useStdinContext } from "../context/StdinContext"
import { fuzzyFilter } from "../utils/fuzzy-search"
import {
BooleanSelect,
buildConfigEntries,
@@ -21,6 +22,8 @@ import {
HookInfo,
HookRow,
MAX_VISIBLE,
ObjectEditorPanel,
ObjectEditorState,
parseValue,
SEPARATOR,
SectionHeader,
@@ -105,6 +108,8 @@ export const ConfigView: React.FC<ConfigViewProps> = ({
const [isEditing, setIsEditing] = useState(false)
const [selectedIndex, setSelectedIndex] = useState(0)
const [editValue, setEditValue] = useState("")
const [searchQuery, setSearchQuery] = useState("")
const [objectEditor, setObjectEditor] = useState<ObjectEditorState | null>(null)
// Build entries for settings tab
const configEntries = useMemo(
@@ -112,6 +117,13 @@ export const ConfigView: React.FC<ConfigViewProps> = ({
[globalState, workspaceState],
)
const filteredConfigEntries = useMemo(() => {
if (!searchQuery.trim()) {
return configEntries
}
return fuzzyFilter(configEntries, searchQuery, (entry) => `${entry.key} ${String(entry.value ?? "")}`)
}, [configEntries, searchQuery])
// Build entries for rules tab
const ruleEntries = useMemo(() => {
const entries: ToggleEntry[] = []
@@ -159,7 +171,7 @@ export const ConfigView: React.FC<ConfigViewProps> = ({
const currentListLength = useMemo(() => {
switch (currentTab) {
case "settings":
return configEntries.length
return filteredConfigEntries.length
case "rules":
return ruleEntries.length
case "workflows":
@@ -171,7 +183,14 @@ export const ConfigView: React.FC<ConfigViewProps> = ({
default:
return 0
}
}, [currentTab, configEntries.length, ruleEntries.length, workflowEntries.length, hookEntries.length, skillEntries.length])
}, [
currentTab,
filteredConfigEntries.length,
ruleEntries.length,
workflowEntries.length,
hookEntries.length,
skillEntries.length,
])
// Get available tabs
const availableTabs = useMemo(() => {
@@ -191,10 +210,11 @@ export const ConfigView: React.FC<ConfigViewProps> = ({
setCurrentTab(newTab)
setSelectedIndex(0)
setIsEditing(false)
setObjectEditor(null)
}
// Settings tab handlers
const selectedConfigEntry = configEntries[selectedIndex]
const selectedConfigEntry = filteredConfigEntries[selectedIndex]
const handleSettingsSave = (value: string | boolean) => {
if (!selectedConfigEntry) {
@@ -210,6 +230,43 @@ export const ConfigView: React.FC<ConfigViewProps> = ({
setIsEditing(false)
}
const getObjectAtPath = (root: Record<string, unknown>, path: string[]): Record<string, unknown> => {
let current: unknown = root
for (const segment of path) {
if (!current || typeof current !== "object") {
return {}
}
current = (current as Record<string, unknown>)[segment]
}
return current && typeof current === "object" ? (current as Record<string, unknown>) : {}
}
const setObjectValueAtPath = (
root: Record<string, unknown>,
path: string[],
key: string,
value: unknown,
): Record<string, unknown> => {
if (path.length === 0) {
return { ...root, [key]: value }
}
const [head, ...rest] = path
const child = root[head]
const childObj = child && typeof child === "object" ? (child as Record<string, unknown>) : {}
return {
...root,
[head]: setObjectValueAtPath(childObj, rest, key, value),
}
}
const persistObjectEditor = (nextObject: Record<string, unknown>, source: "global" | "workspace", key: string) => {
if (source === "global" && onUpdateGlobal) {
onUpdateGlobal(key as GlobalStateAndSettingsKey, nextObject as never)
} else if (source === "workspace" && onUpdateWorkspace) {
onUpdateWorkspace(key as LocalStateKey, nextObject as never)
}
}
const handleSettingsReset = () => {
if (!selectedConfigEntry?.isEditable || selectedConfigEntry.source !== "global") {
return
@@ -240,15 +297,22 @@ export const ConfigView: React.FC<ConfigViewProps> = ({
// Input handling
useInput(
(input, key) => {
if (input.toLowerCase() === "q" || key.escape) {
if (objectEditor) {
return
}
if (key.escape) {
exit()
}
// Tab navigation with Tab key or number keys
if (key.tab || (input >= "1" && input <= "5")) {
const targetIdx = key.tab
? (availableTabs.findIndex((t) => t.key === currentTab) + 1) % availableTabs.length
: parseInt(input) - 1
if (key.leftArrow || key.rightArrow || (input >= "1" && input <= "5")) {
const currentTabIndex = availableTabs.findIndex((t) => t.key === currentTab)
const targetIdx =
input >= "1" && input <= "5"
? Number.parseInt(input) - 1
: key.leftArrow
? (currentTabIndex - 1 + availableTabs.length) % availableTabs.length
: (currentTabIndex + 1) % availableTabs.length
if (targetIdx >= 0 && targetIdx < availableTabs.length) {
handleTabChange(availableTabs[targetIdx].key)
}
@@ -256,21 +320,45 @@ export const ConfigView: React.FC<ConfigViewProps> = ({
}
// List navigation (arrow keys and vim-style j/k)
if (key.upArrow || input === "k") {
if (key.upArrow) {
setSelectedIndex((i) => (i > 0 ? i - 1 : currentListLength - 1))
} else if (key.downArrow || input === "j") {
} else if (key.downArrow) {
setSelectedIndex((i) => (i < currentListLength - 1 ? i + 1 : 0))
}
// Tab-specific actions
if (currentTab === "settings") {
if ((key.return || input === "e") && selectedConfigEntry?.isEditable) {
if ((key.return || key.tab) && selectedConfigEntry?.isEditable) {
if (selectedConfigEntry.type === "boolean") {
handleSettingsSave(!selectedConfigEntry.value)
return
}
if (selectedConfigEntry.type === "object") {
const value =
selectedConfigEntry.value && typeof selectedConfigEntry.value === "object"
? (selectedConfigEntry.value as Record<string, unknown>)
: {}
setObjectEditor({
source: selectedConfigEntry.source,
key: selectedConfigEntry.key,
path: [],
value,
selectedIndex: 0,
isEditingValue: false,
editValue: "",
})
return
}
setEditValue(selectedConfigEntry.value !== undefined ? String(selectedConfigEntry.value) : "")
setIsEditing(true)
} else if (input === "r") {
} else if (key.ctrl && input.toLowerCase() === "r") {
handleSettingsReset()
} else if (key.backspace || key.delete) {
setSearchQuery((prev) => prev.slice(0, -1))
} else if (input && !key.ctrl && !key.meta && !key.escape && !key.upArrow && !key.downArrow) {
setSearchQuery((prev) => prev + input)
}
} else if (key.return || input === " ") {
} else if (key.return || key.tab || input === " ") {
// Toggle for rules/workflows/hooks/skills
handleToggle()
}
@@ -338,13 +426,31 @@ export const ConfigView: React.FC<ConfigViewProps> = ({
)
}
if (objectEditor && currentTab === "settings") {
return (
<ObjectEditorPanel
getObjectAtPath={getObjectAtPath}
onClose={() => setObjectEditor(null)}
onPersist={(nextObject) => persistObjectEditor(nextObject, objectEditor.source, objectEditor.key)}
setObjectValueAtPath={setObjectValueAtPath}
setState={setObjectEditor}
state={objectEditor}
/>
)
}
// Render tab content
const renderTabContent = () => {
switch (currentTab) {
case "settings": {
const visibleEntries = configEntries.slice(startIndex, startIndex + MAX_VISIBLE)
const visibleEntries = filteredConfigEntries.slice(startIndex, startIndex + MAX_VISIBLE)
return (
<React.Fragment>
<Box>
<Text>Search: </Text>
<Text color="white">{searchQuery}</Text>
<Text inverse> </Text>
</Box>
<Box>
<Text>Data directory: </Text>
<Text color="blue" underline>
@@ -507,12 +613,12 @@ export const ConfigView: React.FC<ConfigViewProps> = ({
// Help text based on current tab
const getHelpText = () => {
const base = "↑/↓/j/k Navigate • Tab/1-5 Switch tabs • q/Esc Exit"
const base = "↑/↓ Navigate • ←/→ tabs • 1-5 tabs • Esc Exit"
if (currentTab === "settings") {
return `${base} • Enter/e Edit • r Reset`
return `${base} Type to search • Enter/Tab Edit (booleans toggle) • Backspace clear search • Ctrl+R Reset`
}
const openFolder = onOpenFolder ? " • o Open folder" : ""
return `${base} • Enter/Space Toggle${openFolder}`
return `${base} • Enter/Tab/Space Toggle${openFolder}`
}
return (
+180 -11
View File
@@ -46,16 +46,19 @@ export interface SkillInfo {
enabled: boolean
}
export const EXCLUDED_KEYS = new Set([
"taskHistory",
"primaryRootIndex",
"subagentsEnabled",
"subagentTerminalOutputLineLimit",
"welcomeViewCompleted",
"isNewUser",
])
export interface ObjectEditorState {
source: "global" | "workspace"
key: string
path: string[]
value: Record<string, unknown>
selectedIndex: number
isEditingValue: boolean
editValue: string
}
export const EDITABLE_TYPES: Set<ValueType> = new Set(["string", "number", "boolean"])
export const EXCLUDED_KEYS = new Set(["taskHistory", "primaryRootIndex", "welcomeViewCompleted", "isNewUser"])
export const EDITABLE_TYPES: Set<ValueType> = new Set(["string", "number", "boolean", "object"])
export const MAX_VISIBLE = 12
export const SEPARATOR = "─".repeat(80)
@@ -135,7 +138,7 @@ export function parseValue(input: string, type: ValueType): unknown {
return input.toLowerCase() === "true" || input === "1"
}
if (type === "number") {
const num = parseFloat(input)
const num = Number.parseFloat(input)
return Number.isNaN(num) ? 0 : num
}
if (type === "object") {
@@ -217,7 +220,7 @@ export const TextInput: React.FC<TextInputProps> = ({ label, onChange, onCancel,
</Text>
<Box>
<Text color="white">{value}</Text>
<Text inverse> </Text>
<Text color="cyan">|</Text>
</Box>
<Text color="gray">Type: {type} Enter to save Esc to cancel</Text>
</Box>
@@ -379,3 +382,169 @@ export const SectionHeader: React.FC<{ title: string }> = ({ title }) => (
</Text>
</Box>
)
interface ObjectEditorPanelProps {
state: ObjectEditorState
setState: React.Dispatch<React.SetStateAction<ObjectEditorState | null>>
onClose: () => void
onPersist: (nextObject: Record<string, unknown>) => void
getObjectAtPath: (root: Record<string, unknown>, path: string[]) => Record<string, unknown>
setObjectValueAtPath: (root: Record<string, unknown>, path: string[], key: string, value: unknown) => Record<string, unknown>
}
export const ObjectEditorPanel: React.FC<ObjectEditorPanelProps> = ({
state,
setState,
onClose,
onPersist,
getObjectAtPath,
setObjectValueAtPath,
}) => {
const { isRawModeSupported } = useStdinContext()
const currentNode = getObjectAtPath(state.value, state.path)
const objectEntries = Object.entries(currentNode).sort(([a], [b]) => a.localeCompare(b))
const selectedEntry = objectEntries[state.selectedIndex]
const breadcrumb = [state.key, ...state.path].join(" ")
useInput(
(input, key) => {
if (state.isEditingValue) {
if (key.escape) {
setState((prev) => (prev ? { ...prev, isEditingValue: false, editValue: "" } : prev))
return
}
if (key.return) {
if (!selectedEntry) {
setState((prev) => (prev ? { ...prev, isEditingValue: false, editValue: "" } : prev))
return
}
const [entryKey, entryValue] = selectedEntry
let parsed: unknown = state.editValue
if (typeof entryValue === "boolean") {
parsed = state.editValue.toLowerCase() === "true" || state.editValue === "1"
} else if (typeof entryValue === "number") {
const maybeNum = Number(state.editValue)
parsed = Number.isNaN(maybeNum) ? 0 : maybeNum
}
const nextObject = setObjectValueAtPath(state.value, state.path, entryKey, parsed)
onPersist(nextObject)
setState((prev) => (prev ? { ...prev, value: nextObject, isEditingValue: false, editValue: "" } : prev))
return
}
if (key.backspace || key.delete) {
setState((prev) => (prev ? { ...prev, editValue: prev.editValue.slice(0, -1) } : prev))
return
}
if (input && !key.ctrl && !key.meta) {
setState((prev) => (prev ? { ...prev, editValue: prev.editValue + input } : prev))
}
return
}
if (key.escape) {
if (state.path.length > 0) {
setState((prev) => (prev ? { ...prev, path: prev.path.slice(0, -1), selectedIndex: 0 } : prev))
} else {
onClose()
}
return
}
if (key.upArrow || input === "k") {
setState((prev) =>
prev
? {
...prev,
selectedIndex:
objectEntries.length > 0
? prev.selectedIndex > 0
? prev.selectedIndex - 1
: objectEntries.length - 1
: 0,
}
: prev,
)
return
}
if (key.downArrow || input === "j") {
setState((prev) =>
prev
? {
...prev,
selectedIndex:
objectEntries.length > 0
? prev.selectedIndex < objectEntries.length - 1
? prev.selectedIndex + 1
: 0
: 0,
}
: prev,
)
return
}
if (key.return || key.tab) {
if (!selectedEntry) {
return
}
const [entryKey, entryValue] = selectedEntry
if (typeof entryValue === "boolean") {
const nextObject = setObjectValueAtPath(state.value, state.path, entryKey, !entryValue)
onPersist(nextObject)
setState((prev) => (prev ? { ...prev, value: nextObject } : prev))
return
}
if (entryValue && typeof entryValue === "object" && !Array.isArray(entryValue)) {
setState((prev) => (prev ? { ...prev, path: [...prev.path, entryKey], selectedIndex: 0 } : prev))
return
}
setState((prev) =>
prev
? { ...prev, isEditingValue: true, editValue: entryValue !== undefined ? String(entryValue) : "" }
: prev,
)
}
},
{ isActive: isRawModeSupported },
)
return (
<Box flexDirection="column">
<Text bold color="white">
Edit Nested Object
</Text>
<Text color="gray">{SEPARATOR}</Text>
<Text color="cyan">{breadcrumb}</Text>
{state.isEditingValue ? (
<Box flexDirection="column" marginTop={1}>
<Box>
<Text color="white">{state.editValue}</Text>
<Text color="cyan">|</Text>
</Box>
<Text color="gray">Enter to save Esc to cancel</Text>
</Box>
) : (
<Box flexDirection="column" marginTop={1}>
{objectEntries.length === 0 ? (
<Text color="gray">No nested keys at this level.</Text>
) : (
objectEntries.map(([key, value], idx) => {
const isSelected = idx === state.selectedIndex
const valueText =
value && typeof value === "object" && !Array.isArray(value) ? "{...}" : String(value)
return (
<Text color={isSelected ? "cyan" : undefined} key={key}>
{isSelected ? " " : " "}
<Text color="cyan">{key}</Text>
<Text color="gray">: </Text>
<Text color="white">{valueText}</Text>
</Text>
)
})
)}
<Text color="gray">/ Navigate Enter/Tab Edit or drill in Esc Back/Close</Text>
</Box>
)}
</Box>
)
}
+7 -7
View File
@@ -39,21 +39,21 @@ export const FeaturedModelPicker: React.FC<FeaturedModelPickerProps> = ({
const isSelected = i === selectedIndex
return (
<Box flexDirection="column" key={model.id} marginBottom={1}>
<Box flexDirection="column" key={`${model.id}-${model.labels[0] || "default"}`} marginBottom={1}>
<Box>
<Text color={isSelected ? COLORS.primaryBlue : undefined}>{isSelected ? " " : " "}</Text>
<Text bold color={isSelected ? COLORS.primaryBlue : "white"}>
{model.name}
</Text>
{model.label && (
<Text>
{model.labels.map((label) => (
<Text key={label}>
<Text> </Text>
<Text backgroundColor={model.label === "FREE" ? "gray" : COLORS.primaryBlue} color="black">
<Text backgroundColor={label === "FREE" ? "gray" : COLORS.primaryBlue} color="black">
{" "}
{model.label}{" "}
{label}{" "}
</Text>
</Text>
)}
))}
</Box>
<Box paddingLeft={2}>
<Text color="gray">{model.description}</Text>
@@ -81,7 +81,7 @@ 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: boolean = true): number {
export function getFeaturedModelMaxIndex(showBrowseAll = true): number {
const featuredModels = getAllFeaturedModels()
return showBrowseAll ? featuredModels.length : featuredModels.length - 1
}
+28
View File
@@ -43,6 +43,30 @@ export const HelpPanelContent: React.FC<HelpPanelContentProps> = ({ onClose }) =
</Text>
</Box>
<Box flexDirection="column">
<Text bold>Keyboard Shortcuts</Text>
<Text>
{" "}
<Text color="white">Ctrl+U</Text> - Clear entire input (delete to start)
</Text>
<Text>
{" "}
<Text color="white">Ctrl+K</Text> - Delete from cursor to end
</Text>
<Text>
{" "}
<Text color="white">Ctrl+W</Text> - Delete word backwards
</Text>
<Text>
{" "}
<Text color="white">Ctrl+A / Ctrl+E</Text> - Jump to start / end of input
</Text>
<Text>
{" "}
<Text color="white">Alt/Option+/</Text> - Move by word
</Text>
</Box>
<Box flexDirection="column">
<Text bold>Slash Commands</Text>
<Text>
@@ -64,6 +88,10 @@ export const HelpPanelContent: React.FC<HelpPanelContentProps> = ({ onClose }) =
{" "}
<Text color="white">/clear</Text> - Start a fresh task
</Text>
<Text>
{" "}
<Text color="white">/q</Text> - Quit Cline
</Text>
</Box>
<Text>
+34 -6
View File
@@ -6,6 +6,7 @@
import { Box, Text } from "ink"
import Spinner from "ink-spinner"
import React, { useEffect, useMemo, useState } from "react"
import { refreshOcaModels } from "@/core/controller/models/refreshOcaModels"
import { refreshOpenRouterModels } from "@/core/controller/models/refreshOpenRouterModels"
import {
type ApiProvider,
@@ -64,11 +65,15 @@ import {
xaiDefaultModelId,
xaiModels,
} from "@/shared/api"
import { StringRequest } from "@/shared/proto/cline/common"
import { filterOpenRouterModelIds } from "@/shared/utils/model-filters"
import { COLORS } from "../constants/colors"
import { getOpenRouterDefaultModelId, usesOpenRouterModels } from "../utils/openrouter-models"
import { SearchableList, SearchableListItem } from "./SearchableList"
// Special ID used to indicate the user wants to enter a custom model ID / ARN
export const CUSTOM_MODEL_ID = "__custom__"
// Map providers to their static model lists and defaults
export const providerModels: Record<string, { models: Record<string, unknown>; defaultId: string }> = {
anthropic: { models: anthropicModels, defaultId: anthropicDefaultModelId },
@@ -105,7 +110,7 @@ export function hasStaticModels(provider: string): boolean {
}
export function hasModelPicker(provider: string): boolean {
return hasStaticModels(provider) || usesOpenRouterModels(provider)
return hasStaticModels(provider) || usesOpenRouterModels(provider) || provider === "oca"
}
export function getDefaultModelId(provider: string): string {
@@ -132,7 +137,7 @@ export const ModelPicker: React.FC<ModelPickerProps> = ({ provider, controller,
const [isLoading, setIsLoading] = useState(false)
const [asyncModels, setAsyncModels] = useState<string[]>([])
// Fetch OpenRouter models when needed using shared core function
// Fetch async models (OpenRouter or OCA) when needed
useEffect(() => {
if (usesOpenRouterModels(provider)) {
setIsLoading(true)
@@ -145,22 +150,45 @@ export const ModelPicker: React.FC<ModelPickerProps> = ({ provider, controller,
.finally(() => {
setIsLoading(false)
})
} else if (provider === "oca") {
setIsLoading(true)
refreshOcaModels(controller, StringRequest.create({ value: "" }))
.then((result) => {
if (result.models) {
const modelIds = Object.keys(result.models).sort((a, b) => a.localeCompare(b))
setAsyncModels(modelIds)
}
})
.finally(() => {
setIsLoading(false)
})
}
}, [provider, controller])
const modelList = useMemo(() => {
if (usesOpenRouterModels(provider)) {
if (usesOpenRouterModels(provider) || provider === "oca") {
return asyncModels
}
return getModelList(provider)
}, [provider, asyncModels])
// Providers that support custom model IDs (e.g., Bedrock Application Inference Profiles)
const supportsCustomModel = provider === "bedrock"
const items: SearchableListItem[] = useMemo(() => {
return modelList.map((modelId) => ({
const list = modelList.map((modelId) => ({
id: modelId,
label: modelId,
}))
}, [modelList])
// Add "Custom" option at the end for providers that support it
if (supportsCustomModel) {
list.push({
id: CUSTOM_MODEL_ID,
label: "Custom (ARN / Inference Profile)",
})
}
return list
}, [modelList, supportsCustomModel])
// For providers without a model picker, render nothing
if (!hasModelPicker(provider)) {
@@ -180,7 +208,7 @@ export const ModelPicker: React.FC<ModelPickerProps> = ({ provider, controller,
}
// If async fetch returned no models, render nothing
if (usesOpenRouterModels(provider) && modelList.length === 0) {
if ((usesOpenRouterModels(provider) || provider === "oca") && modelList.length === 0) {
return null
}
+88
View File
@@ -0,0 +1,88 @@
/**
* OCA (Oracle Cloud Assist) employee check component.
* Shows a checkbox for "I'm an Oracle Employee" and a sign-in button.
* Sets ocaMode in state before triggering the OAuth flow.
*/
import { Box, Text, useInput } from "ink"
// biome-ignore lint/style/useImportType: React is used as a value by JSX (jsx: "react" in tsconfig)
import React, { useCallback, useState } from "react"
import { StateManager } from "@/core/storage/StateManager"
import { COLORS } from "../constants/colors"
import { useStdinContext } from "../context/StdinContext"
interface OcaEmployeeCheckProps {
/** Whether this component is active and should handle input */
isActive: boolean
/** Called when user confirms and wants to proceed with sign-in */
onSignIn: () => void
/** Called when user presses Escape to go back */
onCancel: () => void
}
export const OcaEmployeeCheck: React.FC<OcaEmployeeCheckProps> = ({ isActive, onSignIn, onCancel }) => {
const { isRawModeSupported } = useStdinContext()
const [isEmployee, setIsEmployee] = useState(true) // Default to checked (internal), matching extension behavior
const [selectedIndex, setSelectedIndex] = useState(0) // 0 = checkbox, 1 = sign in button
const ITEM_COUNT = 2
const handleSignIn = useCallback(async () => {
// Persist ocaMode to state before starting auth
const stateManager = StateManager.get()
stateManager.setGlobalState("ocaMode", isEmployee ? "internal" : "external")
await stateManager.flushPendingState()
onSignIn()
}, [isEmployee, onSignIn])
useInput(
(_input, key) => {
if (key.escape) {
onCancel()
return
}
if (key.upArrow) {
setSelectedIndex((prev) => (prev > 0 ? prev - 1 : ITEM_COUNT - 1))
} else if (key.downArrow) {
setSelectedIndex((prev) => (prev < ITEM_COUNT - 1 ? prev + 1 : 0))
} else if (key.tab || (key.return && selectedIndex === 0)) {
// Toggle checkbox when Tab is pressed or Enter on checkbox item
if (selectedIndex === 0) {
setIsEmployee((prev) => !prev)
}
} else if (key.return && selectedIndex === 1) {
// Sign in button
handleSignIn()
}
},
{ isActive: isRawModeSupported && isActive },
)
return (
<Box flexDirection="column">
<Text color="white">Oracle Code Assist</Text>
<Text> </Text>
{/* Checkbox: I'm an Oracle Employee */}
<Text>
<Text bold color={selectedIndex === 0 ? COLORS.primaryBlue : undefined}>
{selectedIndex === 0 ? "" : " "}{" "}
</Text>
<Text color={selectedIndex === 0 || isEmployee ? COLORS.primaryBlue : "gray"}>{isEmployee ? "[✓]" : "[ ]"}</Text>
<Text color={selectedIndex === 0 ? COLORS.primaryBlue : "white"}> I'm an Oracle Employee</Text>
{selectedIndex === 0 && <Text color="gray"> (Tab to toggle)</Text>}
</Text>
{/* Sign in button */}
<Text>
<Text bold color={selectedIndex === 1 ? COLORS.primaryBlue : undefined}>
{selectedIndex === 1 ? "" : " "}{" "}
</Text>
<Text color={selectedIndex === 1 ? COLORS.primaryBlue : "white"}>Sign in with Oracle Code Assist</Text>
{selectedIndex === 1 && <Text color="gray"> (Enter)</Text>}
</Text>
<Text> </Text>
<Text color="gray">Please ask your IT administrator to set up Oracle Code Assist as a model provider.</Text>
<Text> </Text>
<Text color="gray">Arrows to navigate, Tab to toggle, Enter to continue, Esc to go back</Text>
</Box>
)
}
+5 -6
View File
@@ -5,11 +5,11 @@
import React, { useMemo } from "react"
import { StateManager } from "@/core/storage/StateManager"
import type { ApiConfiguration } from "@/shared/api"
import { CLI_EXCLUDED_PROVIDERS, getProviderLabel, getProviderOrder } from "../utils/providers"
import { SearchableList, SearchableListItem } from "./SearchableList"
import { getProviderLabel, useValidProviders } from "../utils/providers"
import { SearchableList, type SearchableListItem } from "./SearchableList"
// Re-export for backwards compatibility
export { CLI_EXCLUDED_PROVIDERS, getProviderLabel, getProviderOrder }
export { getProviderLabel }
/**
* Check if a provider is configured (has required credentials/settings)
@@ -125,17 +125,16 @@ interface ProviderPickerProps {
export const ProviderPicker: React.FC<ProviderPickerProps> = ({ onSelect, isActive = true }) => {
// Get API configuration to check which providers are configured
const apiConfig = StateManager.get().getApiConfiguration()
const sorted = useValidProviders()
// Use providers.json order, filtered to exclude CLI-incompatible providers
const items: SearchableListItem[] = useMemo(() => {
const sorted = getProviderOrder().filter((p: string) => !CLI_EXCLUDED_PROVIDERS.has(p))
return sorted.map((providerId: string) => ({
id: providerId,
label: getProviderLabel(providerId),
suffix: isProviderConfigured(providerId, apiConfig) ? "(Configured)" : undefined,
}))
}, [apiConfig])
}, [apiConfig, sorted])
return <SearchableList isActive={isActive} items={items} onSelect={(item) => onSelect(item.id)} />
}
+112
View File
@@ -0,0 +1,112 @@
import { render } from "ink-testing-library"
// biome-ignore lint/correctness/noUnusedImports: React must be in scope for JSX in this test file.
import React from "react"
import { beforeEach, describe, expect, it, vi } from "vitest"
// Mock ink's useApp
const mockExit = vi.fn()
vi.mock("ink", async (importOriginal) => {
const actual = await importOriginal<typeof import("ink")>()
return {
...actual,
useApp: () => ({ exit: mockExit }),
}
})
// Mock child_process
vi.mock("child_process", () => ({
execSync: vi.fn().mockReturnValue(""),
exec: vi.fn(),
}))
// Mock dependencies
vi.mock("@/core/controller/slash/getAvailableSlashCommands", () => ({
getAvailableSlashCommands: vi.fn().mockResolvedValue({ commands: [] }),
}))
vi.mock("@/core/storage/StateManager", () => ({
StateManager: {
get: () => ({
getGlobalSettingsKey: vi.fn().mockReturnValue("act"),
getGlobalStateKey: vi.fn().mockReturnValue([]),
getApiConfiguration: vi.fn().mockReturnValue({}),
}),
},
}))
vi.mock("@/services/telemetry", () => ({
telemetryService: {
captureHostEvent: vi.fn(),
},
}))
vi.mock("@shared/services/Session", () => ({
Session: {
get: () => ({
getStats: vi.fn().mockReturnValue({}),
}),
},
}))
vi.mock("../context/TaskContext", () => ({
useTaskContext: () => ({
controller: {},
clearState: vi.fn(),
}),
useTaskState: () => ({
clineMessages: [],
}),
}))
vi.mock("../hooks/useStateSubscriber", () => ({
useIsSpinnerActive: () => ({ isActive: false, startTime: 0 }),
}))
import { ChatView } from "./ChatView"
// Helper to wait for async state updates
const delay = (ms = 60) => new Promise((resolve) => setTimeout(resolve, ms))
describe("Quit Command (/q and /exit)", () => {
const mockOnExit = vi.fn()
beforeEach(() => {
vi.clearAllMocks()
})
it("should exit the application when /q is selected from slash menu", async () => {
const { stdin } = render(<ChatView onExit={mockOnExit} />)
await delay()
// Type /q
stdin.write("/q")
await delay()
// Press Enter
stdin.write("\r")
// handleExit has a 150ms timeout
await delay(200)
expect(mockExit).toHaveBeenCalled()
expect(mockOnExit).toHaveBeenCalled()
})
it("should exit the application when /exit is selected from slash menu", async () => {
const { stdin } = render(<ChatView onExit={mockOnExit} />)
await delay()
// Type /exit
stdin.write("/exit")
await delay()
// Press Enter
stdin.write("\r")
// handleExit has a 150ms timeout
await delay(200)
expect(mockExit).toHaveBeenCalled()
expect(mockOnExit).toHaveBeenCalled()
})
})
+240 -22
View File
@@ -7,17 +7,21 @@ import type { AutoApprovalSettings } from "@shared/AutoApprovalSettings"
import { DEFAULT_AUTO_APPROVAL_SETTINGS } from "@shared/AutoApprovalSettings"
import type { ApiProvider, ModelInfo } from "@shared/api"
import { getProviderModelIdKey, isSettingsKey, ProviderToApiKeyMap } from "@shared/storage"
import { isOpenaiReasoningEffort, OPENAI_REASONING_EFFORT_OPTIONS, type OpenaiReasoningEffort } from "@shared/storage/types"
import type { TelemetrySetting } from "@shared/TelemetrySetting"
import { Box, Text, useInput } from "ink"
import Spinner from "ink-spinner"
import React, { useCallback, useEffect, useMemo, useState } from "react"
import { buildApiHandler } from "@/core/api"
import type { Controller } from "@/core/controller"
import { refreshOcaModels } from "@/core/controller/models/refreshOcaModels"
import { StateManager } from "@/core/storage/StateManager"
import { openAiCodexOAuthManager } from "@/integrations/openai-codex/oauth"
import { ClineAccountService } from "@/services/account/ClineAccountService"
import { AuthService, ClineAccountOrganization } from "@/services/auth/AuthService"
import { StringRequest } from "@/shared/proto/cline/common"
import { openExternal } from "@/utils/env"
import { supportsReasoningEffortForModel } from "@/utils/model-utils"
import { version as CLI_VERSION } from "../../package.json"
import { COLORS } from "../constants/colors"
import { useStdinContext } from "../context/StdinContext"
@@ -25,6 +29,7 @@ import { useOcaAuth } from "../hooks/useOcaAuth"
import { isMouseEscapeSequence } from "../utils/input"
import { applyBedrockConfig, applyProviderConfig } from "../utils/provider-config"
import { ApiKeyInput } from "./ApiKeyInput"
import { BedrockCustomModelFlow } from "./BedrockCustomModelFlow"
import { type BedrockConfig, BedrockSetup } from "./BedrockSetup"
import { Checkbox } from "./Checkbox"
import {
@@ -34,7 +39,8 @@ import {
isBrowseAllSelected,
} from "./FeaturedModelPicker"
import { LanguagePicker } from "./LanguagePicker"
import { hasModelPicker, ModelPicker } from "./ModelPicker"
import { CUSTOM_MODEL_ID, hasModelPicker, ModelPicker } from "./ModelPicker"
import { OcaEmployeeCheck } from "./OcaEmployeeCheck"
import { OrganizationPicker } from "./OrganizationPicker"
import { Panel, PanelTab } from "./Panel"
import { getProviderLabel, ProviderPicker } from "./ProviderPicker"
@@ -51,13 +57,25 @@ type SettingsTab = "api" | "auto-approve" | "features" | "other" | "account"
interface ListItem {
key: string
label: string
type: "checkbox" | "readonly" | "editable" | "separator" | "header" | "spacer" | "action"
type: "checkbox" | "readonly" | "editable" | "separator" | "header" | "spacer" | "action" | "cycle"
value: string | boolean
description?: string
isSubItem?: boolean
parentKey?: string
}
function normalizeReasoningEffort(value: unknown): OpenaiReasoningEffort {
if (isOpenaiReasoningEffort(value)) {
return value
}
return "low"
}
function nextReasoningEffort(current: OpenaiReasoningEffort): OpenaiReasoningEffort {
const idx = OPENAI_REASONING_EFFORT_OPTIONS.indexOf(current)
return OPENAI_REASONING_EFFORT_OPTIONS[(idx + 1) % OPENAI_REASONING_EFFORT_OPTIONS.length]
}
const TABS: PanelTab[] = [
{ key: "api", label: "API" },
{ key: "auto-approve", label: "Auto-approve" },
@@ -68,6 +86,12 @@ const TABS: PanelTab[] = [
// Settings configuration for simple boolean toggles
const FEATURE_SETTINGS = {
subagents: {
stateKey: "subagentsEnabled",
default: false,
label: "Subagents",
description: "Let Cline run focused subagents in parallel to explore the codebase for you",
},
autoCondense: {
stateKey: "useAutoCondense",
default: false,
@@ -98,6 +122,12 @@ const FEATURE_SETTINGS = {
label: "Parallel tool calling",
description: "Allow multiple tools in a single response",
},
doubleCheckCompletion: {
stateKey: "doubleCheckCompletionEnabled",
default: false,
label: "Double-check completion",
description: "Reject first completion attempt and require re-verification",
},
} as const
type FeatureKey = keyof typeof FEATURE_SETTINGS
@@ -136,11 +166,15 @@ export const SettingsPanelContent: React.FC<SettingsPanelContentProps> = ({
const [isEnteringApiKey, setIsEnteringApiKey] = useState(false)
const [isConfiguringBedrock, setIsConfiguringBedrock] = useState(false)
const [isWaitingForCodexAuth, setIsWaitingForCodexAuth] = useState(false)
const [isShowingOcaEmployeeCheck, setIsShowingOcaEmployeeCheck] = useState(false)
const [codexAuthError, setCodexAuthError] = useState<string | null>(null)
const [pendingProvider, setPendingProvider] = useState<string | null>(null)
const [apiKeyValue, setApiKeyValue] = useState("")
const [editValue, setEditValue] = useState("")
// Bedrock custom ARN flow state
const [isBedrockCustomFlow, setIsBedrockCustomFlow] = useState(false)
// Settings state - single object for feature toggles
const [features, setFeatures] = useState<Record<FeatureKey, boolean>>(() => {
const initial: Record<string, boolean> = {}
@@ -165,6 +199,12 @@ export const SettingsPanelContent: React.FC<SettingsPanelContentProps> = ({
const [planThinkingEnabled, setPlanThinkingEnabled] = useState<boolean>(
() => (stateManager.getGlobalSettingsKey("planModeThinkingBudgetTokens") ?? 0) > 0,
)
const [actReasoningEffort, setActReasoningEffort] = useState<OpenaiReasoningEffort>(() =>
normalizeReasoningEffort(stateManager.getGlobalSettingsKey("actModeReasoningEffort")),
)
const [planReasoningEffort, setPlanReasoningEffort] = useState<OpenaiReasoningEffort>(() =>
normalizeReasoningEffort(stateManager.getGlobalSettingsKey("planModeReasoningEffort")),
)
// Auto-approve settings (complex nested object)
const [autoApproveSettings, setAutoApproveSettings] = useState<AutoApprovalSettings>(() => {
@@ -203,6 +243,8 @@ export const SettingsPanelContent: React.FC<SettingsPanelContentProps> = ({
// OCA auth hook
const handleOcaAuthSuccess = useCallback(async () => {
await applyProviderConfig({ providerId: "oca", controller })
// Fetch OCA models from the API - this sets actModeOcaModelId/planModeOcaModelId in state
await refreshOcaModels(controller!, StringRequest.create({ value: "" }))
setProvider("oca")
refreshModelIds()
}, [controller, refreshModelIds])
@@ -395,9 +437,12 @@ export const SettingsPanelContent: React.FC<SettingsPanelContentProps> = ({
// Build items list based on current tab
const items: ListItem[] = useMemo(() => {
// OpenAI Native, Codex, and GPT models don't support thinking budget (they use reasoning effort)
const isGptModel = actModelId?.toLowerCase().includes("gpt") || planModelId?.toLowerCase().includes("gpt")
const showThinkingOption = provider !== "openai-native" && provider !== "openai-codex" && !isGptModel
// Some providers/models expose reasoning effort instead of thinking budget controls.
const providerUsesReasoningEffort = provider === "openai-native" || provider === "openai-codex"
const showActReasoningEffort = supportsReasoningEffortForModel(actModelId || "")
const showPlanReasoningEffort = supportsReasoningEffortForModel(planModelId || "")
const showActThinkingOption = !providerUsesReasoningEffort && !showActReasoningEffort
const showPlanThinkingOption = !providerUsesReasoningEffort && !showPlanReasoningEffort
switch (currentTab) {
case "api":
@@ -421,7 +466,7 @@ export const SettingsPanelContent: React.FC<SettingsPanelContentProps> = ({
type: "editable" as const,
value: actModelId || "not set",
},
...(showThinkingOption
...(showActThinkingOption
? [
{
key: "actThinkingEnabled",
@@ -431,6 +476,16 @@ export const SettingsPanelContent: React.FC<SettingsPanelContentProps> = ({
},
]
: []),
...(showActReasoningEffort
? [
{
key: "actReasoningEffort",
label: "Reasoning effort",
type: "cycle" as const,
value: actReasoningEffort,
},
]
: []),
{ key: "planHeader", label: "Plan Mode", type: "header" as const, value: "" },
{
key: "planModelId",
@@ -438,7 +493,7 @@ export const SettingsPanelContent: React.FC<SettingsPanelContentProps> = ({
type: "editable" as const,
value: planModelId || "not set",
},
...(showThinkingOption
...(showPlanThinkingOption
? [
{
key: "planThinkingEnabled",
@@ -448,6 +503,16 @@ export const SettingsPanelContent: React.FC<SettingsPanelContentProps> = ({
},
]
: []),
...(showPlanReasoningEffort
? [
{
key: "planReasoningEffort",
label: "Reasoning effort",
type: "cycle" as const,
value: planReasoningEffort,
},
]
: []),
{ key: "spacer1", label: "", type: "spacer" as const, value: "" },
]
: [
@@ -457,7 +522,7 @@ export const SettingsPanelContent: React.FC<SettingsPanelContentProps> = ({
type: "editable" as const,
value: actModelId || "not set",
},
...(showThinkingOption
...(showActThinkingOption
? [
{
key: "actThinkingEnabled",
@@ -467,6 +532,16 @@ export const SettingsPanelContent: React.FC<SettingsPanelContentProps> = ({
},
]
: []),
...(showActReasoningEffort
? [
{
key: "actReasoningEffort",
label: "Reasoning effort",
type: "cycle" as const,
value: actReasoningEffort,
},
]
: []),
]),
{
key: "separateModels",
@@ -629,6 +704,8 @@ export const SettingsPanelContent: React.FC<SettingsPanelContentProps> = ({
separateModels,
actThinkingEnabled,
planThinkingEnabled,
actReasoningEffort,
planReasoningEffort,
autoApproveSettings,
features,
preferredLanguage,
@@ -662,6 +739,33 @@ export const SettingsPanelContent: React.FC<SettingsPanelContentProps> = ({
}
}, [items.length, selectedIndex])
const rebuildTaskApi = useCallback(() => {
if (!controller?.task) {
return
}
const currentMode = stateManager.getGlobalSettingsKey("mode")
const apiConfig = stateManager.getApiConfiguration()
controller.task.api = buildApiHandler({ ...apiConfig, ulid: controller.task.ulid }, currentMode)
}, [controller, stateManager])
const setReasoningEffortForMode = useCallback(
(mode: "act" | "plan", effort: OpenaiReasoningEffort) => {
if (mode === "act") {
setActReasoningEffort(effort)
stateManager.setGlobalState("actModeReasoningEffort", effort)
if (!separateModels) {
setPlanReasoningEffort(effort)
stateManager.setGlobalState("planModeReasoningEffort", effort)
}
} else {
setPlanReasoningEffort(effort)
stateManager.setGlobalState("planModeReasoningEffort", effort)
}
rebuildTaskApi()
},
[separateModels, rebuildTaskApi, stateManager],
)
// Handle toggle/edit for selected item
const handleAction = useCallback(() => {
const item = items[selectedIndex]
@@ -685,6 +789,15 @@ export const SettingsPanelContent: React.FC<SettingsPanelContentProps> = ({
return
}
if (item.type === "cycle") {
const targetMode = item.key === "actReasoningEffort" ? "act" : item.key === "planReasoningEffort" ? "plan" : undefined
if (targetMode) {
const currentEffort = targetMode === "act" ? actReasoningEffort : planReasoningEffort
setReasoningEffortForMode(targetMode, nextReasoningEffort(currentEffort))
}
return
}
if (item.type === "editable") {
// For provider field, use the provider picker
if (item.key === "provider") {
@@ -742,7 +855,16 @@ export const SettingsPanelContent: React.FC<SettingsPanelContentProps> = ({
const actModel = stateManager.getGlobalSettingsKey(actKey)
if (planKey) stateManager.setGlobalState(planKey, actModel)
}
const actThinkingBudget = stateManager.getGlobalSettingsKey("actModeThinkingBudgetTokens") ?? 0
stateManager.setGlobalState("planModeThinkingBudgetTokens", actThinkingBudget)
setPlanThinkingEnabled(actThinkingBudget > 0)
const actEffort = normalizeReasoningEffort(stateManager.getGlobalSettingsKey("actModeReasoningEffort"))
stateManager.setGlobalState("planModeReasoningEffort", actEffort)
setPlanReasoningEffort(actEffort)
}
rebuildTaskApi()
return
}
@@ -750,23 +872,19 @@ export const SettingsPanelContent: React.FC<SettingsPanelContentProps> = ({
if (item.key === "actThinkingEnabled") {
setActThinkingEnabled(newValue)
stateManager.setGlobalState("actModeThinkingBudgetTokens", newValue ? 1024 : 0)
// Rebuild API handler to apply thinking budget change
if (controller?.task) {
const currentMode = stateManager.getGlobalSettingsKey("mode")
const apiConfig = stateManager.getApiConfiguration()
controller.task.api = buildApiHandler({ ...apiConfig, ulid: controller.task.ulid }, currentMode)
if (!separateModels) {
setPlanThinkingEnabled(newValue)
stateManager.setGlobalState("planModeThinkingBudgetTokens", newValue ? 1024 : 0)
}
// Rebuild API handler to apply thinking budget change
rebuildTaskApi()
return
}
if (item.key === "planThinkingEnabled") {
setPlanThinkingEnabled(newValue)
stateManager.setGlobalState("planModeThinkingBudgetTokens", newValue ? 1024 : 0)
// Rebuild API handler to apply thinking budget change
if (controller?.task) {
const currentMode = stateManager.getGlobalSettingsKey("mode")
const apiConfig = stateManager.getApiConfiguration()
controller.task.api = buildApiHandler({ ...apiConfig, ulid: controller.task.ulid }, currentMode)
}
rebuildTaskApi()
return
}
@@ -823,12 +941,63 @@ export const SettingsPanelContent: React.FC<SettingsPanelContentProps> = ({
handleClineLogin,
handleClineLogout,
accountOrganizations,
separateModels,
actReasoningEffort,
planReasoningEffort,
rebuildTaskApi,
setReasoningEffortForMode,
])
// Handle completion of the Bedrock custom ARN flow (ARN + base model selected)
const handleBedrockCustomFlowComplete = useCallback(
async (arn: string, baseModelId: string) => {
if (!pickingModelKey) return
const apiConfig = stateManager.getApiConfiguration()
// Build a minimal BedrockConfig from current state for applyBedrockConfig
const bedrockConfig: BedrockConfig = {
awsRegion: apiConfig.awsRegion ?? "us-east-1",
awsAuthentication: apiConfig.awsUseProfile ? "profile" : "credentials",
awsUseCrossRegionInference: Boolean(apiConfig.awsUseCrossRegionInference),
}
await applyBedrockConfig({
bedrockConfig,
modelId: arn,
customModelBaseId: baseModelId,
controller,
})
// Flush pending state to ensure everything is persisted
await stateManager.flushPendingState()
// Rebuild API handler if there's an active task
rebuildTaskApi()
refreshModelIds()
setIsBedrockCustomFlow(false)
setPickingModelKey(null)
// If opened from /models command, close the entire settings panel
if (initialMode) {
onClose()
}
},
[pickingModelKey, stateManager, controller, rebuildTaskApi, refreshModelIds, initialMode, onClose],
)
// Handle model selection from picker
const handleModelSelect = useCallback(
async (modelId: string) => {
if (!pickingModelKey) return
// Intercept "Custom" selection for Bedrock — redirect to custom ARN input flow
if (modelId === CUSTOM_MODEL_ID && provider === "bedrock") {
setIsPickingModel(false)
setIsBedrockCustomFlow(true)
return
}
const apiConfig = stateManager.getApiConfiguration()
const actProvider = apiConfig.actModeApiProvider
const planProvider = apiConfig.planModeApiProvider || actProvider
@@ -889,7 +1058,7 @@ export const SettingsPanelContent: React.FC<SettingsPanelContentProps> = ({
onClose()
}
},
[pickingModelKey, separateModels, stateManager, controller, refreshModelIds, initialMode, onClose],
[pickingModelKey, separateModels, stateManager, controller, provider, refreshModelIds, initialMode, onClose],
)
// Handle language selection from picker
@@ -965,8 +1134,8 @@ export const SettingsPanelContent: React.FC<SettingsPanelContentProps> = ({
setProvider("oca")
refreshModelIds()
} else {
// Not logged in - trigger OAuth
startOcaAuth()
// Not logged in - show employee check before auth
setIsShowingOcaEmployeeCheck(true)
}
return
}
@@ -1213,6 +1382,11 @@ export const SettingsPanelContent: React.FC<SettingsPanelContentProps> = ({
return
}
// Bedrock custom flow - input handled by BedrockCustomModelFlow component
if (isBedrockCustomFlow) {
return
}
if (isEditing) {
if (key.escape) {
setIsEditing(false)
@@ -1257,7 +1431,7 @@ export const SettingsPanelContent: React.FC<SettingsPanelContentProps> = ({
return
}
},
{ isActive: isRawModeSupported && !isEnteringApiKey && !isConfiguringBedrock },
{ isActive: isRawModeSupported && !isEnteringApiKey && !isConfiguringBedrock && !isShowingOcaEmployeeCheck },
)
// Render content
@@ -1433,6 +1607,19 @@ export const SettingsPanelContent: React.FC<SettingsPanelContentProps> = ({
)
}
if (isShowingOcaEmployeeCheck) {
return (
<OcaEmployeeCheck
isActive={isShowingOcaEmployeeCheck}
onCancel={() => setIsShowingOcaEmployeeCheck(false)}
onSignIn={() => {
setIsShowingOcaEmployeeCheck(false)
startOcaAuth()
}}
/>
)
}
if (isWaitingForOcaAuth) {
return (
<Box flexDirection="column">
@@ -1452,6 +1639,20 @@ export const SettingsPanelContent: React.FC<SettingsPanelContentProps> = ({
)
}
// Bedrock custom model flow (ARN input + base model selection)
if (isBedrockCustomFlow) {
return (
<BedrockCustomModelFlow
isActive={isBedrockCustomFlow}
onCancel={() => {
setIsBedrockCustomFlow(false)
setIsPickingModel(true)
}}
onComplete={handleBedrockCustomFlowComplete}
/>
)
}
// Account tab - loading state
if (currentTab === "account" && isAccountLoading) {
return (
@@ -1569,6 +1770,21 @@ export const SettingsPanelContent: React.FC<SettingsPanelContentProps> = ({
)
}
if (item.type === "cycle") {
return (
<Text key={item.key}>
<Text bold color={isSelected ? COLORS.primaryBlue : undefined}>
{isSelected ? "" : " "}{" "}
</Text>
<Text color={isSelected ? COLORS.primaryBlue : "white"}>{item.label}: </Text>
<Text color={COLORS.primaryBlue}>
{typeof item.value === "string" ? item.value : String(item.value)}
</Text>
{isSelected && <Text color="gray"> (Tab to cycle)</Text>}
</Text>
)
}
// Readonly or editable field
return (
<Text key={item.key}>
@@ -1599,7 +1815,9 @@ export const SettingsPanelContent: React.FC<SettingsPanelContentProps> = ({
!!codexAuthError ||
isPickingOrganization ||
isWaitingForClineAuth ||
isShowingOcaEmployeeCheck ||
isWaitingForOcaAuth ||
isBedrockCustomFlow ||
isEditing
return (
@@ -0,0 +1,230 @@
/**
* Tests for SkillsPanelContent component
*
* Tests keyboard interactions and callbacks.
* Rendering tests are limited due to ink-testing-library constraints with nested components.
*/
import { render } from "ink-testing-library"
// biome-ignore lint/correctness/noUnusedImports: React must be in scope for JSX in this test file.
import React from "react"
import { beforeEach, describe, expect, it, vi } from "vitest"
// Mock refreshSkills
const mockRefreshSkills = vi.fn()
vi.mock("@/core/controller/file/refreshSkills", () => ({
refreshSkills: () => mockRefreshSkills(),
}))
// Mock toggleSkill
const mockToggleSkill = vi.fn()
vi.mock("@/core/controller/file/toggleSkill", () => ({
toggleSkill: (...args: unknown[]) => mockToggleSkill(...args),
}))
// Mock child_process exec
const mockExec = vi.fn()
vi.mock("node:child_process", () => ({
exec: (...args: unknown[]) => mockExec(...args),
}))
// Mock StdinContext
vi.mock("../context/StdinContext", () => ({
useStdinContext: () => ({ isRawModeSupported: true }),
}))
import { SkillsPanelContent } from "./SkillsPanelContent"
// Helper to wait for async state updates
const delay = (ms = 60) => new Promise((resolve) => setTimeout(resolve, ms))
describe("SkillsPanelContent", () => {
const mockController = {} as any
const mockOnClose = vi.fn()
const mockOnUseSkill = vi.fn()
const defaultProps = {
controller: mockController,
onClose: mockOnClose,
onUseSkill: mockOnUseSkill,
}
beforeEach(() => {
vi.clearAllMocks()
mockRefreshSkills.mockResolvedValue({
globalSkills: [],
localSkills: [],
})
})
describe("keyboard interactions", () => {
it("should call onClose when Escape is pressed", async () => {
mockRefreshSkills.mockResolvedValue({
globalSkills: [],
localSkills: [],
})
const { stdin } = render(<SkillsPanelContent {...defaultProps} />)
await delay()
stdin.write("\x1B") // Escape
await delay()
expect(mockOnClose).toHaveBeenCalled()
})
it("should call onUseSkill with skill path when Enter is pressed on a skill", async () => {
mockRefreshSkills.mockResolvedValue({
globalSkills: [{ name: "test-skill", description: "Test", path: "/test/path/SKILL.md", enabled: true }],
localSkills: [],
})
const { stdin } = render(<SkillsPanelContent {...defaultProps} />)
await delay()
stdin.write("\r") // Enter
await delay()
expect(mockOnUseSkill).toHaveBeenCalledWith("/test/path/SKILL.md")
})
it("should call toggleSkill when Space is pressed on a skill", async () => {
mockRefreshSkills.mockResolvedValue({
globalSkills: [{ name: "test-skill", description: "Test", path: "/test/path/SKILL.md", enabled: true }],
localSkills: [],
})
const { stdin } = render(<SkillsPanelContent {...defaultProps} />)
await delay()
stdin.write(" ") // Space
await delay()
expect(mockToggleSkill).toHaveBeenCalledWith(
mockController,
expect.objectContaining({
skillPath: "/test/path/SKILL.md",
isGlobal: true,
enabled: false, // toggled from true to false
}),
)
})
it("should open marketplace URL when Enter is pressed on marketplace item", async () => {
mockRefreshSkills.mockResolvedValue({
globalSkills: [{ name: "skill", description: "desc", path: "/path", enabled: true }],
localSkills: [],
})
const { stdin } = render(<SkillsPanelContent {...defaultProps} />)
await delay()
// Navigate down to marketplace (past the one skill)
stdin.write("\x1B[B") // Down arrow
await delay()
stdin.write("\r") // Enter
await delay()
// Should have called exec with open command
expect(mockExec).toHaveBeenCalled()
const execCall = mockExec.mock.calls[0][0]
expect(execCall).toContain("https://skills.sh/")
})
it("should navigate through skills with arrow keys", async () => {
mockRefreshSkills.mockResolvedValue({
globalSkills: [
{ name: "skill-1", description: "First", path: "/path1", enabled: true },
{ name: "skill-2", description: "Second", path: "/path2", enabled: true },
],
localSkills: [],
})
const { stdin } = render(<SkillsPanelContent {...defaultProps} />)
await delay()
// Navigate down
stdin.write("\x1B[B") // Down arrow
await delay()
// Press Enter - should use second skill
stdin.write("\r")
await delay()
expect(mockOnUseSkill).toHaveBeenCalledWith("/path2")
})
it("should navigate with vim keys (j/k)", async () => {
mockRefreshSkills.mockResolvedValue({
globalSkills: [
{ name: "skill-1", description: "First", path: "/path1", enabled: true },
{ name: "skill-2", description: "Second", path: "/path2", enabled: true },
],
localSkills: [],
})
const { stdin } = render(<SkillsPanelContent {...defaultProps} />)
await delay()
// Navigate down with j
stdin.write("j")
await delay()
// Press Enter - should use second skill
stdin.write("\r")
await delay()
expect(mockOnUseSkill).toHaveBeenCalledWith("/path2")
})
it("should revert optimistic toggle on failure", async () => {
mockRefreshSkills.mockResolvedValue({
globalSkills: [{ name: "test-skill", description: "Test", path: "/test/path/SKILL.md", enabled: true }],
localSkills: [],
})
mockToggleSkill.mockRejectedValueOnce(new Error("toggle failed"))
const { stdin, lastFrame } = render(<SkillsPanelContent {...defaultProps} />)
await delay()
stdin.write(" ") // Space to toggle
await delay(100)
// toggleSkill was called with enabled: false (toggled from true)
expect(mockToggleSkill).toHaveBeenCalledWith(mockController, expect.objectContaining({ enabled: false }))
const frame = lastFrame() || ""
expect(frame).toContain("● test-skill")
expect(frame).not.toContain("○ test-skill")
})
it("should wrap navigation at list boundaries", async () => {
mockRefreshSkills.mockResolvedValue({
globalSkills: [{ name: "only-skill", description: "Only", path: "/only", enabled: true }],
localSkills: [],
})
const { stdin } = render(<SkillsPanelContent {...defaultProps} />)
await delay()
// Navigate up from first item (should wrap to last - marketplace)
stdin.write("\x1B[A") // Up arrow
await delay()
stdin.write("\r") // Enter
await delay()
// Should have opened marketplace (wrapped to last item)
expect(mockExec).toHaveBeenCalled()
})
})
describe("skill loading", () => {
it("should call refreshSkills on mount", async () => {
render(<SkillsPanelContent {...defaultProps} />)
await delay()
expect(mockRefreshSkills).toHaveBeenCalled()
})
})
})
+257
View File
@@ -0,0 +1,257 @@
/**
* Skills panel content for inline display in ChatView
* Shows installed skills with toggle and use functionality
*/
import { exec } from "node:child_process"
import os from "node:os"
import { Box, Text, useInput } from "ink"
import React, { useCallback, useEffect, useMemo, useState } from "react"
import type { Controller } from "@/core/controller"
import { refreshSkills } from "@/core/controller/file/refreshSkills"
import { toggleSkill } from "@/core/controller/file/toggleSkill"
import { COLORS } from "../constants/colors"
import { useStdinContext } from "../context/StdinContext"
import { isMouseEscapeSequence } from "../utils/input"
import { Panel } from "./Panel"
const SKILLS_MARKETPLACE_URL = "https://skills.sh/"
interface SkillInfo {
name: string
description: string
path: string
enabled: boolean
}
interface SkillsPanelContentProps {
controller: Controller
onClose: () => void
onUseSkill: (skillPath: string) => void
}
const MAX_VISIBLE = 8
export const SkillsPanelContent: React.FC<SkillsPanelContentProps> = ({ controller, onClose, onUseSkill }) => {
const { isRawModeSupported } = useStdinContext()
const [globalSkills, setGlobalSkills] = useState<SkillInfo[]>([])
const [localSkills, setLocalSkills] = useState<SkillInfo[]>([])
const [selectedIndex, setSelectedIndex] = useState(0)
const [isLoading, setIsLoading] = useState(true)
// Load skills on mount
useEffect(() => {
const loadSkills = async () => {
try {
const skillsData = await refreshSkills(controller)
setGlobalSkills(skillsData.globalSkills || [])
setLocalSkills(skillsData.localSkills || [])
} catch (_error) {
// Skills loading failed, show empty state
} finally {
setIsLoading(false)
}
}
loadSkills()
}, [controller])
// Build flat list of skills with source info (global first, then local, alphabetical within each)
const skillEntries = useMemo(() => {
const entries: { skill: SkillInfo; isGlobal: boolean }[] = []
globalSkills.forEach((skill) => entries.push({ skill, isGlobal: true }))
localSkills.forEach((skill) => entries.push({ skill, isGlobal: false }))
return entries.sort((a, b) => {
if (a.isGlobal !== b.isGlobal) return a.isGlobal ? -1 : 1
return a.skill.name.localeCompare(b.skill.name)
})
}, [globalSkills, localSkills])
// Handle toggle
const handleToggle = useCallback(async () => {
const entry = skillEntries[selectedIndex]
if (!entry) return
const newEnabled = !entry.skill.enabled
const setter = entry.isGlobal ? setGlobalSkills : setLocalSkills
const update = (enabled: boolean) =>
setter((prev) => prev.map((s) => (s.path === entry.skill.path ? { ...s, enabled } : s)))
// Optimistic update
update(newEnabled)
try {
await toggleSkill(controller, {
metadata: undefined,
skillPath: entry.skill.path,
isGlobal: entry.isGlobal,
enabled: newEnabled,
})
} catch {
// Revert on failure
update(!newEnabled)
}
}, [controller, skillEntries, selectedIndex])
// Handle use skill (insert @ mention)
const handleUse = useCallback(() => {
const entry = skillEntries[selectedIndex]
if (!entry) return
onUseSkill(entry.skill.path)
}, [skillEntries, selectedIndex, onUseSkill])
// Handle opening the marketplace URL
const openMarketplace = useCallback(() => {
const platform = os.platform()
let command: string
if (platform === "darwin") {
command = `open "${SKILLS_MARKETPLACE_URL}"`
} else if (platform === "win32") {
command = `start "${SKILLS_MARKETPLACE_URL}"`
} else {
command = `xdg-open "${SKILLS_MARKETPLACE_URL}"`
}
exec(command, (err) => {
if (err) {
// Fallback: show URL in terminal if browser open fails
console.error(`Visit: ${SKILLS_MARKETPLACE_URL}`)
}
})
}, [])
// Total items = skills + 1 for marketplace link
const totalItems = skillEntries.length + 1
const isMarketplaceSelected = selectedIndex === skillEntries.length
useInput(
(input, key) => {
if (isMouseEscapeSequence(input)) {
return
}
if (key.escape) {
onClose()
return
}
// Navigation
if (key.upArrow || input === "k") {
setSelectedIndex((i) => (i > 0 ? i - 1 : totalItems - 1))
return
}
if (key.downArrow || input === "j") {
setSelectedIndex((i) => (i < totalItems - 1 ? i + 1 : 0))
return
}
// Actions
if (key.return) {
if (isMarketplaceSelected) {
openMarketplace()
} else {
handleUse()
}
return
}
if (input === " " && !isMarketplaceSelected) {
handleToggle()
return
}
},
{ isActive: isRawModeSupported },
)
// Scrolling window (includes marketplace row)
const halfVisible = Math.floor(MAX_VISIBLE / 2)
const startIndex = Math.max(0, Math.min(selectedIndex - halfVisible, totalItems - MAX_VISIBLE))
if (isLoading) {
return (
<Panel label="Skills">
<Text color="gray">Loading skills...</Text>
</Panel>
)
}
// Check if marketplace row is in visible window
const marketplaceIndex = skillEntries.length
const showMarketplace = marketplaceIndex >= startIndex && marketplaceIndex < startIndex + MAX_VISIBLE
return (
<Panel label="Skills">
<Box flexDirection="column" gap={1}>
{skillEntries.length === 0 ? (
<Box flexDirection="column" gap={1}>
<Text color="gray">No skills installed.</Text>
<Text>
Install skills with: <Text color="white">npx skills add owner/repo</Text>
</Text>
</Box>
) : (
<Box flexDirection="column">
{skillEntries
.slice(startIndex, Math.min(startIndex + MAX_VISIBLE, skillEntries.length))
.map((entry, idx) => {
const actualIndex = startIndex + idx
const prevEntry = skillEntries[actualIndex - 1]
const showHeader = actualIndex === 0 || (prevEntry && prevEntry.isGlobal !== entry.isGlobal)
return (
<React.Fragment key={entry.skill.path}>
{showHeader && (
<Box marginTop={actualIndex > 0 ? 1 : 0}>
<Text bold color="gray">
{entry.isGlobal ? "Global Skills:" : "Workspace Skills:"}
</Text>
</Box>
)}
<SkillRow isSelected={actualIndex === selectedIndex} skill={entry.skill} />
</React.Fragment>
)
})}
</Box>
)}
{/* Marketplace link - selectable */}
{showMarketplace && (
<Box marginTop={1}>
<Text color={isMarketplaceSelected ? "cyan" : undefined}>
{isMarketplaceSelected ? " " : " "}
<Text color={COLORS.primaryBlue}>Browse more skills at https://skills.sh/</Text>
</Text>
</Box>
)}
{/* Help text */}
<Box marginTop={1}>
<Text color="gray">
/ Navigate Enter {isMarketplaceSelected ? "Open" : "Use"}
{!isMarketplaceSelected && " • Space Toggle"}
</Text>
</Box>
</Box>
</Panel>
)
}
const SkillRow: React.FC<{ skill: SkillInfo; isSelected: boolean }> = ({ skill, isSelected }) => {
return (
<Box flexDirection="column">
<Box>
<Text color={isSelected ? "cyan" : undefined}>
{isSelected ? " " : " "}
<Text color={skill.enabled ? "green" : "red"}>{skill.enabled ? "●" : "○"}</Text>
<Text> </Text>
<Text bold color="white">
{skill.name}
</Text>
</Text>
</Box>
{skill.description && (
<Box marginLeft={4}>
<Text color="gray">
{skill.description.length > 60 ? skill.description.slice(0, 57) + "..." : skill.description}
</Text>
</Box>
)}
</Box>
)
}
+361
View File
@@ -0,0 +1,361 @@
import type { ClineAskUseSubagents, ClineMessage, ClineSaySubagentStatus } from "@shared/ExtensionMessage"
import { Box, Text } from "ink"
import Spinner from "ink-spinner"
import React from "react"
import { COLORS } from "../constants/colors"
import { useTerminalSize } from "../hooks/useTerminalSize"
import { jsonParseSafe } from "../utils/parser"
interface SubagentMessageProps {
message: ClineMessage
isStreaming?: boolean
mode?: "act" | "plan"
}
const TREE_PREFIX_WIDTH = 5
const MIN_PROMPT_WIDTH = 20
const DotRow: React.FC<{ children: React.ReactNode; color?: string; flashing?: boolean }> = ({
children,
color,
flashing = false,
}) => (
<Box flexDirection="row">
<Box width={2}>
{flashing ? (
<Text color={color}>
<Spinner type="toggle8" />
</Text>
) : (
<Text color={color}></Text>
)}
</Box>
<Box flexGrow={1}>{children}</Box>
</Box>
)
function formatCompactTokens(tokens: number | undefined): string {
const value = Number.isFinite(tokens) ? Math.max(0, tokens || 0) : 0
return new Intl.NumberFormat("en-US", {
notation: "compact",
maximumFractionDigits: 1,
})
.format(value)
.toLowerCase()
}
function formatCompactCost(cost: number | undefined): string {
const value = Number.isFinite(cost) ? Math.max(0, cost || 0) : 0
const maximumFractionDigits = value >= 0.01 ? 2 : 4
return new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
minimumFractionDigits: 2,
maximumFractionDigits,
}).format(value)
}
function formatSubagentStatsValues(
toolCalls: number | undefined,
contextTokens: number | undefined,
totalCost: number | undefined,
latestToolCall?: string,
) {
const safeToolCalls = Number.isFinite(toolCalls) ? Math.max(0, toolCalls || 0) : 0
const toolUses = safeToolCalls === 1 ? "tool use" : "tool uses"
const tokensUsed = formatCompactTokens(contextTokens || 0)
const formattedCost = formatCompactCost(totalCost || 0)
const stats = `${safeToolCalls} ${toolUses} · ${tokensUsed} tokens · ${formattedCost}`
const latestTool = latestToolCall?.trim()
return latestTool ? `${latestTool} · ${stats}` : stats
}
function wrapPrompt(text: string, width: number): string[] {
if (!text) {
return [""]
}
const normalizedWidth = Math.max(1, width)
const wrappedLines: string[] = []
const paragraphs = text.split("\n")
for (const paragraph of paragraphs) {
const words = paragraph.trim().split(/\s+/).filter(Boolean)
if (words.length === 0) {
wrappedLines.push("")
continue
}
let line = ""
for (const word of words) {
if (!line) {
if (word.length <= normalizedWidth) {
line = word
continue
}
let remaining = word
while (remaining.length > normalizedWidth) {
wrappedLines.push(remaining.slice(0, normalizedWidth))
remaining = remaining.slice(normalizedWidth)
}
line = remaining
continue
}
if (line.length + 1 + word.length <= normalizedWidth) {
line = `${line} ${word}`
continue
}
wrappedLines.push(line)
if (word.length <= normalizedWidth) {
line = word
continue
}
let remaining = word
while (remaining.length > normalizedWidth) {
wrappedLines.push(remaining.slice(0, normalizedWidth))
remaining = remaining.slice(normalizedWidth)
}
line = remaining
}
if (line) {
wrappedLines.push(line)
}
}
return wrappedLines.length > 0 ? wrappedLines : [text]
}
const TreePromptRow: React.FC<{
prefix: React.ReactNode
continuationPrefix: string
prompt: string
promptWidth: number
color?: string
}> = ({ prefix, continuationPrefix, prompt, promptWidth, color }) => {
const lines = wrapPrompt(prompt, promptWidth)
return (
<Box flexDirection="column" width="100%">
{lines.map((line, index) => (
<Box flexDirection="row" key={`${line}-${index}`} width="100%">
<Box flexShrink={0} width={TREE_PREFIX_WIDTH}>
{index === 0 ? prefix : <Text color="gray">{continuationPrefix}</Text>}
</Box>
<Box flexGrow={1}>
<Text color={color}>{line}</Text>
</Box>
</Box>
))}
</Box>
)
}
const TreeStatsRow: React.FC<{ prefix: string; stats: string }> = ({ prefix, stats }) => (
<Box flexDirection="row" width="100%">
<Box flexShrink={0} width={TREE_PREFIX_WIDTH}>
<Text color="gray">{prefix}</Text>
</Box>
<Box flexGrow={1}>
<Text color="gray"> {stats}</Text>
</Box>
</Box>
)
export const SubagentMessage: React.FC<SubagentMessageProps> = ({ message, mode, isStreaming }) => {
const { type, ask, say, text, partial } = message
const toolColor = mode === "plan" ? "yellow" : COLORS.primaryBlue
const { columns } = useTerminalSize()
const promptWidth = Math.max(MIN_PROMPT_WIDTH, columns - 2 - TREE_PREFIX_WIDTH)
if ((type === "ask" && ask === "use_subagents") || say === "use_subagents") {
const parsed = text
? jsonParseSafe<ClineAskUseSubagents>(text, {
prompts: [],
})
: { prompts: [] }
const prompts = (parsed.prompts || []).map((prompt) => prompt?.trim()).filter(Boolean)
if (prompts.length === 0) {
return (
<Box flexDirection="column" marginBottom={1} width="100%">
<DotRow color={toolColor}>
<Text color={toolColor}>Cline wants to run subagents:</Text>
</DotRow>
</Box>
)
}
const singular = prompts.length === 1
return (
<Box flexDirection="column" marginBottom={1} width="100%">
<DotRow color={toolColor} flashing={partial === true && isStreaming}>
<Text color={toolColor}>{singular ? "Cline wants to run a subagent:" : "Cline wants to run subagents:"}</Text>
</DotRow>
<Box flexDirection="column" marginLeft={2} width="100%">
{prompts.map((prompt, index) => {
const isLastPrompt = index === prompts.length - 1
const branch = isLastPrompt ? "└─" : "├─"
const continuationPrefix = isLastPrompt ? " " : "│ "
const shouldShowPromptStats = partial !== true || !isLastPrompt
return (
<Box flexDirection="column" key={`${prompt}-${index}`}>
<TreePromptRow
color={toolColor}
continuationPrefix={continuationPrefix}
prefix={<Text color={toolColor}>{`${branch} `}</Text>}
prompt={prompt}
promptWidth={promptWidth}
/>
{shouldShowPromptStats && (
<TreeStatsRow
prefix={continuationPrefix}
stats={formatSubagentStatsValues(undefined, undefined, undefined)}
/>
)}
</Box>
)
})}
</Box>
</Box>
)
}
if (say === "subagent" && text) {
const parsed = jsonParseSafe<ClineSaySubagentStatus>(text, {
status: "running",
total: 0,
completed: 0,
successes: 0,
failures: 0,
toolCalls: 0,
inputTokens: 0,
outputTokens: 0,
contextWindow: 0,
maxContextTokens: 0,
maxContextUsagePercentage: 0,
items: [],
})
const items = parsed.items || []
if (items.length === 0) {
return null
}
return (
<Box flexDirection="column" marginBottom={1} width="100%">
<DotRow color={toolColor} flashing={partial === true && isStreaming}>
<Text color={toolColor}>
{items.length === 1 ? "Cline is running a subagent:" : "Cline is running subagents:"}
</Text>
</DotRow>
<Box flexDirection="column" marginLeft={2} width="100%">
{items.map((entry, index) => {
const isLastEntry = index === items.length - 1
const branch = isLastEntry ? "└─" : "├─"
const continuationPrefix = isLastEntry ? " " : "│ "
const key = `${entry.index}-${index}`
const shouldShowStats = true
if (entry.status === "completed") {
return (
<Box flexDirection="column" key={key}>
<TreePromptRow
color="green"
continuationPrefix={continuationPrefix}
prefix={
<Box flexDirection="row">
<Text color="gray">{`${branch} `}</Text>
<Text color="green"></Text>
</Box>
}
prompt={entry.prompt}
promptWidth={promptWidth}
/>
<TreeStatsRow
prefix={continuationPrefix}
stats={formatSubagentStatsValues(
entry.toolCalls,
entry.contextTokens,
entry.totalCost,
entry.latestToolCall,
)}
/>
</Box>
)
}
if (entry.status === "failed") {
return (
<Box flexDirection="column" key={key}>
<TreePromptRow
color="red"
continuationPrefix={continuationPrefix}
prefix={
<Box flexDirection="row">
<Text color="gray">{`${branch} `}</Text>
<Text color="red"></Text>
</Box>
}
prompt={entry.prompt}
promptWidth={promptWidth}
/>
<TreeStatsRow
prefix={continuationPrefix}
stats={formatSubagentStatsValues(
entry.toolCalls,
entry.contextTokens,
entry.totalCost,
entry.latestToolCall,
)}
/>
</Box>
)
}
return (
<Box flexDirection="column" key={key}>
<TreePromptRow
color={toolColor}
continuationPrefix={continuationPrefix}
prefix={
<Box flexDirection="row">
<Text color="gray">{branch} </Text>
{entry.status === "running" ? (
<Text color={toolColor}>
<Spinner type="dots" />
</Text>
) : (
<Text color={toolColor}></Text>
)}
</Box>
}
prompt={entry.prompt}
promptWidth={promptWidth}
/>
{shouldShowStats && (
<TreeStatsRow
prefix={continuationPrefix}
stats={formatSubagentStatsValues(
entry.toolCalls,
entry.contextTokens,
entry.totalCost,
entry.latestToolCall,
)}
/>
)}
</Box>
)
})}
</Box>
</Box>
)
}
return null
}
+12
View File
@@ -0,0 +1,12 @@
import { describe, expect, it } from "vitest"
import { getAllFeaturedModels } from "./featured-models"
describe("featured models", () => {
it("includes display names for all featured models", () => {
const models = getAllFeaturedModels()
for (const model of models) {
expect(model.name).toBeTruthy()
}
})
})
+35 -23
View File
@@ -7,50 +7,62 @@ export interface FeaturedModel {
id: string
name: string
description: string
label: string
labels: string[]
}
export const FEATURED_MODELS = {
export const FEATURED_MODELS: { recommended: FeaturedModel[]; free: FeaturedModel[] } = {
recommended: [
{
id: "anthropic/claude-opus-4.5",
name: "Claude Opus 4.5",
description: "State-of-the-art for complex coding",
label: "Best",
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",
label: "New",
labels: ["HOT"],
},
{
id: "google/gemini-3-pro-preview",
name: "Gemini 3 Pro",
description: "1M context window for large codebases",
label: "Trending",
},
] as FeaturedModel[],
],
free: [
{
id: "moonshotai/kimi-k2.5",
name: "Kimi K2.5",
description: "State-of-the-art model topping benchmarks",
label: "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: "Advanced agentic coding model",
label: "FREE",
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: "US built open source coding model",
label: "FREE",
description: "Arcee AI's advanced large preview model in the Trinity series",
labels: ["FREE"],
},
] as FeaturedModel[],
],
}
export function getAllFeaturedModels(): FeaturedModel[] {
+13 -1
View File
@@ -6,6 +6,7 @@
* - Ctrl+A/E: start/end of line
* - Ctrl+W: delete word backwards
* - Ctrl+U: delete to start of line
* - Ctrl+K: delete to end of line
*
* Note: Home/End keys are handled by useHomeEndKeys hook because Ink doesn't
* expose them in useInput (it sets input='' for these keys).
@@ -152,6 +153,14 @@ export function useTextInput(): UseTextInputReturn {
}
}, [])
const deleteToEnd = useCallback(() => {
const pos = cursorRef.current
if (pos < textRef.current.length) {
setTextState((prev) => prev.slice(0, pos))
// Cursor stays at same position (now at end of text)
}
}, [])
// Cursor movement (internal, used by handlers)
const moveToStart = useCallback(() => setCursorPosState(0), [])
const moveToEnd = useCallback(() => setCursorPosState(textRef.current.length), [])
@@ -190,6 +199,9 @@ export function useTextInput(): UseTextInputReturn {
case "u": // Ctrl+U - delete to start
deleteToStart()
return true
case "k": // Ctrl+K - delete to end
deleteToEnd()
return true
case "w": // Ctrl+W - delete word backwards
deleteWordBefore()
return true
@@ -197,7 +209,7 @@ export function useTextInput(): UseTextInputReturn {
return false
}
},
[moveToStart, moveToEnd, deleteToStart, deleteWordBefore],
[moveToStart, moveToEnd, deleteToStart, deleteToEnd, deleteWordBefore],
)
return {
+42 -2
View File
@@ -30,7 +30,9 @@ describe("CLI Commands", () => {
.option("-v, --verbose", "Show verbose output")
.option("-c, --cwd <path>", "Working directory")
.option("--config <path>", "Configuration directory")
.option("--thinking", "Enable extended thinking")
.option("--thinking [tokens]", "Enable extended thinking")
.option("--reasoning-effort <effort>", "Reasoning effort")
.option("--max-consecutive-mistakes <count>", "Maximum consecutive mistakes")
.action(() => {})
program
@@ -67,7 +69,9 @@ describe("CLI Commands", () => {
.option("-v, --verbose", "Verbose output")
.option("-c, --cwd <path>", "Working directory")
.option("--config <path>", "Configuration directory")
.option("--thinking", "Enable extended thinking")
.option("--thinking [tokens]", "Enable extended thinking")
.option("--reasoning-effort <effort>", "Reasoning effort")
.option("--max-consecutive-mistakes <count>", "Maximum consecutive mistakes")
.action(() => {})
})
@@ -146,6 +150,27 @@ describe("CLI Commands", () => {
expect(taskCmd.opts().thinking).toBe(true)
})
it("should parse --thinking with token budget", () => {
const taskCmd = program.commands.find((c) => c.name() === "task")!
const args = ["test prompt", "--thinking", "8000"]
taskCmd.parse(args, { from: "user" })
expect(taskCmd.opts().thinking).toBe("8000")
})
it("should parse --reasoning-effort option", () => {
const taskCmd = program.commands.find((c) => c.name() === "task")!
const args = ["test prompt", "--reasoning-effort", "high"]
taskCmd.parse(args, { from: "user" })
expect(taskCmd.opts().reasoningEffort).toBe("high")
})
it("should parse --max-consecutive-mistakes option", () => {
const taskCmd = program.commands.find((c) => c.name() === "task")!
const args = ["test prompt", "--max-consecutive-mistakes", "999"]
taskCmd.parse(args, { from: "user" })
expect(taskCmd.opts().maxConsecutiveMistakes).toBe("999")
})
it("should parse short flags", () => {
const taskCmd = program.commands.find((c) => c.name() === "task")!
const args = ["test prompt", "-a", "-v", "-m", "gpt-4"]
@@ -281,6 +306,21 @@ describe("CLI Commands", () => {
program.parse(["node", "cli", "--thinking"])
expect(program.opts().thinking).toBe(true)
})
it("should parse --thinking with token budget", () => {
program.parse(["node", "cli", "--thinking", "4096"])
expect(program.opts().thinking).toBe("4096")
})
it("should parse --reasoning-effort option", () => {
program.parse(["node", "cli", "--reasoning-effort", "medium"])
expect(program.opts().reasoningEffort).toBe("medium")
})
it("should parse --max-consecutive-mistakes option", () => {
program.parse(["node", "cli", "--max-consecutive-mistakes", "7"])
expect(program.opts().maxConsecutiveMistakes).toBe("7")
})
})
describe("command structure", () => {
+406 -183
View File
@@ -8,20 +8,20 @@ import { Command } from "commander"
import { render } from "ink"
import React from "react"
import { ClineEndpoint } from "@/config"
import { Controller } from "@/core/controller"
import type { Controller } from "@/core/controller"
import { StateManager } from "@/core/storage/StateManager"
import { AuthHandler } from "@/hosts/external/AuthHandler"
import { HostProvider } from "@/hosts/host-provider"
import { FileEditProvider } from "@/integrations/editor/FileEditProvider"
import { openAiCodexOAuthManager } from "@/integrations/openai-codex/oauth"
import { StandaloneTerminalManager } from "@/integrations/terminal/standalone/StandaloneTerminalManager"
import { BannerService } from "@/services/banner/BannerService"
import { ErrorService } from "@/services/error/ErrorService"
import { initializeDistinctId } from "@/services/logging/distinctId"
import { telemetryService } from "@/services/telemetry"
import { PostHogClientProvider } from "@/services/telemetry/providers/posthog/PostHogClientProvider"
import { HistoryItem } from "@/shared/HistoryItem"
import { Logger } from "@/shared/services/Logger"
import { Session } from "@/shared/services/Session"
import { getProviderModelIdKey, ProviderToApiKeyMap } 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"
import { App } from "./components/App"
@@ -31,6 +31,7 @@ import { CliCommentReviewController } from "./controllers/CliCommentReviewContro
import { CliWebviewProvider } from "./controllers/CliWebviewProvider"
import { restoreConsole } from "./utils/console"
import { printInfo, printWarning } from "./utils/display"
import { selectOutputMode } from "./utils/mode-selection"
import { parseImagesFromInput, processImagePaths } from "./utils/parser"
import { CLINE_CLI_DIR, getCliBinaryPath } from "./utils/path"
import { readStdinIfPiped } from "./utils/piped"
@@ -41,6 +42,253 @@ import { autoUpdateOnStartup, checkForUpdates } from "./utils/update"
import { initializeCliContext } from "./vscode-context"
import { CLI_LOG_FILE, shutdownEvent, window } from "./vscode-shim"
/**
* Common options shared between runTask and resumeTask
*/
interface TaskOptions {
act?: boolean
plan?: boolean
model?: string
verbose?: boolean
cwd?: string
config?: string
thinking?: boolean | string
reasoningEffort?: string
maxConsecutiveMistakes?: string
yolo?: boolean
doubleCheckCompletion?: boolean
timeout?: string
json?: boolean
stdinWasPiped?: boolean
}
let telemetryDisposed = false
async function disposeTelemetryServices(): Promise<void> {
if (telemetryDisposed) {
return
}
telemetryDisposed = true
await Promise.allSettled([telemetryService.dispose(), PostHogClientProvider.getInstance().dispose()])
}
async function disposeCliContext(ctx: CliContext): Promise<void> {
await ctx.controller.stateManager.flushPendingState()
await ctx.controller.dispose()
await ErrorService.get().dispose()
await disposeTelemetryServices()
}
function setModeScopedState(currentMode: "act" | "plan", setter: (mode: "act" | "plan") => void): void {
const stateManager = StateManager.get()
setter(currentMode)
const separateModels = stateManager.getGlobalSettingsKey("planActSeparateModelsSetting") ?? false
if (!separateModels) {
const otherMode: "act" | "plan" = currentMode === "act" ? "plan" : "act"
setter(otherMode)
}
}
function normalizeReasoningEffort(value?: string): OpenaiReasoningEffort | undefined {
if (value === undefined) {
return undefined
}
const normalized = value.toLowerCase()
if (isOpenaiReasoningEffort(normalized)) {
return normalized
}
printWarning(
`Invalid --reasoning-effort '${value}'. Using 'medium'. Valid values: ${OPENAI_REASONING_EFFORT_OPTIONS.join(", ")}.`,
)
return "medium"
}
function normalizeMaxConsecutiveMistakes(value?: string): number | undefined {
if (value === undefined) {
return undefined
}
const parsed = Number.parseInt(value, 10)
if (Number.isNaN(parsed) || parsed < 1) {
printWarning(`Invalid --max-consecutive-mistakes value '${value}'. Expected integer >= 1.`)
return undefined
}
return parsed
}
/**
* Apply task-related options (mode, model, thinking, yolo) to StateManager.
* Shared between runTask and resumeTask to avoid duplication.
*/
function applyTaskOptions(options: TaskOptions): void {
// Apply mode flag
if (options.plan) {
StateManager.get().setGlobalState("mode", "plan")
telemetryService.captureHostEvent("mode_flag", "plan")
} else if (options.act) {
StateManager.get().setGlobalState("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 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)
}
telemetryService.captureHostEvent("model_flag", options.model)
}
// Set thinking budget based on --thinking flag (boolean or number)
let thinkingBudget = 0
if (options.thinking) {
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) {
telemetryService.captureHostEvent("thinking_flag", "true")
}
const reasoningEffort = normalizeReasoningEffort(options.reasoningEffort)
if (reasoningEffort !== undefined) {
setModeScopedState(currentMode, (mode) => {
const reasoningKey = mode === "act" ? "actModeReasoningEffort" : "planModeReasoningEffort"
StateManager.get().setGlobalState(reasoningKey, reasoningEffort)
})
telemetryService.captureHostEvent("reasoning_effort_flag", reasoningEffort)
}
const maxConsecutiveMistakes = normalizeMaxConsecutiveMistakes(options.maxConsecutiveMistakes)
if (maxConsecutiveMistakes !== undefined) {
StateManager.get().setGlobalState("maxConsecutiveMistakes", maxConsecutiveMistakes)
telemetryService.captureHostEvent("max_consecutive_mistakes_flag", String(maxConsecutiveMistakes))
}
// Set yolo mode as a session-scoped override so AutoApprove picks it up,
// but it is never persisted to disk (setSessionOverride never touches pendingGlobalState).
if (options.yolo) {
StateManager.get().setSessionOverride("yoloModeToggled", true)
telemetryService.captureHostEvent("yolo_flag", "true")
}
// Set double-check completion based on flag
if (options.doubleCheckCompletion) {
StateManager.get().setGlobalState("doubleCheckCompletionEnabled", true)
telemetryService.captureHostEvent("double_check_completion_flag", "true")
}
}
/**
* Get mode selection result using the extracted, testable selectOutputMode function.
* This wrapper provides the current process TTY state.
*/
function getModeSelection(options: TaskOptions) {
return selectOutputMode({
stdoutIsTTY: process.stdout.isTTY === true,
stdinIsTTY: process.stdin.isTTY === true,
stdinWasPiped: options.stdinWasPiped ?? false,
json: options.json,
yolo: options.yolo,
})
}
/**
* Determine if plain text mode should be used based on options and environment.
*/
function shouldUsePlainTextMode(options: TaskOptions): boolean {
return getModeSelection(options).usePlainTextMode
}
/**
* Get the reason for using plain text mode (for telemetry).
*/
function getPlainTextModeReason(options: TaskOptions): string {
return getModeSelection(options).reason
}
/**
* Run a task in plain text mode (no Ink UI).
* Handles auth check, task execution, cleanup, and exit.
*/
async function runTaskInPlainTextMode(
ctx: CliContext,
options: TaskOptions,
taskConfig: {
prompt?: string
taskId?: string
imageDataUrls?: string[]
},
): Promise<never> {
// Set flag so shutdown handler knows not to clear Ink UI lines
isPlainTextMode = true
// Check if auth is configured before attempting to run the task
// In plain text mode we can't show the interactive auth flow
const hasAuth = await isAuthConfigured()
if (!hasAuth) {
printWarning("Not authenticated. Please run 'cline auth' first to configure your API credentials.")
await disposeCliContext(ctx)
exit(1)
}
const reason = getPlainTextModeReason(options)
telemetryService.captureHostEvent("plain_text_mode", reason)
// Plain text mode: no Ink rendering, just clean text output
const success = await runPlainTextTask({
controller: ctx.controller,
prompt: taskConfig.prompt,
taskId: taskConfig.taskId,
imageDataUrls: taskConfig.imageDataUrls,
verbose: options.verbose,
jsonOutput: options.json,
timeoutSeconds: options.timeout ? Number.parseInt(options.timeout, 10) : undefined,
})
// Cleanup
await disposeCliContext(ctx)
// Ensure stdout is fully drained before exiting - critical for piping
await drainStdout()
exit(success ? 0 : 1)
}
/**
* Create the standard cleanup function for Ink apps.
*/
function createInkCleanup(ctx: CliContext, onTaskError?: () => boolean): () => Promise<void> {
return async () => {
await disposeCliContext(ctx)
if (onTaskError?.()) {
printWarning("Task ended with errors.")
exit(1)
}
exit(0)
}
}
// Track active context for graceful shutdown
let activeContext: CliContext | null = null
let isShuttingDown = false
@@ -92,10 +340,17 @@ function setupSignalHandlers() {
if (task) {
task.abortTask()
}
await activeContext.controller.stateManager.flushPendingState()
await activeContext.controller.dispose()
await disposeCliContext(activeContext)
} else {
// Best-effort flush of restored yolo state when no active context
try {
await StateManager.get().flushPendingState()
} catch {
// StateManager may not be initialized yet
}
await ErrorService.get().dispose()
await disposeTelemetryServices()
}
await ErrorService.get().dispose()
} catch {
// Best effort cleanup
}
@@ -143,13 +398,22 @@ interface InitOptions {
*/
async function initializeCli(options: InitOptions): Promise<CliContext> {
const workspacePath = options.cwd || process.cwd()
const { extensionContext, DATA_DIR, EXTENSION_DIR } = initializeCliContext({
const { extensionContext, storageContext, DATA_DIR, EXTENSION_DIR } = initializeCliContext({
clineDir: options.config,
workspaceDir: workspacePath,
})
await ClineEndpoint.initialize()
await initializeDistinctId(extensionContext)
// Set up output channel and Logger early so ClineEndpoint.initialize logs are captured
const outputChannel = window.createOutputChannel("Cline CLI")
const logToChannel = (message: string) => outputChannel.appendLine(message)
// Configure the shared Logging class early to capture all initialization logs
Logger.subscribe(logToChannel)
await ClineEndpoint.initialize(EXTENSION_DIR)
// Auto-update check (after endpoints initialized, so we can detect bundled configs)
autoUpdateOnStartup(CLI_VERSION)
// Initialize/reset session tracking for this CLI run
Session.reset()
@@ -158,11 +422,9 @@ async function initializeCli(options: InitOptions): Promise<CliContext> {
AuthHandler.getInstance().setEnabled(true)
}
const outputChannel = window.createOutputChannel("Cline CLI")
outputChannel.appendLine(
`Cline CLI initialized. Data dir: ${DATA_DIR}, Extension dir: ${EXTENSION_DIR}, Log dir: ${CLINE_CLI_DIR.log}`,
)
const logToChannel = (message: string) => outputChannel.appendLine(message)
HostProvider.initialize(
() => new CliWebviewProvider(extensionContext as any),
@@ -171,28 +433,20 @@ async function initializeCli(options: InitOptions): Promise<CliContext> {
() => new StandaloneTerminalManager(),
createCliHostBridgeProvider(workspacePath),
logToChannel,
async () => (options.enableAuth ? AuthHandler.getInstance().getCallbackUrl() : ""),
async (path: string) => (options.enableAuth ? AuthHandler.getInstance().getCallbackUrl(path) : ""),
getCliBinaryPath,
EXTENSION_DIR,
DATA_DIR,
)
await StateManager.initialize(extensionContext as any)
await StateManager.initialize(storageContext)
await ErrorService.initialize()
// Initialize OpenAI Codex OAuth manager with extension context for secrets storage
openAiCodexOAuthManager.initialize(extensionContext)
// Configure the shared Logging class to use HostProvider's output channel
Logger.subscribe((msg: string) => HostProvider.get().logToChannel(msg))
const webview = HostProvider.get().createWebviewProvider() as CliWebviewProvider
const controller = webview.controller
BannerService.initialize(webview.controller)
telemetryService.captureExtensionActivated()
telemetryService.captureHostEvent("cline_cli", "initialized")
await telemetryService.captureExtensionActivated()
await telemetryService.captureHostEvent("cline_cli", "initialized")
const ctx = { extensionContext, dataDir: DATA_DIR, extensionDir: EXTENSION_DIR, workspacePath, controller }
activeContext = ctx
@@ -228,24 +482,7 @@ async function runInkApp(element: React.ReactElement, cleanup: () => Promise<voi
/**
* Run a task with the given prompt - uses welcome view for consistent behavior
*/
async function runTask(
prompt: string,
options: {
act?: boolean
plan?: boolean
model?: string
verbose?: boolean
cwd?: string
config?: string
thinking?: boolean
yolo?: boolean
timeout?: string
images?: string[]
json?: boolean
stdinWasPiped?: boolean
},
existingContext?: CliContext,
) {
async function runTask(prompt: string, options: TaskOptions & { images?: string[] }, existingContext?: CliContext) {
const ctx = existingContext || (await initializeCli({ ...options, enableAuth: true }))
// Parse images from the prompt text (e.g., @/path/to/image.png)
@@ -262,101 +499,23 @@ async function runTask(
// Task without prompt starts in interactive mode
telemetryService.captureHostEvent("task_command", prompt ? "task" : "interactive")
if (options.plan) {
StateManager.get().setGlobalState("mode", "plan")
telemetryService.captureHostEvent("mode_flag", "plan")
} else if (options.act) {
StateManager.get().setGlobalState("mode", "act")
telemetryService.captureHostEvent("mode_flag", "act")
}
if (options.model) {
const selectedMode = (StateManager.get().getGlobalSettingsKey("mode") || "act") as "act" | "plan"
// Get the current provider for the selected mode
const providerKey = selectedMode === "act" ? "actModeApiProvider" : "planModeApiProvider"
const currentProvider = StateManager.get().getGlobalSettingsKey(providerKey) as ApiProvider
// Update model ID using provider-specific key (e.g., cline uses actModeOpenRouterModelId)
const modelKey = getProviderModelIdKey(currentProvider, selectedMode)
if (modelKey) {
StateManager.get().setGlobalState(modelKey, options.model)
}
telemetryService.captureHostEvent("model_flag", options.model)
}
// Set thinking budget based on --thinking flag
const thinkingBudget = options.thinking ? 1024 : 0
const currentMode = StateManager.get().getGlobalSettingsKey("mode") || "act"
const thinkingKey = currentMode === "act" ? "actModeThinkingBudgetTokens" : "planModeThinkingBudgetTokens"
StateManager.get().setGlobalState(thinkingKey, thinkingBudget)
if (options.thinking) {
telemetryService.captureHostEvent("thinking_flag", "true")
}
// Set yolo mode based on --yolo flag
if (options.yolo) {
StateManager.get().setGlobalState("yoloModeToggled", true)
telemetryService.captureHostEvent("yolo_flag", "true")
}
// Apply shared task options (mode, model, thinking, yolo)
applyTaskOptions(options)
await StateManager.get().flushPendingState()
// Detect if output is a TTY (interactive terminal) or redirected to a file/pipe
const isTTY = process.stdout.isTTY === true
// Use plain text mode when output is redirected, stdin was piped, JSON mode is enabled, or --yolo flag is used
// Ink requires raw mode on stdin which isn't available when stdin is piped
// Note: we use the stdinWasPiped flag passed from the caller because process.stdin.isTTY
// may not be reliable after stdin has been consumed by readStdinIfPiped()
if (!isTTY || options.stdinWasPiped || options.json || options.yolo) {
// Set flag so shutdown handler knows not to clear Ink UI lines
isPlainTextMode = true
// Check if auth is configured before attempting to run the task
// In plain text mode we can't show the interactive auth flow
const hasAuth = await isAuthConfigured()
if (!hasAuth) {
printWarning("Not authenticated. Please run 'cline auth' first to configure your API credentials.")
await ctx.controller.stateManager.flushPendingState()
await ctx.controller.dispose()
await ErrorService.get().dispose()
exit(1)
}
const reason = options.yolo
? "yolo_flag"
: options.json
? "json"
: options.stdinWasPiped
? "piped_stdin"
: "redirected_output"
telemetryService.captureHostEvent("plain_text_mode", reason)
// Plain text mode: no Ink rendering, just clean text output
const success = await runPlainTextTask({
controller: ctx.controller,
if (shouldUsePlainTextMode(options)) {
return runTaskInPlainTextMode(ctx, options, {
prompt: taskPrompt,
imageDataUrls: imageDataUrls.length > 0 ? imageDataUrls : undefined,
verbose: options.verbose,
jsonOutput: options.json,
timeoutSeconds: options.timeout ? parseInt(options.timeout, 10) : undefined,
})
// Cleanup
await ctx.controller.stateManager.flushPendingState()
await ctx.controller.dispose()
await ErrorService.get().dispose()
// Ensure stdout is fully drained before exiting - critical for piping
await drainStdout()
exit(success ? 0 : 1)
}
let taskError = false
// Render the welcome view with optional initial prompt/images
// Interactive mode: Render the welcome view with optional initial prompt/images
// If prompt provided (cline task "prompt"), ChatView will auto-submit
// If no prompt (cline interactive), user will type it in
let taskError = false
await runInkApp(
React.createElement(App, {
view: "welcome",
@@ -369,20 +528,10 @@ async function runTask(
taskError = true
},
onWelcomeExit: () => {
// User pressed Esc
exit(0)
// User pressed Esc; Ink exits and cleanup handles process exit.
},
}),
async () => {
await ctx.controller.stateManager.flushPendingState()
await ctx.controller.dispose()
await ErrorService.get().dispose()
if (taskError) {
printWarning("Task ended with errors.")
exit(1)
}
exit(0)
},
createInkCleanup(ctx, () => taskError),
)
}
@@ -395,8 +544,8 @@ async function listHistory(options: { config?: string; limit?: number; page?: nu
const taskHistory = StateManager.get().getGlobalStateKey("taskHistory") || []
// Sort by timestamp (newest first) before pagination
const sortedHistory = [...taskHistory].sort((a: any, b: any) => (b.ts || 0) - (a.ts || 0))
const limit = typeof options.limit === "string" ? parseInt(options.limit, 10) : options.limit || 10
const initialPage = typeof options.page === "string" ? parseInt(options.page, 10) : options.page || 1
const limit = typeof options.limit === "string" ? Number.parseInt(options.limit, 10) : options.limit || 10
const initialPage = typeof options.page === "string" ? Number.parseInt(options.page, 10) : options.page || 1
const totalCount = sortedHistory.length
const totalPages = Math.ceil(totalCount / limit)
@@ -404,9 +553,7 @@ async function listHistory(options: { config?: string; limit?: number; page?: nu
if (sortedHistory.length === 0) {
printInfo("No task history found.")
await ctx.controller.stateManager.flushPendingState()
await ctx.controller.dispose()
await ErrorService.get().dispose()
await disposeCliContext(ctx)
exit(0)
}
@@ -420,9 +567,7 @@ async function listHistory(options: { config?: string; limit?: number; page?: nu
isRawModeSupported: checkRawModeSupport(),
}),
async () => {
await ctx.controller.stateManager.flushPendingState()
await ctx.controller.dispose()
await ErrorService.get().dispose()
await disposeCliContext(ctx)
exit(0)
},
)
@@ -451,9 +596,7 @@ async function showConfig(options: { config?: string }) {
isRawModeSupported: checkRawModeSupport(),
}),
async () => {
await ctx.controller.stateManager.flushPendingState()
await ctx.controller.dispose()
await ErrorService.get().dispose()
await disposeCliContext(ctx)
exit(0)
},
)
@@ -529,17 +672,15 @@ async function runAuth(options: {
baseurl: options.baseurl,
})
await ctx.controller.stateManager.flushPendingState()
await ctx.controller.dispose()
await ErrorService.get().dispose()
if (!result.success) {
printWarning(result.error || "Quick setup failed")
telemetryService.captureHostEvent("auth", "error")
await telemetryService.captureHostEvent("auth", "error")
await disposeCliContext(ctx)
exit(1)
}
telemetryService.captureHostEvent("auth", "completed")
await telemetryService.captureHostEvent("auth", "completed")
await disposeCliContext(ctx)
exit(0)
}
@@ -553,7 +694,6 @@ async function runAuth(options: {
isRawModeSupported: checkRawModeSupport(),
onComplete: () => {
telemetryService.captureHostEvent("auth", "completed")
exit(0)
},
onError: () => {
telemetryService.captureHostEvent("auth", "error")
@@ -561,16 +701,10 @@ async function runAuth(options: {
},
}),
async () => {
await ctx.controller.stateManager.flushPendingState()
await ctx.controller.dispose()
await ErrorService.get().dispose()
exit(0)
await disposeCliContext(ctx)
exit(authError ? 1 : 0)
},
)
if (authError) {
process.exit(1)
}
}
// Setup CLI commands
@@ -589,14 +723,23 @@ 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("-t, --timeout <seconds>", "Timeout in seconds for yes/yolo mode (default: 600)")
.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")
.option("-c, --cwd <path>", "Working directory for the task")
.option("--config <path>", "Path to Cline configuration directory")
.option("--thinking", "Enable extended thinking (1024 token budget)")
.option("--thinking [tokens]", "Enable extended thinking (default: 1024 tokens)")
.option("--reasoning-effort <effort>", "Reasoning effort: none|low|medium|high|xhigh")
.option("--max-consecutive-mistakes <count>", "Maximum consecutive mistakes before halting in yolo mode")
.option("--json", "Output messages as JSON instead of styled text")
.action((prompt, options) => runTask(prompt, options))
.option("--double-check-completion", "Reject first completion attempt to force re-verification")
.option("-T, --taskId <id>", "Resume an existing task by ID")
.action((prompt, options) => {
if (options.taskId) {
return resumeTask(options.taskId, { ...options, initialPrompt: prompt })
}
return runTask(prompt, options)
})
program
.command("history")
@@ -616,9 +759,9 @@ program
program
.command("auth")
.description("Authenticate a provider and configure what model is used")
.option("-p, --provider <id>", "Provider ID for quick setup (e.g., openai-native, anthropic)")
.option("-p, --provider <id>", "Provider ID for quick setup (e.g., openai-native, anthropic, moonshot)")
.option("-k, --apikey <key>", "API key for the provider")
.option("-m, --modelid <id>", "Model ID to configure (e.g., gpt-4o, claude-sonnet-4-5-20250929)")
.option("-m, --modelid <id>", "Model ID to configure (e.g., gpt-4o, claude-sonnet-4-6, kimi-k2.5)")
.option("-b, --baseurl <url>", "Base URL (optional, only for openai provider)")
.option("-v, --verbose", "Show verbose output")
.option("-c, --cwd <path>", "Working directory for the task")
@@ -654,7 +797,7 @@ devCommand
* If `welcomeViewCompleted` is undefined (first run), checks if ANY provider has credentials
* and sets the flag accordingly.
*/
async function isAuthConfigured(): Promise<boolean> {
export async function isAuthConfigured(): Promise<boolean> {
const stateManager = StateManager.get()
// Check welcomeViewCompleted first - this is the single source of truth
@@ -709,6 +852,67 @@ async function checkAnyProviderConfigured(): Promise<boolean> {
return false
}
/**
* Validate that a task exists in history
* @returns The task history item if found, null otherwise
*/
function findTaskInHistory(taskId: string): HistoryItem | null {
const taskHistory = StateManager.get().getGlobalStateKey("taskHistory") || []
return taskHistory.find((item) => item.id === taskId) || 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 })
// Validate task exists
const historyItem = findTaskInHistory(taskId)
if (!historyItem) {
printWarning(`Task not found: ${taskId}`)
printInfo("Use 'cline history' to see available tasks.")
await disposeCliContext(ctx)
exit(1)
}
telemetryService.captureHostEvent("resume_task_command", options.initialPrompt ? "with_prompt" : "interactive")
// Apply shared task options (mode, model, thinking, yolo)
applyTaskOptions(options)
await StateManager.get().flushPendingState()
// Use plain text mode for non-interactive scenarios
if (shouldUsePlainTextMode(options)) {
return runTaskInPlainTextMode(ctx, options, {
prompt: options.initialPrompt,
taskId: taskId,
})
}
// Interactive mode: render the task view with the existing task
let taskError = false
await runInkApp(
React.createElement(App, {
view: "task",
taskId: taskId,
verbose: options.verbose,
controller: ctx.controller,
isRawModeSupported: checkRawModeSupport(),
initialPrompt: options.initialPrompt || undefined,
onError: () => {
taskError = true
},
onWelcomeExit: () => {
// User pressed Esc; Ink exits and cleanup handles process exit.
},
}),
createInkCleanup(ctx, () => taskError),
)
}
/**
* Show welcome prompt and wait for user input
* If auth is not configured, show auth flow first
@@ -729,16 +933,14 @@ async function showWelcome(options: { verbose?: boolean; cwd?: string; config?:
controller: ctx.controller,
isRawModeSupported: checkRawModeSupport(),
onWelcomeExit: () => {
exit(0)
// User pressed Esc; Ink exits and cleanup handles process exit.
},
onError: () => {
hadError = true
},
}),
async () => {
await ctx.controller.stateManager.flushPendingState()
await ctx.controller.dispose()
await ErrorService.get().dispose()
await disposeCliContext(ctx)
exit(hadError ? 1 : 0)
},
)
@@ -750,14 +952,18 @@ 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("-t, --timeout <seconds>", "Timeout in seconds for yolo mode (default: 600)")
.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")
.option("-c, --cwd <path>", "Working directory")
.option("--config <path>", "Configuration directory")
.option("--thinking", "Enable extended thinking (1024 token budget)")
.option("--thinking [tokens]", "Enable extended thinking (default: 1024 tokens)")
.option("--reasoning-effort <effort>", "Reasoning effort: none|low|medium|high|xhigh")
.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("--acp", "Run in ACP (Agent Client Protocol) mode for editor integration")
.option("-T, --taskId <id>", "Resume an existing task by ID")
.action(async (prompt, options) => {
// Check for ACP mode first - this takes precedence over everything else
if (options.acp) {
@@ -772,8 +978,18 @@ program
// Always check for piped stdin content
const stdinInput = await readStdinIfPiped()
// Error if stdin was piped but empty (e.g., `echo "" | cline`)
if (stdinInput === "") {
// Track whether stdin was actually piped (even if empty) vs not piped (null)
// stdinInput === null means stdin wasn't piped (TTY or not FIFO/file)
// stdinInput === "" means stdin was piped but empty
// stdinInput has content means stdin was piped with data
const stdinWasPiped = stdinInput !== null
// Error if stdin was piped but empty AND no prompt was provided
// This handles:
// - `echo "" | cline` -> error (empty stdin, no prompt)
// - `cline "prompt"` in GitHub Actions -> OK (empty stdin ignored, has prompt)
// - `cat file | cline "explain"` -> OK (has stdin AND prompt)
if (stdinInput === "" && !prompt) {
printWarning("Empty input received from stdin. Please provide content to process.")
exit(1)
}
@@ -796,17 +1012,24 @@ program
}
}
// Handle --taskId flag to resume an existing task
if (options.taskId) {
await resumeTask(options.taskId, {
...options,
initialPrompt: effectivePrompt,
stdinWasPiped,
})
return
}
if (effectivePrompt) {
// Pass stdinWasPiped flag so runTask knows to use plain text mode
await runTask(effectivePrompt, { ...options, stdinWasPiped: !!stdinInput })
await runTask(effectivePrompt, { ...options, stdinWasPiped })
} else {
// Show welcome prompt if no prompt given
await showWelcome(options)
}
})
// Background auto-update check (non-blocking)
autoUpdateOnStartup(CLI_VERSION)
// Parse and run
program.parse()
+194
View File
@@ -0,0 +1,194 @@
import { describe, expect, it } from "vitest"
import { selectOutputMode } from "./mode-selection"
describe("selectOutputMode", () => {
describe("interactive mode (Ink)", () => {
it("should use interactive mode when both stdin and stdout are TTY", () => {
const result = selectOutputMode({
stdoutIsTTY: true,
stdinIsTTY: true,
stdinWasPiped: false,
})
expect(result.usePlainTextMode).toBe(false)
expect(result.reason).toBe("interactive")
})
})
describe("yolo flag", () => {
it("should use plain text mode when --yolo flag is set", () => {
const result = selectOutputMode({
stdoutIsTTY: true,
stdinIsTTY: true,
stdinWasPiped: false,
yolo: true,
})
expect(result.usePlainTextMode).toBe(true)
expect(result.reason).toBe("yolo_flag")
})
it("should prioritize yolo over other flags", () => {
const result = selectOutputMode({
stdoutIsTTY: false,
stdinIsTTY: false,
stdinWasPiped: true,
json: true,
yolo: true,
})
expect(result.reason).toBe("yolo_flag")
})
})
describe("json flag", () => {
it("should use plain text mode when --json flag is set", () => {
const result = selectOutputMode({
stdoutIsTTY: true,
stdinIsTTY: true,
stdinWasPiped: false,
json: true,
})
expect(result.usePlainTextMode).toBe(true)
expect(result.reason).toBe("json")
})
})
describe("piped stdin", () => {
it("should use plain text mode when stdin was piped (echo x | cline)", () => {
const result = selectOutputMode({
stdoutIsTTY: true,
stdinIsTTY: false, // piped stdin is not a TTY
stdinWasPiped: true,
})
expect(result.usePlainTextMode).toBe(true)
expect(result.reason).toBe("piped_stdin")
})
it("should use plain text mode when stdin was piped but empty (echo '' | cline 'prompt')", () => {
const result = selectOutputMode({
stdoutIsTTY: true,
stdinIsTTY: false,
stdinWasPiped: true, // empty pipe still counts as piped
})
expect(result.usePlainTextMode).toBe(true)
expect(result.reason).toBe("piped_stdin")
})
})
describe("stdin redirected (< /dev/null)", () => {
it("should use plain text mode when stdin is redirected from /dev/null", () => {
// cline "prompt" < /dev/null
// stdin is not a TTY, but also not a FIFO/file, so stdinWasPiped=false
const result = selectOutputMode({
stdoutIsTTY: true,
stdinIsTTY: false, // redirected, not a TTY
stdinWasPiped: false, // /dev/null is a character device, not FIFO
})
expect(result.usePlainTextMode).toBe(true)
expect(result.reason).toBe("stdin_redirected")
})
})
describe("stdout redirected", () => {
it("should use plain text mode when stdout is redirected to file", () => {
// cline "prompt" > output.txt
const result = selectOutputMode({
stdoutIsTTY: false,
stdinIsTTY: true,
stdinWasPiped: false,
})
expect(result.usePlainTextMode).toBe(true)
expect(result.reason).toBe("stdout_redirected")
})
it("should use plain text mode when stdout is piped", () => {
// cline "prompt" | grep something
const result = selectOutputMode({
stdoutIsTTY: false,
stdinIsTTY: true,
stdinWasPiped: false,
})
expect(result.usePlainTextMode).toBe(true)
expect(result.reason).toBe("stdout_redirected")
})
})
describe("GitHub Actions scenarios", () => {
it("should use plain text mode in GitHub Actions (stdin is empty FIFO)", () => {
// In GitHub Actions: stdin is an empty FIFO pipe
// stdinIsTTY=false, stdinWasPiped=true (FIFO detected)
const result = selectOutputMode({
stdoutIsTTY: true, // GitHub Actions stdout is TTY-like
stdinIsTTY: false,
stdinWasPiped: true, // empty FIFO still counts as piped
})
expect(result.usePlainTextMode).toBe(true)
})
it("should use plain text mode with --yolo in CI", () => {
const result = selectOutputMode({
stdoutIsTTY: false,
stdinIsTTY: false,
stdinWasPiped: false,
yolo: true,
})
expect(result.usePlainTextMode).toBe(true)
expect(result.reason).toBe("yolo_flag")
})
})
describe("real-world scenarios", () => {
it("cline (no args, interactive terminal)", () => {
const result = selectOutputMode({
stdoutIsTTY: true,
stdinIsTTY: true,
stdinWasPiped: false,
})
expect(result.usePlainTextMode).toBe(false)
})
it('cline "prompt" (prompt arg, interactive terminal)', () => {
const result = selectOutputMode({
stdoutIsTTY: true,
stdinIsTTY: true,
stdinWasPiped: false,
})
expect(result.usePlainTextMode).toBe(false)
})
it('cat file | cline "explain"', () => {
const result = selectOutputMode({
stdoutIsTTY: true,
stdinIsTTY: false,
stdinWasPiped: true,
})
expect(result.usePlainTextMode).toBe(true)
})
it('cline --yolo "prompt"', () => {
const result = selectOutputMode({
stdoutIsTTY: true,
stdinIsTTY: true,
stdinWasPiped: false,
yolo: true,
})
expect(result.usePlainTextMode).toBe(true)
})
it('cline "prompt" < /dev/null', () => {
const result = selectOutputMode({
stdoutIsTTY: true,
stdinIsTTY: false,
stdinWasPiped: false,
})
expect(result.usePlainTextMode).toBe(true)
})
it('cline "prompt" > output.log', () => {
const result = selectOutputMode({
stdoutIsTTY: false,
stdinIsTTY: true,
stdinWasPiped: false,
})
expect(result.usePlainTextMode).toBe(true)
})
})
})
+63
View File
@@ -0,0 +1,63 @@
/**
* Mode selection logic for CLI - determines whether to use Ink (interactive) or plain text mode
*
* This is extracted as a pure function for testability. The decision tree:
* - Plain text mode when output is redirected (stdout not TTY)
* - Plain text mode when input is redirected (stdin not TTY) - Ink requires raw mode
* - Plain text mode when stdin was piped (e.g., echo "x" | cline)
* - Plain text mode when --json flag is used
* - Plain text mode when --yolo flag is used
* - Otherwise: Interactive Ink mode
*/
export interface ModeSelectionInput {
/** Is stdout connected to a TTY (interactive terminal)? */
stdoutIsTTY: boolean
/** Is stdin connected to a TTY (interactive terminal)? */
stdinIsTTY: boolean
/** Was stdin piped (FIFO or file), even if empty? */
stdinWasPiped: boolean
/** --json flag for machine-readable output */
json?: boolean
/** --yolo flag for auto-approve mode */
yolo?: boolean
}
export interface ModeSelectionResult {
/** Use plain text mode instead of Ink */
usePlainTextMode: boolean
/** Reason for the mode selection (for telemetry/debugging) */
reason: "interactive" | "yolo_flag" | "json" | "piped_stdin" | "stdin_redirected" | "stdout_redirected"
}
/**
* Determine whether to use plain text mode or interactive Ink mode
*
* @param input - Environment and option flags
* @returns Mode selection result with reason
*/
export function selectOutputMode(input: ModeSelectionInput): ModeSelectionResult {
// Priority order matters - check most specific flags first
if (input.yolo) {
return { usePlainTextMode: true, reason: "yolo_flag" }
}
if (input.json) {
return { usePlainTextMode: true, reason: "json" }
}
if (input.stdinWasPiped) {
return { usePlainTextMode: true, reason: "piped_stdin" }
}
if (!input.stdinIsTTY) {
return { usePlainTextMode: true, reason: "stdin_redirected" }
}
if (!input.stdoutIsTTY) {
return { usePlainTextMode: true, reason: "stdout_redirected" }
}
return { usePlainTextMode: false, reason: "interactive" }
}
+28
View File
@@ -0,0 +1,28 @@
import { afterEach, describe, expect, it, vi } from "vitest"
import { emitTaskStartedMessage } from "./task-start-output"
describe("emitTaskStartedMessage", () => {
afterEach(() => {
vi.restoreAllMocks()
})
it("writes structured task_started JSON to stdout in json mode", () => {
const stdoutWriteSpy = vi.spyOn(process.stdout, "write").mockImplementation(() => true)
const stderrWriteSpy = vi.spyOn(process.stderr, "write").mockImplementation(() => true)
emitTaskStartedMessage("task-123", true)
expect(stdoutWriteSpy).toHaveBeenCalledWith('{"type":"task_started","taskId":"task-123"}\n')
expect(stderrWriteSpy).not.toHaveBeenCalled()
})
it("writes human-readable task started line to stderr in non-json mode", () => {
const stdoutWriteSpy = vi.spyOn(process.stdout, "write").mockImplementation(() => true)
const stderrWriteSpy = vi.spyOn(process.stderr, "write").mockImplementation(() => true)
emitTaskStartedMessage("task-456", false)
expect(stderrWriteSpy).toHaveBeenCalledWith("Task started: task-456\n")
expect(stdoutWriteSpy).not.toHaveBeenCalled()
})
})
+67 -10
View File
@@ -12,18 +12,24 @@
// Console output is intentional here for plain text mode
import type { ClineMessage, ExtensionState } from "@shared/ExtensionMessage"
import { StringRequest } from "@shared/proto/cline/common"
import type { Controller } from "@/core/controller"
import { getRequestRegistry } from "@/core/controller/grpc-handler"
import { subscribeToState } from "@/core/controller/state/subscribeToState"
import { showTaskWithId } from "@/core/controller/task/showTaskWithId"
import { emitTaskStartedMessage } from "./task-start-output"
export interface PlainTextTaskOptions {
controller: Controller
prompt: string
/** Prompt for new task or message to send to resumed task */
prompt?: string
imageDataUrls?: string[]
verbose?: boolean
jsonOutput?: boolean
/** Timeout in seconds (default: 600 = 10 minutes) */
/** Timeout in seconds (only applied when explicitly provided) */
timeoutSeconds?: number
/** Task ID to resume an existing task */
taskId?: string
}
/**
@@ -39,17 +45,39 @@ export interface PlainTextTaskOptions {
export async function runPlainTextTask(options: PlainTextTaskOptions): Promise<boolean> {
const { controller, prompt, imageDataUrls, verbose, jsonOutput } = options
let completionResolve: () => void
let completionResolve: (reason?: any) => void
let completionReject: (reason?: any) => void
const completionPromise = new Promise<void>((res, rej) => {
const completionPromise = new Promise<string>((res, rej) => {
completionResolve = res
completionReject = rej
})
let hasError = false
let hasEmittedTaskStarted = false
// Track which messages have been processed (by timestamp)
const processedMessages = new Map<number, string>()
const isViewTaskOnly = Boolean(options.taskId) && !prompt
// When resuming a task, we need to ignore completion_result messages that existed
// before we sent our new prompt. This timestamp marks the cutoff - only completion
// results AFTER this time should trigger task completion.
const completionCutoffTs = Date.now()
const emitTaskStarted = () => {
if (hasEmittedTaskStarted) {
return
}
const taskId = controller.task?.taskId
if (!taskId) {
return
}
emitTaskStartedMessage(taskId, Boolean(jsonOutput))
hasEmittedTaskStarted = true
}
// Helper to process a message and track completion state
const processMessage = (message: ClineMessage) => {
const ts = message.ts || 0
@@ -67,8 +95,12 @@ export async function runPlainTextTask(options: PlainTextTaskOptions): Promise<b
processedMessages.set(ts, message.text ?? "")
// Check for completion (only on non-partial messages)
// When resuming a task, only consider completion_result messages that appeared
// AFTER we sent our resume message (ts > completionCutoffTs)
if (message.say === "completion_result" || message.ask === "completion_result") {
completionResolve()
if (isViewTaskOnly || ts > completionCutoffTs) {
completionResolve()
}
} else if (message.say === "error" || message.ask === "api_req_failed") {
completionReject(message.text ?? "message.say error || message.ask api_req_failed")
}
@@ -99,11 +131,36 @@ export async function runPlainTextTask(options: PlainTextTaskOptions): Promise<b
)
try {
// Start the task
await controller.initTask(prompt, imageDataUrls)
const timeoutMs = (options.timeoutSeconds ?? 600) * 1000 // default 10 minutes
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), timeoutMs))
await Promise.race([completionPromise, timeoutPromise])
// Either resume an existing task or start a new one
if (options.taskId) {
// Load the existing task
await showTaskWithId(controller, StringRequest.create({ value: options.taskId }))
emitTaskStarted()
// If a prompt was provided, send it as a message to the resumed task
if (prompt && controller.task) {
// Wait a moment for the task to fully load
await new Promise((resolve) => setTimeout(resolve, 100))
// Send the prompt as a response to any pending ask, or as a new message
await controller.task.handleWebviewAskResponse("messageResponse", prompt)
}
} else if (prompt) {
// Start a new task with the prompt
await controller.initTask(prompt, imageDataUrls)
emitTaskStarted()
} else {
throw new Error("Either taskId or prompt must be provided")
}
// Wait for task completion, with optional timeout only when explicitly configured
if (options.timeoutSeconds) {
const timeoutMs = options.timeoutSeconds * 1000
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), timeoutMs))
await Promise.race([completionPromise, timeoutPromise])
} else {
await completionPromise
}
} catch (error) {
const errMsg = error instanceof Error ? error.message : String(error)
if (jsonOutput) {
+16 -1
View File
@@ -80,15 +80,18 @@ export async function applyProviderConfig(options: ApplyProviderConfigOptions):
export interface ApplyBedrockConfigOptions {
bedrockConfig: BedrockConfig
modelId?: string
customModelBaseId?: string // Base model ID for custom ARN/Inference Profile (for capability detection)
controller?: Controller
}
/**
* Apply Bedrock provider configuration to state
* Handles AWS-specific fields (authentication, region, credentials)
* When customModelBaseId is provided, sets the custom model flags so the system
* knows to use the ARN as the model ID and the base model for capability detection.
*/
export async function applyBedrockConfig(options: ApplyBedrockConfigOptions): Promise<void> {
const { bedrockConfig, modelId, controller } = options
const { bedrockConfig, modelId, customModelBaseId, controller } = options
const stateManager = StateManager.get()
const config: Record<string, unknown> = {
@@ -108,6 +111,18 @@ export async function applyBedrockConfig(options: ApplyBedrockConfigOptions): Pr
if (planModelKey) config[planModelKey] = finalModelId
}
// Handle custom model (Application Inference Profile ARN)
if (customModelBaseId) {
config.actModeAwsBedrockCustomSelected = true
config.planModeAwsBedrockCustomSelected = true
config.actModeAwsBedrockCustomModelBaseId = customModelBaseId
config.planModeAwsBedrockCustomModelBaseId = customModelBaseId
} else {
// Ensure custom flags are cleared when using a standard model
config.actModeAwsBedrockCustomSelected = false
config.planModeAwsBedrockCustomSelected = false
}
// Add optional AWS credentials
if (bedrockConfig.awsProfile !== undefined) config.awsProfile = bedrockConfig.awsProfile
if (bedrockConfig.awsAccessKey) config.awsAccessKey = bedrockConfig.awsAccessKey
+21 -2
View File
@@ -3,7 +3,10 @@
* Used by both UI components and CLI commands
*/
import { useMemo } from "react"
import { StateManager } from "@/core/storage/StateManager"
import providersData from "@/shared/providers/providers.json"
import type { RemoteConfigFields } from "@/shared/storage/state-keys"
// Create a lookup map from provider value to display label
const providerLabels: Record<string, string> = Object.fromEntries(
@@ -17,7 +20,7 @@ const providerOrder: string[] = providersData.list.map((p: { value: string }) =>
* Providers that are not supported in CLI.
* - vscode-lm: Requires VS Code's Language Model API (see ENG-1490 for OAuth-based support)
*/
export const CLI_EXCLUDED_PROVIDERS = new Set<string>(["vscode-lm"])
const CLI_EXCLUDED_PROVIDERS = new Set<string>(["vscode-lm"])
/**
* Get the display label for a provider ID
@@ -29,7 +32,7 @@ export function getProviderLabel(providerId: string): string {
/**
* Get the ordered list of all provider IDs (from providers.json)
*/
export function getProviderOrder(): string[] {
function getProviderOrder(): string[] {
return providerOrder
}
@@ -46,3 +49,19 @@ export function getValidCliProviders(): string[] {
export function isValidCliProvider(providerId: string): boolean {
return providerOrder.includes(providerId) && !CLI_EXCLUDED_PROVIDERS.has(providerId)
}
const getValidProviders = (remoteConfig: Partial<RemoteConfigFields> | undefined) => {
if (remoteConfig?.remoteConfiguredProviders?.length) {
return remoteConfig.remoteConfiguredProviders
}
return getProviderOrder().filter((p: string) => !CLI_EXCLUDED_PROVIDERS.has(p))
}
export const useValidProviders = () => {
const remoteConfig = StateManager.get().getRemoteConfigSettings()
return useMemo(() => {
return getValidProviders(remoteConfig)
}, [remoteConfig])
}
+8
View File
@@ -0,0 +1,8 @@
export function emitTaskStartedMessage(taskId: string, jsonOutput: boolean): void {
if (jsonOutput) {
process.stdout.write(JSON.stringify({ type: "task_started", taskId }) + "\n")
return
}
process.stderr.write(`Task started: ${taskId}\n`)
}
+36
View File
@@ -0,0 +1,36 @@
/**
* Wait for a condition to become truthy, with a timeout.
* Uses Promise.race for clean timeout handling instead of polling.
*
* @param condition - Function that returns the value to check (truthy = done)
* @param timeoutMs - Maximum time to wait in milliseconds
* @param pollIntervalMs - How often to check the condition (default: 100ms)
* @returns The truthy value if condition is met, or undefined if timeout
*/
export async function waitFor<T>(
condition: () => T | undefined | null,
timeoutMs: number,
pollIntervalMs: number = 100,
): Promise<T | undefined> {
// Check immediately first
const immediate = condition()
if (immediate) {
return immediate
}
return new Promise((resolve) => {
const intervalId = setInterval(() => {
const result = condition()
if (result) {
clearInterval(intervalId)
clearTimeout(timeoutId)
resolve(result)
}
}, pollIntervalMs)
const timeoutId = setTimeout(() => {
clearInterval(intervalId)
resolve(undefined)
}, timeoutMs)
})
}
+7 -1
View File
@@ -1,6 +1,7 @@
import { spawn } from "node:child_process"
import { realpathSync } from "node:fs"
import { exit } from "node:process"
import { ClineEndpoint } from "@/config"
import { fetch } from "@/shared/net"
import { printInfo, printWarning } from "./display"
@@ -107,7 +108,7 @@ async function getLatestVersion(currentVersion: string): Promise<string | null>
* process to install if a newer version is available.
*
* Supports npm, pnpm, yarn, and bun global installs.
* Skipped for npx, local dev, and unknown installations.
* Skipped for npx, local dev, unknown installations, and bundled enterprise packages.
* Can be disabled with CLINE_NO_AUTO_UPDATE=1 environment variable.
*/
export function autoUpdateOnStartup(currentVersion: string): void {
@@ -121,6 +122,11 @@ export function autoUpdateOnStartup(currentVersion: string): void {
return
}
// Skip if using bundled enterprise config (single source of truth)
if (ClineEndpoint.isBundledConfig()) {
return
}
const { updateCommand } = getInstallationInfo(currentVersion)
if (!updateCommand) {
return
+62 -93
View File
@@ -1,17 +1,20 @@
/**
* VSCode context stub for CLI mode
* Provides mock implementations of VSCode extension context
* Provides mock implementations of VSCode extension context.
*/
import { mkdirSync } from "node:fs"
import { fileURLToPath } from "node:url"
import os from "os"
import path from "path"
import { ExtensionRegistryInfo } from "@/registry"
import { ClineExtensionContext } from "@/shared/cline"
import { ClineFileStorage } from "@/shared/storage"
import type { ClineMemento } from "@/shared/storage/ClineStorage"
import { createStorageContext, type StorageContext } from "@/shared/storage/storage-context"
import { EnvironmentVariableCollection, ExtensionKind, ExtensionMode, readJson, URI } from "./vscode-shim"
const SETTINGS_SUBFOLDER = "data"
// ES module equivalent of __dirname
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
/**
* CLI-specific state overrides.
@@ -30,33 +33,43 @@ const CLI_STATE_OVERRIDES: Record<string, any> = {
}
/**
* File-based Memento store with optional key overrides.
* Implements VSCode's Memento interface using SyncJsonFileStorage.
* Memento adapter that wraps a ClineFileStorage with optional key overrides.
* Used for globalState where CLI needs to inject hardcoded overrides.
*/
class MementoStore extends ClineFileStorage {
private overrides: Record<string, any>
class MementoAdapter implements ClineMemento {
constructor(
private readonly store: ClineMemento,
private readonly overrides: Record<string, any> = {},
) {}
constructor(filePath: string, overrides: Record<string, any> = {}) {
super(filePath, "MementoStore")
this.overrides = overrides
}
// VSCode Memento interface - override base class get() with overload support
override get<T>(key: string): T | undefined
override get<T>(key: string, defaultValue: T): T
override get<T>(key: string, defaultValue?: T): T | undefined {
get<T>(key: string): T | undefined
get<T>(key: string, defaultValue: T): T
get<T>(key: string, defaultValue?: T): T | undefined {
if (key in this.overrides) {
return this.overrides[key] as T
}
const value = super.get<T>(key)
const value = this.store.get<T>(key)
return value !== undefined ? value : defaultValue
}
override async update(key: string, value: any): Promise<void> {
if (key in this.overrides) {
return
update(key: string, value: any): Thenable<void> {
return this.setBatch({ [key]: value })
}
keys(): readonly string[] {
return this.store.keys()
}
setBatch(entries: Record<string, any>): Thenable<void> {
// Filter out overridden keys and delegate to underlying store
const filteredEntries: Record<string, any> = {}
for (const [key, value] of Object.entries(entries)) {
if (!(key in this.overrides)) {
filteredEntries[key] = value
}
}
this.set(key, value)
this.store.setBatch(filteredEntries)
return Promise.resolve()
}
setKeysForSync(_keys: readonly string[]): void {
@@ -64,84 +77,48 @@ class MementoStore extends ClineFileStorage {
}
}
/**
* File-based secret storage implementing VSCode's SecretStorage interface.
* Uses sync storage internally but exposes async API for VSCode compatibility.
*/
class SecretStore {
private storage: ClineFileStorage<string>
private onDidChangeEmitter = {
event: () => ({ dispose: () => {} }),
fire: (_e: any) => {},
dispose: () => {},
}
onDidChange = this.onDidChangeEmitter.event
constructor(filePath: string) {
this.storage = new ClineFileStorage<string>(filePath, "SecretStore")
}
get(key: string): Promise<string | undefined> {
return Promise.resolve(this.storage.get(key))
}
store(key: string, value: string): Promise<void> {
this.storage.set(key, value)
return Promise.resolve()
}
delete(key: string): Promise<void> {
this.storage.delete(key)
return Promise.resolve()
}
}
export interface CliContextConfig {
clineDir?: string
/** The workspace directory being worked in (for hashing into storage path) */
/** The workspace directory being worked in (used to compute workspace storage hash) */
workspaceDir?: string
}
/**
* Create a short hash of a string for use in directory names
*/
function hashString(str: string): string {
let hash = 0
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i)
hash = (hash << 5) - hash + char
hash = hash & hash // Convert to 32bit integer
}
return Math.abs(hash).toString(16).substring(0, 8)
}
export interface CliContextResult {
extensionContext: ClineExtensionContext
storageContext: StorageContext
DATA_DIR: string
EXTENSION_DIR: string
WORKSPACE_STORAGE_DIR: string
}
/**
* Initialize the VSCode-like context for CLI mode
* Initialize the VSCode-like context for CLI mode.
*
* Creates a shared StorageContext (the single source of truth for all storage)
* and wraps it in a ClineExtensionContext shell for legacy APIs that still
* expect the VSCode ExtensionContext shape.
*/
export function initializeCliContext(config: CliContextConfig = {}): CliContextResult {
const CLINE_DIR = config.clineDir || process.env.CLINE_DIR || path.join(os.homedir(), ".cline")
const DATA_DIR = path.join(CLINE_DIR, SETTINGS_SUBFOLDER)
// Workspace storage should always be under ~/.cline/data/workspaces/<hash>/
// where hash is derived from the workspace path to keep workspaces isolated
const workspacePath = config.workspaceDir || process.cwd()
const workspaceHash = hashString(workspacePath)
const WORKSPACE_STORAGE_DIR = process.env.WORKSPACE_STORAGE_DIR || path.join(DATA_DIR, "workspaces", workspaceHash)
// Create the shared StorageContext — this owns all ClineFileStorage instances.
// CLI, JetBrains, and VSCode all share this same file-backed implementation.
let storageContext = createStorageContext({
clineDir: CLINE_DIR,
workspacePath: config.workspaceDir || process.cwd(),
workspaceStorageDir: process.env.WORKSPACE_STORAGE_DIR || undefined,
})
storageContext = {
...storageContext,
// Storage — delegates to storageContext stores (with CLI overrides for globalState)
globalState: new MementoAdapter(storageContext.globalState, CLI_STATE_OVERRIDES),
}
// Ensure directories exist
mkdirSync(DATA_DIR, { recursive: true })
mkdirSync(WORKSPACE_STORAGE_DIR, { recursive: true })
const DATA_DIR = storageContext.dataDir
const WORKSPACE_STORAGE_DIR = storageContext.workspaceStoragePath
// For CLI, extension dir is the root of the project (parent of cli)
const EXTENSION_DIR = path.resolve(__dirname, "..", "..")
// For CLI, extension dir is the package root (one level up from dist/)
const EXTENSION_DIR = path.resolve(__dirname, "..")
const EXTENSION_MODE = process.env.IS_DEV === "true" ? ExtensionMode.Development : ExtensionMode.Production
const extension: ClineExtensionContext["extension"] = {
@@ -155,38 +132,30 @@ export function initializeCliContext(config: CliContextConfig = {}): CliContextR
extensionKind: ExtensionKind.UI,
}
// Build the ClineExtensionContext shell. All storage delegates to storageContext —
// there are NO separate ClineFileStorage instances here.
const extensionContext: ClineExtensionContext = {
extension: extension,
extensionMode: EXTENSION_MODE,
// Set up KV stores (globalState has CLI-specific overrides)
globalState: new MementoStore(path.join(DATA_DIR, "globalState.json"), CLI_STATE_OVERRIDES),
secrets: new SecretStore(path.join(DATA_DIR, "secrets.json")),
// Set up URIs
// URIs / paths
storageUri: URI.file(WORKSPACE_STORAGE_DIR),
storagePath: WORKSPACE_STORAGE_DIR,
globalStorageUri: URI.file(DATA_DIR),
globalStoragePath: DATA_DIR,
// Logs
logUri: URI.file(DATA_DIR),
logPath: DATA_DIR,
extensionUri: URI.file(EXTENSION_DIR),
extensionPath: EXTENSION_DIR,
asAbsolutePath: (relPath: string) => path.join(EXTENSION_DIR, relPath),
subscriptions: [],
environmentVariableCollection: new EnvironmentVariableCollection() as any,
// Workspace state
workspaceState: new MementoStore(path.join(WORKSPACE_STORAGE_DIR, "workspaceState.json")),
}
return {
extensionContext,
storageContext,
DATA_DIR,
EXTENSION_DIR,
WORKSPACE_STORAGE_DIR,
+1
View File
@@ -13,6 +13,7 @@ export default defineConfig({
},
resolve: {
alias: {
vscode: path.resolve(__dirname, "src/vscode-shim.ts"),
// Match tsconfig paths - baseUrl is parent directory
"@": path.resolve(__dirname, "../src"),
"@api": path.resolve(__dirname, "../src/core/api"),
+4 -4
View File
@@ -202,15 +202,15 @@ If Cline can't access files or run commands:
Learn about Cline CLI's core capabilities and use cases.
</Card>
<Card title="Three Core Flows" icon="route" href="/cline-cli/three-core-flows">
Master interactive mode, headless automation, and multi-instance workflows.
<Card title="Headless Mode" icon="robot" href="/cline-cli/three-core-flows">
Run Cline autonomously in scripts, CI/CD pipelines, and automated workflows.
</Card>
<Card title="Skills" icon="graduation-cap" href="/features/skills">
<Card title="Skills" icon="graduation-cap" href="/customization/skills">
Understand how Cline's Skills work across all editors via ACP.
</Card>
<Card title="Hooks" icon="link" href="/features/hooks/index">
<Card title="Hooks" icon="link" href="/customization/hooks">
Learn how to enforce policies with Hooks in any editor.
</Card>
</Columns>
-482
View File
@@ -1,482 +0,0 @@
---
title: "CLI Reference (Deprecated)"
description: "Command reference for Cline CLI versions earlier than 2.0.0 (deprecated). For the latest commands and options, see the current Cline CLI reference."
---
Complete command reference for Cline CLI. Use this for detailed documentation on all commands, options, and configuration.
For quick help in your terminal:
```bash
cline --help # Show all commands
cline task --help # Show task-specific commands
man cline # View the full manual page
```
## Manual Page
The complete manual page for the Cline CLI:
```
CLINE(1) User Commands CLINE(1)
NAME
cline - orchestrate and interact with Cline AI coding agents
SYNOPSIS
cline [prompt] [options]
cline command [subcommand] [options] [arguments]
DESCRIPTION
Try: cat README.md | cline "Summarize this for me:"
cline is a command-line interface for orchestrating multiple Cline AI
coding agents. Cline is an autonomous AI agent who can read, write,
and execute code across your projects. He operates through a
client-server architecture where Cline Core runs as a standalone
service, and the CLI acts as a scriptable interface for managing tasks,
instances, and agent interactions.
The CLI is designed for both interactive use and automation, making it
ideal for CI/CD pipelines, parallel task execution, and terminal-based
workflows. Multiple frontends (CLI, VSCode, JetBrains) can attach to
the same Cline Core instance, enabling seamless task handoff between
environments.
MODES OF OPERATION
Instant Task Mode
The simplest invocation: cline "prompt here" immediately spawns
an instance, creates a task, and enters chat mode. This is
equivalent to running cline instance new && cline task new &&
cline task chat in sequence.
Subcommand Mode
Advanced usage with explicit control: cline <command>
[subcommand] [options] provides fine-grained control over
instances, tasks, authentication, and configuration.
AGENT BEHAVIOR
Cline operates in two primary modes:
ACT MODE
Cline actively uses tools to accomplish tasks. He can read
files, write code, execute commands, use a headless browser, and
more. This is the default mode for task execution.
PLAN MODE
Cline gathers information and creates a detailed plan before
implementation. He explores the codebase, asks clarifying
questions, and presents a strategy for user approval before
switching to ACT MODE.
INSTANT TASK OPTIONS
When using the instant task syntax cline "prompt" the following options
are available:
-o, --oneshot
Full autonomous mode. Cline completes the task and stops
following after completion. Example: cline -o "what's 6 + 8?"
-s, --setting setting value
Override a setting for this task
-y, --no-interactive, --yolo
Enable fully autonomous mode. Disables all interactivity:
• ask_followup_question tool is disabled
• attempt_completion happens automatically
• execute_command runs in non-blocking mode with timeout
• PLAN MODE automatically switches to ACT MODE
-m, --mode mode
Starting mode. Options: act (default), plan
-w, --workspace path
Additional workspace paths. Can be specified multiple times to
include multiple directories. The current working directory is
always included as the first workspace. Example: cline -w
/path/to/other/project "refactor shared code"
GLOBAL OPTIONS
These options apply to all subcommands:
-F, --output-format format
Output format. Options: rich (default), json, plain
-h, --help
Display help information for the command.
-v, --verbose
Enable verbose output for debugging.
COMMANDS
Authentication
cline auth [provider] [key]
cline a [provider] [key]
Configure authentication for AI model providers. Launches an
interactive wizard if no arguments provided. If provider is
specified without a key, prompts for the key or launches the
appropriate OAuth flow.
Instance Management
Cline Core instances are independent agent processes that can run in
the background. Multiple instances can run simultaneously, enabling
parallel task execution.
cline instance
cline i
Display instance management help.
cline instance new [-d|--default]
cline i n [-d|--default]
Spawn a new Cline Core instance. Use --default to set it as
the default instance for subsequent commands.
cline instance list
cline i l
List all running Cline Core instances with their addresses and
status.
cline instance default address
cline i d address
Set the default instance to avoid specifying --address in task
commands.
cline instance kill address [-a|--all]
cline i k address [-a|--all]
Terminate a Cline Core instance. Use --all to kill all running
instances.
Task Management
Tasks represent individual work items that Cline executes. Tasks
maintain conversation history, checkpoints, and settings.
cline task [-a|--address ADDR]
cline t [-a|--address ADDR]
Display task management help. The --address flag specifies
which Cline Core instance to use (e.g., localhost:50052).
cline task new prompt [options]
cline t n prompt [options]
Create a new task in the default or specified instance.
Options:
-s, --setting setting value
Set task-specific settings
-y, --no-interactive, --yolo
Enable autonomous mode
-m, --mode mode
Starting mode (act or plan)
cline task open task-id [options]
cline t o task-id [options]
Resume a previous task from history. Accepts the same options
as task new.
cline task list
cline t l
List all tasks in history with their id and snippet
cline task chat
cline t c
Enter interactive chat mode for the current task. Allows
back-and-forth conversation with Cline.
cline task send [message] [options]
cline t s [message] [options]
Send a message to Cline. If no message is provided, reads from
stdin. Options:
-a, --approve
Approve Cline's proposed action
-d, --deny
Deny Cline's proposed action
-f, --file FILE
Attach a file to the message
-y, --no-interactive, --yolo
Enable autonomous mode
-m, --mode mode
Switch mode (act or plan)
cline task view [-f|--follow] [-c|--follow-complete]
cline t v [-f|--follow] [-c|--follow-complete]
Display the current conversation. Use --follow to stream
updates in real-time, or --follow-complete to follow until task
completion.
cline task restore checkpoint
cline t r checkpoint
Restore the task to a previous checkpoint state.
cline task pause
cline t p
Pause task execution.
Configuration
Configuration can be set globally. Override these global settings for
a task using the --setting flag
cline config
cline c
cline config set key value
cline c s key value
Set a configuration variable.
cline config get key
cline c g key
Read a configuration variable.
cline config list
cline c l
List all configuration variables and their values.
Context Window Configuration
For local model providers, you can configure the context window size:
Ollama
cline config s ollama-api-options-ctx-num=32768
LM Studio
cline config s lm-studio-max-tokens=32768
For other providers (Anthropic, OpenRouter, etc.), the context window
is defined per model in the model metadata and is not user-settable.
Cline uses each model's built-in context limits automatically.
TASK SETTINGS
Task settings are persisted in the ~/.cline/x/tasks directory. When
resuming a task with cline task open, task settings are automatically
restored.
Common settings include:
yolo Enable autonomous mode (true/false)
mode Starting mode (act/plan)
hooks_enabled
Enable or disable hooks for the task (true/false)
HOOKS INTEGRATION
Hooks let you inject custom logic into Cline's workflow at key moments.
They can validate operations before they execute, monitor tool usage,
and shape AI decisions. This allows you to integrate hooks into
automated workflows, CI/CD pipelines, and headless task execution.
Enable hooks for a task:
cline "prompt" -s hooks_enabled=true
Configure hooks globally:
cline config set hooks-enabled=true
cline config get hooks-enabled
Note: Hooks in the CLI are only supported on macOS and Linux.
For complete hooks documentation, see:
<https://docs.cline.bot/features/hooks/index>
NOTES & EXAMPLES
The cline task send and cline task new commands support reading from
stdin, enabling powerful pipeline compositions:
cat requirements.txt | cline task send
echo "Refactor this code" | cline -y
Instance Management
Manage multiple Cline instances:
# Start a new instance and make it default
cline instance new --default
# List all running instances
cline instance list
# Kill a specific instance
cline instance kill localhost:50052
# Kill all CLI instances
cline instance kill --all-cli
Task History
Work with task history:
# List previous tasks
cline task list
# Resume a previous task
cline task open 1760501486669
# View conversation history
cline task view
# Start interactive chat with this task
cline task chat
ARCHITECTURE
Cline operates on a three-layer architecture:
Presentation Layer
User interfaces (CLI, VSCode, JetBrains) that connect to Cline
Core via gRPC
Cline Core
The autonomous agent service handling task management, AI model
integration, state management, tool orchestration, and real-time
streaming updates
Host Provider Layer
Environment-specific integrations (VSCode APIs, JetBrains APIs,
shell APIs) that Cline Core uses to interact with the host
system
BUGS
Report bugs at: <https://github.com/cline/cline/issues>
For real-time help, join the Discord community at:
<https://discord.gg/cline>
SEE ALSO
Full documentation: <https://docs.cline.bot>
AUTHORS
Cline is developed by the Cline Bot Inc. and the open source community.
COPYRIGHT
Copyright © 2025 Cline Bot Inc. Licensed under the Apache License 2.0.
```
## JSON output (-F json)
When you run a command with `-F json` (or `--output-format json`), Cline prints each client message as JSON.
### ClineMessage schema
| Field | Type | Required | Notes |
|-------|------|----------|-------|
| `type` | `"ask" or "say"` | Yes | Top-level message category. |
| `text` | `string` | Yes | Human-readable message content. |
| `ts` | `number` | Yes | Unix epoch timestamp in milliseconds. |
| `reasoning` | `string` | No | Omitted when empty. |
| `say` | `string` | No | Omitted when empty. Present when `type` is `"say"`. |
| `ask` | `string` | No | Omitted when empty. Present when `type` is `"ask"`. |
| `partial` | `boolean` | No | Omitted when false. `true` for streaming updates. |
| `images` | `string[]` | No | Omitted when empty. Image URIs when included with a message. |
| `files` | `string[]` | No | Omitted when empty. File paths when attached to a message. |
| `lastCheckpointHash` | `string` | No | Omitted when empty. Git checkpoint hash when available. |
| `isCheckpointCheckedOut` | `boolean` | No | Omitted when false. `true` if Cline checked out a checkpoint. |
| `isOperationOutsideWorkspace` | `boolean` | No | Omitted when false. `true` if an operation happened outside the workspace. |
<Note>
Most fields are optional and omitted when empty. If you parse this output, treat missing fields as “not present”, not as empty strings.
</Note>
### Example
```json
{
"type": "say",
"text": "Cline is about to run a command.",
"ts": 1760501486669,
"say": "command",
"partial": false
}
```
### Shell Completion
Generate autocompletion scripts for various shells:
#### Bash
```bash
# Generate bash completion
cline completion bash > /etc/bash_completion.d/cline
# Or for user-level installation
cline completion bash > ~/.local/share/bash-completion/completions/cline
```
#### Zsh
```bash
# Generate zsh completion
cline completion zsh > "${fpath[1]}/_cline"
# Or add to your .zshrc
echo 'source <(cline completion zsh)' >> ~/.zshrc
```
#### Fish
```bash
# Generate fish completion
cline completion fish > ~/.config/fish/completions/cline.fish
```
#### PowerShell
```powershell
# Generate PowerShell completion
cline completion powershell > cline.ps1
# Add to your PowerShell profile
Add-Content $PROFILE "cline completion powershell | Out-String | Invoke-Expression"
```
### Version Command
```bash
# Show version information
cline version
```
### Environment Variables
#### CLINE_DIR
Override the default Cline directory location:
```bash
# Override default Cline directory
export CLINE_DIR=/custom/path
# Default: ~/.cline
```
This directory is used for:
- Instance registry database
- Configuration files
- Task history
- Checkpoints
+3 -5
View File
@@ -3,8 +3,6 @@ title: "CLI Reference"
description: "Complete command reference for Cline CLI including all commands, flags, and configuration options"
---
# CLI Reference
This page documents all available commands, flags, and configuration options for Cline CLI. For quick help in your terminal, use:
```bash
@@ -316,7 +314,7 @@ When using `--json`, each message is output as a JSON object (one per line):
Cline stores all data in `~/.cline/` by default:
```
```text
~/.cline/
├── data/ # Configuration directory
│ ├── globalState.json # Global settings
@@ -416,8 +414,8 @@ cline auth -p openai -k your-key -b https://api.example.com/v1
Keyboard shortcuts, slash commands, and file mentions.
</Card>
<Card title="CLI Workflows" icon="route" href="/cline-cli/three-core-flows">
Interactive mode, direct execution, and automation patterns.
<Card title="Headless Mode" icon="robot" href="/cline-cli/three-core-flows">
Run Cline autonomously in scripts, CI/CD pipelines, and automated workflows.
</Card>
<Card title="Configuration" icon="gear" href="/cline-cli/configuration">
+48 -6
View File
@@ -38,7 +38,7 @@ Rules help Cline understand your project's conventions, coding standards, and pr
### Workflows Tab
View and manage [workflows](/features/slash-commands/workflows/index):
View and manage [workflows](/customization/workflows):
- List available workflows
- View workflow definitions
@@ -46,7 +46,7 @@ View and manage [workflows](/features/slash-commands/workflows/index):
### Hooks Tab
Configure [hooks](/features/hooks/index) for custom logic integration:
Configure [hooks](/customization/hooks) for custom logic integration:
- Enable/disable hooks globally
- View configured hook scripts
@@ -58,7 +58,7 @@ Hooks must be enabled via settings. Use `cline config` to toggle `hooks-enabled`
### Skills Tab
Manage [skills](/features/skills) that extend Cline's capabilities:
Manage [skills](/customization/skills) that extend Cline's capabilities:
- View available skills
- Enable/disable specific skills
@@ -68,11 +68,13 @@ Manage [skills](/features/skills) that extend Cline's capabilities:
Cline stores configuration in `~/.cline/data/`:
```
```text
~/.cline/
├── data/ # Configuration directory
│ ├── globalState.json # Global settings
│ ├── secrets.json # API keys (encrypted)
│ ├── settings/ # Settings files
│ │ └── cline_mcp_settings.json # MCP server configuration
│ ├── workspace/ # Workspace-specific state
│ └── tasks/ # Task history and data
└── log/ # Log files
@@ -172,6 +174,46 @@ cline --config ~/.cline-work "review this PR"
cline --config ~/.cline-personal "help me with this side project"
```
## MCP Server Configuration
Cline CLI supports [MCP (Model Context Protocol)](/mcp/mcp-overview) servers, giving you access to external tools and data sources directly from the terminal. The CLI uses the same MCP configuration format as the VS Code extension.
### Setting Up MCP Servers
To configure MCP servers for the CLI, create or edit the settings file at:
```
~/.cline/data/settings/cline_mcp_settings.json
```
The file uses the same JSON format as the VS Code extension:
```json
{
"mcpServers": {
"my-server": {
"command": "node",
"args": ["/path/to/server.js"],
"env": {
"API_KEY": "your_api_key"
},
"alwaysAllow": ["tool1", "tool2"],
"disabled": false
}
}
}
```
For the full configuration reference including STDIO and SSE transport types, see [Adding and Configuring MCP Servers](/mcp/adding-and-configuring-servers).
<Note>
The CLI does not yet have a `/mcp` slash command for managing MCP servers interactively. For now, you'll need to edit the `cline_mcp_settings.json` file directly.
</Note>
### Custom Config Directory
If you use the `CLINE_DIR` environment variable or `--config` flag, the MCP settings file will be located at `<your-config-dir>/data/settings/cline_mcp_settings.json` instead.
## Configuration for Local Providers
### Ollama
@@ -268,8 +310,8 @@ cline auth # Re-authenticate
## Next Steps
<Columns cols={2}>
<Card title="CLI Workflows" icon="route" href="/cline-cli/three-core-flows">
Learn about interactive mode, direct execution, and automation patterns.
<Card title="Headless Mode" icon="robot" href="/cline-cli/three-core-flows">
Run Cline autonomously in scripts, CI/CD pipelines, and automated workflows.
</Card>
<Card title="CLI Reference" icon="terminal" href="/cline-cli/cli-reference">
+457
View File
@@ -0,0 +1,457 @@
---
title: "Getting Started"
description: "Run Cline AI coding agents directly in your terminal with an interactive CLI or automated workflows"
---
## What is Cline CLI?
Cline CLI brings the full power of Cline to your terminal. Whether you prefer an interactive experience or automated workflows for CI/CD pipelines, the CLI adapts to your needs.
The CLI supports macOS, Linux, and Windows, and works with all the same AI providers as the VS Code extension.
## Two Ways to Use Cline CLI
The CLI operates in two distinct modes, automatically selecting the appropriate one based on how you invoke it:
### Interactive Mode
Interactive mode is designed for **hands-on development sessions** where you want to collaborate with Cline in real-time. It provides a rich terminal interface that feels like chatting with an AI assistant.
**When it activates:** Running `cline` without arguments, or when stdin is a TTY (terminal).
```bash
cline
```
Key features:
- **Real-time conversation** - Type messages, see Cline's responses, and iterate on tasks
- **Visual feedback** - Animated welcome screen, syntax-highlighted code, and progress indicators
- **File mentions** with `@` - Reference workspace files with fuzzy search autocomplete
- **Slash commands** with `/` - Quick access to `/settings`, `/history`, `/models`, and workflows
- **Keyboard shortcuts** - `Tab` to toggle Plan/Act, `Shift+Tab` for auto-approve all
- **Session summaries** - See tasks completed, files modified, and token usage on exit
- **Settings panel** - Configure providers, models, and features without leaving the CLI
Interactive mode keeps you in control. You review Cline's plan, approve or modify actions, and guide the conversation.
[Learn more about interactive mode →](/cline-cli/interactive-mode)
### Headless Mode (Non-Interactive)
Headless mode is designed for **automation, scripting, and CI/CD pipelines** where human interaction isn't possible or desired.
**When it activates:** Using the `-y`/`--yolo` flag, `--json` flag, piping input/output, or when stdin is not a TTY.
```bash
# Headless with auto-approval (YOLO mode)
cline -y "Run tests and fix any failures"
# Headless with JSON output for parsing
cline --json "List all TODO comments" | jq '.text'
# Headless via piped input
cat README.md | cline "Summarize this document"
# Chain multiple headless commands
git diff | cline -y "explain these changes" | cline -y "write a commit message"
```
Key features:
- **No visual interface** - Clean text or JSON output suitable for scripting
- **Automatic execution** - With `-y`, Cline approves all actions and runs autonomously
- **Process control** - Exits automatically when the task completes
- **Piped workflows** - Read from stdin, write to stdout, chain with other commands
- **Machine-readable output** - Use `--json` to get structured output for parsing
<Warning>
Headless mode with `-y` gives Cline full autonomy. Run on a clean git branch so you can easily revert changes if needed.
</Warning>
### Mode Detection Summary
Cline automatically detects which mode to use based on your invocation. This table shows how different command patterns trigger each mode, helping you predict behavior in scripts and interactive sessions.
| Invocation | Mode | Reason |
|------------|------|--------|
| `cline` | Interactive | No arguments, TTY connected |
| `cline "task"` | Interactive | TTY connected |
| `cline -y "task"` | Headless | YOLO flag forces headless |
| `cline --json "task"` | Headless | JSON flag forces headless |
| `cat file \| cline "task"` | Headless | stdin is piped |
| `cline "task" > output.txt` | Headless | stdout is redirected |
[Learn more about headless mode →](/cline-cli/three-core-flows)
## Supported Model Providers
Cline CLI supports all providers available in the VS Code extension:
- **Anthropic** (Claude)
- **OpenAI** (GPT-4o, GPT-4)
- **OpenAI Codex** (ChatGPT subscription)
- **OpenRouter**
- **AWS Bedrock**
- **Google Gemini**
- **X AI (Grok)**
- **Cerebras**
- **DeepSeek**
- **Ollama** (local models)
- **LM Studio** (local models)
- **OpenAI Compatible** (any compatible API)
During setup, authenticate with `cline auth` to configure your preferred provider. [See authentication →](#authenticate)
## What You Can Build
### Automated Code Maintenance
Keep your codebase healthy with automated fixes. Cline scans for issues and applies corrections across multiple files.
```bash
cline -y "Fix all ESLint errors in src/"
```
Finds and fixes linting violations throughout your source directory.
```bash
cline -y "Update all deprecated React lifecycle methods"
```
Migrates legacy code patterns to modern equivalents (e.g., `componentWillMount` → `useEffect`).
```bash
cline -y "Update dependencies with known vulnerabilities"
```
Identifies outdated packages with security issues and updates them to safe versions.
### CI/CD Integration
Integrate Cline into your continuous integration pipelines for automated code review and documentation.
```bash
git diff origin/main | cline -y "Review these changes for issues"
```
Pipes your PR diff to Cline for automated code review, catching bugs and style issues before merge.
```bash
git log --oneline v1.0..v1.1 | cline -y "Write release notes"
```
Generates human-readable release notes from your commit history between two tags.
```bash
cline -y "Run tests and fix failures" --timeout 600
```
Executes your test suite, analyzes failures, and attempts fixes with a 10-minute timeout.
### Development Workflows
From quick edits to complex refactors, Cline adapts to your workflow.
```bash
cline
```
Launches interactive mode for exploratory development and back-and-forth collaboration.
```bash
cline "Refactor this function to use async/await"
```
Executes a focused task directly from the command line with approval prompts at key steps.
```bash
cline "Based on @src/api.ts, add error handling to all endpoints"
```
Uses file mentions (`@`) to give Cline context about specific files in your workspace.
### Custom Shell Pipelines
Chain Cline with other CLI tools to build powerful automation workflows.
```bash
gh pr diff 123 | cline -y "Review this PR"
```
Fetches a GitHub PR diff and pipes it directly to Cline for review.
```bash
cline --json "List all TODO comments" | jq '.text'
```
Outputs structured JSON that you can process with tools like `jq` for scripting.
```bash
git diff | cline -y "explain" | cline -y "write a haiku about these changes"
```
Chains multiple Cline invocations together for creative multi-step workflows.
## Features at a Glance
| Feature | Interactive Mode | Non-Interactive Mode |
|---------|------------------|----------------------|
| Interactive chat | ✓ | - |
| File mentions (@) | ✓ | ✓ (inline) |
| Slash commands (/) | ✓ | - |
| Settings panel | ✓ | `cline config` |
| Plan/Act toggle | ✓ (Tab) | `-p` / `-a` flags |
| Auto-approve | ✓ (Shift+Tab) | `-y` flag |
| Session summary | ✓ | - |
| JSON output | - | `--json` |
| Piped input | - | ✓ |
---
## Installation & Setup
In just a few minutes, you can install the CLI, authenticate with your preferred AI provider, and start running tasks from any directory on your machine.
### Prerequisites
Cline CLI requires **Node.js version 20 or higher**. We recommend Node.js 22 for the best experience.
Check your Node.js version:
```bash
node --version
```
If you need to install or update Node.js, visit [nodejs.org](https://nodejs.org) or use a version manager like [nvm](https://github.com/nvm-sh/nvm).
### Install Cline CLI
Install globally via npm:
```bash
npm install -g cline
```
Verify the installation:
```bash
cline version
```
<Tip>
To install a specific version, use `npm install -g cline@2.0.0`. Check [npm](https://www.npmjs.com/package/cline) for available versions.
</Tip>
### Authenticate
After installation, run the authentication wizard:
```bash
cline auth
```
This launches an interactive wizard with multiple options. Choose the method that works best for your workflow.
#### Option 1: Sign in with Cline (Recommended)
Select **"Sign in with Cline"** to authenticate with your Cline account via OAuth. Your browser opens automatically to complete sign-in.
#### Option 2: Sign in with ChatGPT Subscription
If you have a ChatGPT Plus or Pro subscription, select **"Sign in with ChatGPT Subscription"**. This uses OpenAI's Codex OAuth to authenticate with your existing subscription.
#### Option 3: Import from Existing Tools
Already using another AI coding CLI? Cline can import your existing configuration:
- **Import from Codex CLI** - Imports credentials from `~/.codex/auth.json`
- **Import from OpenCode** - Imports configuration from `~/.local/share/opencode/auth.json`
#### Option 4: Bring Your Own API Key
Select **"Bring your own API key"** to manually configure any supported provider. Or skip the wizard entirely with flags:
```bash
# Anthropic (Claude)
cline auth -p anthropic -k sk-ant-api-xxxxx -m claude-sonnet-4-5-20250929
# OpenAI
cline auth -p openai-native -k sk-xxxxx -m gpt-4o
# OpenRouter
cline auth -p openrouter -k sk-or-xxxxx -m anthropic/claude-sonnet-4-5-20250929
# OpenAI-compatible provider with custom base URL
cline auth -p openai -k your-api-key -b https://api.example.com/v1
```
**Quick Setup Flags:**
| Flag | Description |
|------|-------------|
| `-p, --provider <id>` | Provider ID (e.g., `anthropic`, `openai-native`, `openrouter`) |
| `-k, --apikey <key>` | Your API key |
| `-m, --modelid <id>` | Model ID (e.g., `claude-sonnet-4-5-20250929`, `gpt-4o`) |
| `-b, --baseurl <url>` | Base URL for OpenAI-compatible providers |
<Tip>
Flags are especially useful for scripting, CI/CD environments, or setting up multiple machines.
</Tip>
#### Supported Providers
| Provider | Provider ID | Notes |
|----------|-------------|-------|
| Anthropic | `anthropic` | Direct Claude API access |
| OpenAI | `openai-native` | GPT-4o, GPT-4, etc. |
| OpenAI Codex | `openai-codex` | ChatGPT subscription OAuth |
| OpenRouter | `openrouter` | Access multiple providers |
| AWS Bedrock | `bedrock` | Claude via AWS |
| Google Gemini | `gemini` | Gemini Pro, etc. |
| X AI (Grok) | `xai` | Grok models |
| Cerebras | `cerebras` | Fast inference |
| DeepSeek | `deepseek` | DeepSeek models |
| Ollama | `ollama` | Local models |
| LM Studio | `lmstudio` | Local models |
| OpenAI Compatible | `openai` | Any OpenAI-compatible API |
### Verify Your Setup
Confirm everything is working with a simple test:
```bash
cline "What is 2 + 2?"
```
If Cline responds with an answer, your installation and authentication are complete.
Check your current configuration:
```bash
cline config
```
### Quick Start
Now you're ready to use Cline. Choose how you want to work:
#### Interactive Mode
Launch the interactive CLI for development:
```bash
cline
```
You'll see the Cline welcome screen. Type your task and press Enter. Use:
- `Tab` to toggle between Plan and Act modes
- `Shift+Tab` to enable auto-approve
- `/help` for available commands
[Learn more about interactive mode →](/cline-cli/interactive-mode)
#### Direct Task Execution
Run a task directly from your shell:
```bash
cline "Add error handling to utils.js"
```
For non-interactive execution (perfect for scripts and CI/CD):
```bash
cline -y "Run tests and fix any failures"
```
[Learn more about headless mode →](/cline-cli/three-core-flows)
### Switching Providers
To change your configured provider at any time:
```bash
cline auth
```
You can also use the settings panel in interactive mode:
```bash
cline
# Then type: /settings
# Navigate to the API tab
```
### Updating
Check for updates and install the latest version:
```bash
cline update
```
Or update manually via npm:
```bash
npm update -g cline
```
### Troubleshooting
#### Command Not Found
If `cline` is not found after installation:
1. Ensure npm global bin is in your PATH:
```bash
npm bin -g
```
2. Add the path to your shell configuration (`.bashrc`, `.zshrc`, etc.):
```bash
export PATH="$PATH:$(npm bin -g)"
```
3. Restart your terminal or source your shell config.
#### Permission Errors
If you get permission errors during installation:
```bash
# Option 1: Use a Node version manager (recommended)
# nvm, fnm, or volta handle permissions automatically
# Option 2: Fix npm permissions
# See: https://docs.npmjs.com/resolving-eacces-permissions-errors-when-installing-packages-globally
```
#### OAuth Flow Issues
If the browser doesn't open automatically during OAuth:
1. Copy the URL from the terminal
2. Paste it in your browser manually
3. Complete the sign-in flow
4. Return to the terminal
#### API Key Validation
If your API key is rejected:
1. Verify the key is correct and hasn't expired
2. Check that you've selected the correct provider
3. Ensure your API account has the necessary permissions
**Provider-specific tips:**
- **Anthropic**: Keys start with `sk-ant-`
- **OpenAI**: Keys start with `sk-`
- **AWS Bedrock**: Requires AWS credentials configured separately. See [AWS Bedrock documentation](/provider-config/aws-bedrock/api-key).
### Uninstallation
To remove Cline CLI:
```bash
npm uninstall -g cline
```
To also remove configuration data:
```bash
rm -rf ~/.cline
```
## Next Steps
- **[Interactive Mode](/cline-cli/interactive-mode)** - Master the interactive CLI with shortcuts and slash commands
- **[Headless Mode](/cline-cli/three-core-flows)** - Run Cline autonomously in scripts, CI/CD pipelines, and automated workflows
- **[Configuration](/cline-cli/configuration)** - Configure settings, rules, workflows, and environment variables
- **[CLI Reference](/cline-cli/cli-reference)** - Complete command documentation with all flags and options
+8 -4
View File
@@ -74,6 +74,9 @@ cline auth -p openai-native -k sk-xxxxx -m gpt-4o
# OpenRouter
cline auth -p openrouter -k sk-or-xxxxx -m anthropic/claude-sonnet-4-5-20250929
# Moonshot
cline auth -p moonshot -k sk-xxxxx -m kimi-k2.5
# OpenAI-compatible provider with custom base URL
cline auth -p openai -k your-api-key -b https://api.example.com/v1
```
@@ -82,7 +85,7 @@ cline auth -p openai -k your-api-key -b https://api.example.com/v1
| Flag | Description |
|------|-------------|
| `-p, --provider <id>` | Provider ID (e.g., `anthropic`, `openai-native`, `openrouter`) |
| `-p, --provider <id>` | Provider ID (e.g., `anthropic`, `openai-native`, `openrouter`, `moonshot`) |
| `-k, --apikey <key>` | Your API key |
| `-m, --modelid <id>` | Model ID (e.g., `claude-sonnet-4-5-20250929`, `gpt-4o`) |
| `-b, --baseurl <url>` | Base URL for OpenAI-compatible providers |
@@ -104,6 +107,7 @@ Flags are especially useful for scripting, CI/CD environments, or setting up mul
| X AI (Grok) | `xai` | Grok models |
| Cerebras | `cerebras` | Fast inference |
| DeepSeek | `deepseek` | DeepSeek models |
| Moonshot | `moonshot` | Kimi models via Moonshot AI |
| Ollama | `ollama` | Local models |
| LM Studio | `lmstudio` | Local models |
| OpenAI Compatible | `openai` | Any OpenAI-compatible API |
@@ -157,7 +161,7 @@ For non-interactive execution (perfect for scripts and CI/CD):
cline -y "Run tests and fix any failures"
```
[Learn more about CLI workflows →](/cline-cli/three-core-flows)
[Learn more about headless mode →](/cline-cli/three-core-flows)
## Switching Providers
@@ -260,8 +264,8 @@ rm -rf ~/.cline
Master the interactive CLI with shortcuts and slash commands.
</Card>
<Card title="CLI Workflows" icon="route" href="/cline-cli/three-core-flows">
Learn interactive mode, direct execution, and automation patterns.
<Card title="Headless Mode" icon="robot" href="/cline-cli/three-core-flows">
Run Cline autonomously in scripts, CI/CD pipelines, and automated workflows.
</Card>
<Card title="Configuration" icon="gear" href="/cline-cli/configuration">
+10 -10
View File
@@ -65,7 +65,7 @@ Keyboard shortcuts are the primary way to navigate and control the interactive C
Reference files from your workspace by typing `@` followed by the filename:
```
```text
@src/utils.ts can you add error handling to this file?
```
@@ -79,7 +79,7 @@ File search uses ripgrep for fast, fuzzy matching. You can type partial paths li
Include multiple files in a single message:
```
```text
Compare @src/old-api.ts with @src/new-api.ts and list the breaking changes
```
@@ -100,9 +100,9 @@ Type `/` to see available commands. Slash commands provide quick access to setti
### Workflow Commands
If you have [workflows](/features/slash-commands/workflows/index) configured, they appear as additional slash commands. For example, if you have a workflow named `code-review`, you can invoke it with:
If you have [workflows](/customization/workflows) configured, they appear as additional slash commands. For example, if you have a workflow named `code-review`, you can invoke it with:
```
```text
/code-review
```
@@ -120,7 +120,7 @@ Access the settings panel with `/settings`. Navigate between tabs using arrow ke
## Plan and Act Modes
Cline operates in two modes, toggled with `Tab`. These modes work the same way in the CLI as they do in the VS Code extension. For a deeper explanation of how Plan and Act modes work, see the [Plan and Act documentation](/features/plan-and-act).
Cline operates in two modes, toggled with `Tab`. These modes work the same way in the CLI as they do in the VS Code extension. For a deeper explanation of how Plan and Act modes work, see the [Plan and Act documentation](/core-workflows/plan-and-act).
### Plan Mode
@@ -211,7 +211,7 @@ Use terminal multiplexers like tmux or split terminals to run multiple Cline ins
Give Cline context about what you're working on:
```
```text
I'm building a REST API with Express. The routes are in @src/routes/ and models in @src/models/. Help me add user authentication.
```
@@ -219,7 +219,7 @@ I'm building a REST API with Express. The routes are in @src/routes/ and models
When you're unsure about the best approach:
```
```text
[Tab to Plan mode]
How should I structure the database schema for a multi-tenant SaaS app?
```
@@ -228,7 +228,7 @@ How should I structure the database schema for a multi-tenant SaaS app?
The interactive CLI maintains conversation context. Build on previous messages:
```
```text
> Add a login endpoint
[Cline creates the endpoint]
@@ -242,8 +242,8 @@ The interactive CLI maintains conversation context. Build on previous messages:
## Next Steps
<Columns cols={2}>
<Card title="CLI Workflows" icon="route" href="/cline-cli/three-core-flows">
Learn about interactive mode, direct execution, and automation patterns.
<Card title="Headless Mode" icon="robot" href="/cline-cli/three-core-flows">
Run Cline autonomously in scripts, CI/CD pipelines, and automated workflows.
</Card>
<Card title="Configuration" icon="gear" href="/cline-cli/configuration">
+23 -5
View File
@@ -15,6 +15,15 @@ Ready to get started? Check out the [installation guide](/cline-cli/installation
## Two Ways to Use Cline CLI
<Columns cols={2}>
<Card title="Interactive Mode" icon="terminal" href="/cline-cli/interactive-mode">
**For hands-on development.** Launch `cline` in your terminal and collaborate with Cline in real-time — chat, review plans, approve actions, and iterate on tasks with a rich visual interface.
</Card>
<Card title="Headless Mode" icon="robot" href="/cline-cli/three-core-flows">
**For automation & CI/CD.** Run `cline -y "task"` to let Cline work autonomously — no interaction needed. Pipe input/output, get JSON results, and chain commands in scripts and pipelines.
</Card>
</Columns>
The CLI operates in two distinct modes, automatically selecting the appropriate one based on how you invoke it:
### Interactive Mode
@@ -86,7 +95,7 @@ Cline automatically detects which mode to use based on your invocation. This tab
| `cat file \| cline "task"` | Headless | stdin is piped |
| `cline "task" > output.txt` | Headless | stdout is redirected |
[Learn more about CLI workflows →](/cline-cli/three-core-flows)
[Learn more about headless mode →](/cline-cli/three-core-flows)
## Supported Model Providers
@@ -198,6 +207,15 @@ Chains multiple Cline invocations together for creative multi-step workflows.
| Session summary | ✓ | - |
| JSON output | - | `--json` |
| Piped input | - | ✓ |
| [MCP servers](/cline-cli/configuration#mcp-server-configuration) | ✓ | ✓ |
## MCP Server Support
Cline CLI supports [MCP (Model Context Protocol)](/mcp/mcp-overview) servers, the same extensibility system available in the VS Code extension. MCP servers give Cline access to external tools and data sources, from databases and APIs to browser automation and project management.
To use MCP servers with the CLI, add your server configuration to `~/.cline/data/settings/cline_mcp_settings.json`. The format is identical to the VS Code extension.
[Configure MCP servers for the CLI →](/cline-cli/configuration#mcp-server-configuration)
## Learn More
@@ -210,8 +228,8 @@ Chains multiple Cline invocations together for creative multi-step workflows.
Master the interactive CLI with keyboard shortcuts and slash commands.
</Card>
<Card title="CLI Workflows" icon="route" href="/cline-cli/three-core-flows">
Learn interactive mode, direct execution, and automation patterns.
<Card title="Headless Mode" icon="robot" href="/cline-cli/three-core-flows">
Run Cline autonomously in scripts, CI/CD pipelines, and automated workflows.
</Card>
<Card title="Configuration" icon="gear" href="/cline-cli/configuration">
@@ -222,7 +240,7 @@ Chains multiple Cline invocations together for creative multi-step workflows.
Run Cline as an ACP agent in JetBrains, Neovim, Zed, and more.
</Card>
<Card title="Use in Other Editors" icon="code" href="/cline-cli/acp-editor-integrations">
Run Cline as an ACP agent in JetBrains, Neovim, Zed, and more.
<Card title="CLI Samples" icon="flask" href="/cline-cli/samples/overview">
Real-world examples of headless workflows and automation patterns.
</Card>
</Columns>
+535
View File
@@ -0,0 +1,535 @@
---
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
@@ -3,8 +3,6 @@ title: "GitHub Actions Integration"
description: "Automatically respond to GitHub issues by mentioning @cline in comments using Cline CLI in GitHub Actions."
---
# GitHub Integration Sample
Automate GitHub issue analysis with AI. Mention `@cline` in any issue comment to trigger an autonomous investigation that reads files, analyzes code, and provides actionable insights - all running automatically in GitHub Actions.
@@ -273,7 +271,7 @@ git push
Once set up, simply mention `@cline` in any issue comment:
```
```text
@cline what's causing this error?
@cline analyze the root cause
+2 -4
View File
@@ -3,8 +3,6 @@ title: "GitHub Issue RCA Sample"
description: "Automated GitHub issue analysis using Cline CLI to identify root causes."
---
# GitHub Root Cause Analysis
Automated GitHub issue analysis using Cline CLI. This script uses Cline's autonomous AI capabilities to fetch, analyze, and identify root causes of GitHub issues, outputting clean, parseable results that can be easily integrated into your development workflows.
<Note>
@@ -203,7 +201,7 @@ fi
This is where the magic happens:
```bash
# Ask Cline for his analysis, showing only the summary
# Ask Cline for its analysis, showing only the summary
cline -y "$PROMPT: $ISSUE_URL" --mode act $ADDRESS -F json | \
sed -n '/^{/,$p' | \
jq -r 'select(.say == "completion_result") | .text' | \
@@ -380,4 +378,4 @@ This pattern can be adapted for many other automation scenarios, from pull reque
- [CLI Installation Guide](https://docs.cline.bot/cline-cli/installation)
- [CLI Reference Documentation](https://docs.cline.bot/cline-cli/cli-reference)
- [Three Core Flows](https://docs.cline.bot/cline-cli/three-core-flows)
- [Headless Mode](https://docs.cline.bot/cline-cli/three-core-flows)
@@ -3,14 +3,8 @@ title: "GitHub PR Review"
description: "Automatically review Pull Requests with AI using Cline CLI in GitHub Actions."
---
# GitHub PR Review Sample
Automate code review for every Pull Request. Detailed analysis, security checks, and code suggestions provided by Cline running autonomously in GitHub Actions.
<Frame>
<img src="https://storage.googleapis.com/cline_public_images/cli-pr-review.png" alt="Cline PR Review Comment" width="600" />
</Frame>
## The Workflow
When a PR is opened or marked ready for review, this workflow:
+8 -13
View File
@@ -3,26 +3,21 @@ title: "Model Orchestration"
description: "Use multiple AI models strategically: optimize costs, reduce bias, and leverage model-specific strengths in your workflows"
---
# Model Orchestration
Cline CLI's `--config` and `--thinking` flags enable sophisticated multi-model workflows. Instead of using a single model for all tasks, you can route different work to different models based on cost, capability, and specialization.
## Why Orchestrate Multiple Models?
**Cost Optimization**
- Use fast, cheap models (Haiku, Gemini Flash) for simple tasks like summarization
- Reserve expensive models (Opus, O1) for complex reasoning and planning
- Reduce API costs by 10-100x on routine operations
By routing work to the right model for the job, you can dramatically reduce API costs. Fast, inexpensive models like Haiku and Gemini Flash handle simple tasks such as summarization, while expensive models like Opus and O1 are reserved for complex reasoning and planning. This approach can reduce costs by 10-100x on routine operations.
**Bias Reduction**
- Different models catch different issues in code reviews
- Cross-validate solutions with multiple AI perspectives
- Reduce blind spots from single-model thinking
Different models catch different issues, so cross-validating solutions with multiple AI perspectives helps reduce blind spots that come from relying on a single model. In code reviews especially, combining viewpoints surfaces problems that any one model might miss.
**Specialization**
- Some models excel at code (Codex, DeepSeek)
- Others are better at documentation (GPT-4, Claude)
- Security analysis benefits from multiple viewpoints
Certain models excel in specific domains: Codex and DeepSeek are strong at code generation, while GPT-4 and Claude shine at documentation and prose. Security analysis in particular benefits from combining multiple model viewpoints, since each brings different training data and heuristics to the table.
## Pattern 1: CI/CD Code Review
@@ -213,8 +208,8 @@ cat *-sec.md | cline -y "find security issues all 3 reviews mentioned"
Complete documentation for --config and --thinking flags
</Card>
<Card title="Three Core Flows" icon="route" href="/cline-cli/three-core-flows">
Learn about interactive mode, headless automation, and multi-instance workflows
<Card title="Headless Mode" icon="robot" href="/cline-cli/three-core-flows">
Run Cline autonomously in scripts, CI/CD pipelines, and automated workflows.
</Card>
<Card title="Model Selection Guide" icon="brain" href="/core-features/model-selection-guide">
+9 -1
View File
@@ -47,10 +47,18 @@ 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
- [CLI Installation Guide](/cline-cli/installation)
- [CLI Reference Documentation](/cline-cli/cli-reference)
- [Three Core Flows](/cline-cli/three-core-flows)
- [Headless Mode](/cline-cli/three-core-flows)
@@ -3,8 +3,6 @@ title: "Worktree Workflows"
description: "Use Git worktrees with Cline CLI to run parallel tasks, test different approaches, and pipe context between isolated environments"
---
# Worktree Workflows
Git worktrees let you have multiple branches checked out simultaneously in different folders. Combined with Cline CLI's `--cwd` flag, this enables powerful parallel development workflows and isolated experimentation.
<Tip>
@@ -269,7 +267,7 @@ git worktree remove ~/cline-worktrees/feature-auth
Complete documentation for --cwd and all other CLI flags
</Card>
<Card title="Three Core Flows" icon="route" href="/cline-cli/three-core-flows">
Learn about interactive mode, task mode, and plain text workflows
<Card title="Headless Mode" icon="robot" href="/cline-cli/three-core-flows">
Run Cline autonomously in scripts, CI/CD pipelines, and automated workflows.
</Card>
</Columns>
+101 -149
View File
@@ -1,134 +1,32 @@
---
title: "CLI Workflows"
description: "Learn the three ways to use Cline CLI: interactive mode, direct task execution, and automation"
title: "Headless Mode"
description: "Run Cline autonomously in scripts, CI/CD pipelines, and automated workflows"
---
Cline CLI supports three primary workflows, each optimized for different use cases. Choose the approach that best fits your needs.
Headless mode runs Cline without an interactive interface — perfect for automation, scripting, and CI/CD pipelines where human interaction isn't possible or desired. Cline executes tasks, produces clean text or JSON output, and exits when complete.
For collaborative, conversational development, see [Interactive Mode](/cline-cli/interactive-mode) instead.
<Note>
**Migrating from an older CLI version?** Instance commands (`cline instance new/list/kill`) have been removed in Cline CLI 2.0. The new architecture is simpler. Just run `cline` for interactive mode or `cline "task"` for direct execution.
**Migrating from an older CLI version?** Instance commands (`cline instance new/list/kill`) have been removed in Cline CLI 2.0. The new architecture is simpler — just use `cline -y "task"` for headless execution.
</Note>
## 1. Interactive Mode
## When Headless Mode Activates
The interactive CLI provides the richest experience for interactive development.
Cline automatically enters headless mode when any of these conditions are met:
### Getting Started
| Invocation | Reason |
|------------|--------|
| `cline -y "task"` | `-y`/`--yolo` flag forces headless |
| `cline --json "task"` | `--json` flag forces headless |
| `cat file \| cline "task"` | stdin is piped |
| `cline "task" > output.txt` | stdout is redirected |
```bash
cline
```
If none of these apply (e.g., running `cline` or `cline "task"` in a terminal), Cline launches in [interactive mode](/cline-cli/interactive-mode).
This launches an interactive session in your current directory. Type your task, and Cline will analyze and execute it.
## YOLO Mode (Fully Autonomous)
### Key Features
**Plan/Act Mode Toggle** - Press `Tab` to switch between modes:
- **Plan Mode**: Cline analyzes your request and presents a strategy
- **Act Mode**: Cline executes actions directly
**Auto-approve Toggle** - Press `Shift+Tab` to enable automatic approval for all actions.
**Slash Commands** - Type `/` for quick access to:
- `/settings` - Configure providers, models, and features
- `/models` - Quick model switching
- `/history` - Browse and resume previous tasks
- `/clear` - Start a fresh task
- `/help` - Show available commands
**File Mentions** - Type `@` to reference workspace files:
```
@src/utils.ts add error handling to this file
```
**Session Summary** - When you exit with `Ctrl+C`, Cline displays a summary of your session including tasks completed, files modified, and token usage.
### When to Use Interactive Mode
- Exploring a new codebase
- Complex refactoring that requires back-and-forth
- Learning how Cline approaches problems
- Tasks where you want to review before executing
[Full Interactive Mode Guide →](/cline-cli/interactive-mode)
## 2. Direct Task Execution
Execute tasks directly from the command line without entering interactive mode.
### Basic Usage
```bash
cline "Add unit tests to utils.js"
```
Cline analyzes your task, creates a plan, and executes it. You'll be prompted for approval at key decision points.
### Piping Context
Pipe file contents or command output into Cline:
```bash
# Explain a file
cat README.md | cline "Summarize this document"
# Review git changes
git diff | cline "Review these changes and suggest improvements"
# Analyze command output
npm test 2>&1 | cline "Analyze these test failures and fix them"
```
### Chaining Cline Commands
Pipe Cline's output into another Cline instance for creative workflows:
```bash
# Explain changes, then write a commit message
git diff | cline -y "explain these changes" | cline -y "write a commit message for this"
# Generate code, then write tests
cline -y "create a fibonacci function" | cline -y "write unit tests for this code"
# Fun: Generate a poem about your code
git diff | cline -y "explain" | cline -y "write a haiku about this"
```
### Including Images
Attach images to your task:
```bash
cline task -i screenshot.png "Fix the layout issue shown in this screenshot"
# Or reference inline
cline "Fix the UI shown in @./design-mockup.png"
```
### Mode Selection
```bash
# Start in Plan mode (analyze before acting)
cline -p "Design a REST API for user management"
# Start in Act mode (default)
cline -a "Fix the typo in README.md"
```
### When to Use Direct Execution
- Quick, well-defined tasks
- Tasks with sufficient context in the prompt
- Scripting and shell workflows
- When you don't need interactive conversation
## 3. Automation & CI/CD
For fully autonomous operation in scripts, CI/CD pipelines, and automated workflows.
### YOLO Mode (Yes Mode)
The `-y` or `--yolo` flag enables fully autonomous operation:
The `-y` or `--yolo` flag enables fully autonomous operation — Cline approves all actions and runs without prompts:
```bash
cline -y "Run the test suite and fix any failures"
@@ -141,12 +39,59 @@ In YOLO mode:
- Perfect for CI/CD and scripts
<Warning>
Run YOLO mode on a clean git branch or directory. You get speed in exchange for oversight, so be ready to revert if needed.
YOLO mode gives Cline full autonomy. Run on a clean git branch so you can easily revert changes if needed.
</Warning>
### JSON Output
### Mode Selection
Use `--json` for machine-readable output:
Control whether Cline plans first or acts immediately:
```bash
# Start in Plan mode (analyze before acting)
cline -y -p "Design a REST API for user management"
# Start in Act mode (default)
cline -y -a "Fix the typo in README.md"
```
## Piping Context
Pipe file contents or command output into Cline to provide context:
```bash
# Explain a file
cat README.md | cline "Summarize this document"
# Review git changes
git diff | cline "Review these changes and suggest improvements"
# Analyze command output
npm test 2>&1 | cline "Analyze these test failures and fix them"
# Pipe a GitHub PR diff
gh pr diff 123 | cline -y "Review this PR"
```
When stdin is piped, Cline automatically enters headless mode — the piped content becomes part of the task context.
## Chaining Commands
Pipe Cline's output into another Cline instance for multi-step workflows:
```bash
# Explain changes, then write a commit message
git diff | cline -y "explain these changes" | cline -y "write a commit message for this"
# Generate code, then write tests
cline -y "create a fibonacci function" | cline -y "write unit tests for this code"
# Fun: Generate a poem about your code
git diff | cline -y "explain" | cline -y "write a haiku about this"
```
## JSON Output
Use `--json` for machine-readable output that's easy to parse in scripts:
```bash
cline --json "List all TODO comments in the codebase" | jq '.text'
@@ -164,25 +109,36 @@ JSON output follows the same format as task files in `~/.cline/data/tasks/<id>/u
| `reasoning` | `string` | (Optional) Model reasoning |
| `partial` | `boolean` | (Optional) Streaming flag |
### Timeout Control
## Including Images
Set a maximum execution time:
Attach images to your headless task:
```bash
cline -y -i screenshot.png "Fix the layout issue shown in this screenshot"
# Or reference inline
cline -y "Fix the UI shown in @./design-mockup.png"
```
## Timeout Control
Set a maximum execution time to prevent runaway tasks:
```bash
cline -y --timeout 600 "Run full test suite"
```
### Environment Variables
## Environment Variables
Control Cline behavior via environment variables:
Control Cline behavior via environment variables — useful for CI/CD where you can't use interactive configuration.
**CLINE_DIR** - Custom configuration directory:
**CLINE_DIR** Custom configuration directory:
```bash
export CLINE_DIR=/path/to/config
cline -y "your task"
```
**CLINE_COMMAND_PERMISSIONS** - Restrict allowed commands:
**CLINE_COMMAND_PERMISSIONS** Restrict allowed commands:
```bash
export CLINE_COMMAND_PERMISSIONS='{"allow": ["npm *", "git *"], "deny": ["rm -rf *"]}'
cline -y "your task"
@@ -190,6 +146,8 @@ cline -y "your task"
See [Configuration](/cline-cli/configuration#environment-variables) for full documentation.
## CI/CD Integration
### GitHub Actions Example
Automate PR reviews with Cline:
@@ -232,7 +190,7 @@ jobs:
### Shell Script Example
Create a code review script:
Create a reusable code review script:
```bash
#!/bin/bash
@@ -252,30 +210,24 @@ fi
echo "$DIFF" | cline -y --json "Review this code diff for issues" | jq -r '.text'
```
### When to Use Automation Mode
## Common Use Cases
- CI/CD pipelines
- Scheduled maintenance tasks
- Batch processing
- Any workflow requiring non-interactive execution
## Choosing the Right Flow
| Use Case | Recommended Flow |
|----------|------------------|
| Exploring a new codebase | Interactive Mode |
| Complex refactoring | Interactive Mode (Plan first) |
| Quick file edits | Direct Execution |
| Code review | Direct Execution with pipe |
| CI/CD integration | Automation (`-y` flag) |
| Scheduled tasks | Automation (`-y` flag) |
| Learning Cline | Interactive Mode |
| Use Case | Example |
|----------|---------|
| Code review | `git diff \| cline -y "Review these changes"` |
| Fix test failures | `cline -y "Run tests and fix any failures"` |
| Generate release notes | `git log --oneline v1.0..v1.1 \| cline -y "Write release notes"` |
| Fix lint errors | `cline -y "Fix all ESLint errors in src/"` |
| Update dependencies | `cline -y "Update dependencies with known vulnerabilities"` |
| Migrate code patterns | `cline -y "Update all deprecated React lifecycle methods"` |
| PR automation | `gh pr diff 123 \| cline -y "Review this PR"` |
| Batch processing | `cline -y --json "List all TODO comments" \| jq '.text'` |
## Next Steps
<Columns cols={2}>
<Card title="Interactive Mode" icon="terminal" href="/cline-cli/interactive-mode">
Master keyboard shortcuts, slash commands, and file mentions.
For hands-on development with keyboard shortcuts, slash commands, and file mentions.
</Card>
<Card title="CLI Reference" icon="book" href="/cline-cli/cli-reference">
@@ -286,7 +238,7 @@ echo "$DIFF" | cline -y --json "Review this code diff for issues" | jq -r '.text
Environment variables, rules, and advanced settings.
</Card>
<Card title="YOLO Mode" icon="zap" href="/features/yolo-mode">
Deep dive into autonomous execution and safety considerations.
<Card title="CLI Samples" icon="flask" href="/cline-cli/samples/overview">
Real-world examples of headless workflows and automation patterns.
</Card>
</Columns>
+316
View File
@@ -0,0 +1,316 @@
---
title: "Documentation Templates"
sidebarTitle: "Templates"
description: "Templates for different types of Cline documentation"
---
Use these templates as starting points for new documentation. Each template is designed for a specific purpose. Choose the one that best fits what you're documenting.
## Choosing a Template
| If you're documenting... | Use this template |
|--------------------------|-------------------|
| What a feature does and how to use it | Feature Doc |
| How to accomplish a specific task | How-To Guide |
| Technical specifications or API details | Reference Doc |
| A complete project walkthrough | Tutorial |
## Feature Doc
Use this template when explaining a Cline feature. Focus on what it does, how to use it, and real examples.
````text
---
title: "Feature Name"
sidebarTitle: "Feature Name"
---
[One sentence explaining what this feature does.]
<Frame>
<img src="..." alt="Feature in action" />
</Frame>
[1-2 paragraphs explaining the feature in plain terms. What problem does it
solve? Why would someone use it?]
## How It Works
[Explain the mechanics without jargon. What happens when you use this feature?]
## Using [Feature Name]
[Show how to access and use it. Include the exact UI path.]
### [Option or Variation 1]
[Details with examples]
### [Option or Variation 2]
[Details with examples]
## Inspiration
[Share how you personally use this feature. Use "I" voice. Give 2-3 real
examples that spark imagination about what's possible.]
<Note>
[Important caveat, limitation, or requirement]
</Note>
````
### Example: Checkpoints Feature
Here's how the [Checkpoints](/core-workflows/checkpoints) doc follows this pattern:
- Opens with one clear sentence about what checkpoints do
- Shows a screenshot of the feature in action
- Explains how checkpoints work under the hood
- Shows exact steps to create and restore checkpoints
- Includes real examples of when checkpoints save the day
## How-To Guide
Use this template when showing how to accomplish a specific task. Focus on clear steps and troubleshooting.
````text
---
title: "How to [Accomplish Task]"
sidebarTitle: "[Short Title]"
description: "[One sentence describing what the reader will learn]"
---
[Brief intro explaining what problem this guide solves and what you'll end up
with after following it.]
## Prerequisites
[What the reader needs before starting. Keep it short. Link to other docs
rather than explaining setup here.]
- Cline installed and configured
- [Other requirement]
## Steps
<Steps>
<Step title="[First Action]">
[Clear instructions. Show exactly what to click or type.]
```bash
example command if needed
```
</Step>
<Step title="[Second Action]">
[Next step. Include screenshots for complex UI interactions.]
<Frame>
<img src="..." alt="What you should see" />
</Frame>
</Step>
<Step title="[Final Action]">
[Complete the task. Show the expected result.]
</Step>
</Steps>
## Troubleshooting
Common issues and how to fix them:
- **Problem description**: Solution in one or two sentences.
- **Another problem**: Another solution.
## Next Steps
<Card title="Related Feature" icon="arrow-right" href="/path/to/related">
Continue learning with this related guide.
</Card>
````
### Example: Your First Project
The [Your First Project](/getting-started/your-first-project) guide follows this pattern:
- Clear goal stated upfront
- Prerequisites listed briefly
- Step-by-step instructions with the Steps component
- Troubleshooting section for common issues
## Reference Doc
Use this template for technical specifications, API documentation, or detailed configuration options.
````text
---
title: "[Component/API] Reference"
sidebarTitle: "[Short Title]"
description: "[What this reference covers]"
---
[Brief description of what this reference documents and when you'd need it.]
## Overview
[High-level explanation. What is this component? What role does it play?]
## [Category 1]
### [Item Name]
[What it does in one sentence.]
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `propertyName` | `string` | `"default"` | What this property controls |
| `anotherProp` | `boolean` | `false` | What this does |
**Example:**
```typescript
// Show practical usage
const example = {
propertyName: "custom value",
anotherProp: true
}
```
### [Another Item]
[Continue for each item in this category.]
## [Category 2]
[Continue with other categories as needed.]
## Examples
[Show 2-3 complete, practical examples that combine multiple concepts.]
### [Example 1 Title]
```typescript
// Complete working example
```
### [Example 2 Title]
```typescript
// Another complete example
```
## Related
- [Related Doc 1](/path/to/doc) - Brief description
- [Related Doc 2](/path/to/doc) - Brief description
````
### Example: Cline Tools Guide
The [Cline Tools Guide](/tools-reference/all-cline-tools) follows this pattern:
- Overview of the tool system
- Each tool documented with parameters and examples
- Practical examples showing tools in context
## Tutorial
Use this template for comprehensive project walkthroughs where users build something from start to finish.
````text
---
title: "[Build/Create X] Tutorial"
sidebarTitle: "[Short Title]"
description: "[What the reader will build]"
---
In this tutorial, you'll build [specific outcome]. By the end, you'll have
[tangible result you can see/use].
<Frame>
<img src="..." alt="Preview of what you'll build" />
</Frame>
## What You'll Learn
- [Skill or concept 1]
- [Skill or concept 2]
- [Skill or concept 3]
## Prerequisites
[Required setup. Link to installation guides rather than repeating them.]
- [Prerequisite 1]
- [Prerequisite 2]
## Part 1: [First Major Section]
[Introduction to this section. What are we doing and why?]
### [Subsection]
[Detailed walkthrough with code blocks and explanations.]
```typescript
// Code that the reader should write or understand
```
[Explain what the code does and why.]
## Part 2: [Second Major Section]
[Continue building on Part 1.]
### [Subsection]
[More detailed walkthrough.]
## Part 3: [Final Section]
[Complete the project.]
## Summary
You built [what they built]. Along the way, you learned:
- [Key takeaway 1]
- [Key takeaway 2]
- [Key takeaway 3]
## Next Steps
<CardGroup cols={2}>
<Card title="Go Deeper" icon="book" href="/path/to/advanced">
Learn more advanced techniques.
</Card>
<Card title="Related Tutorial" icon="code" href="/path/to/related">
Build something else with similar concepts.
</Card>
</CardGroup>
````
### Example Structure
A good tutorial:
- Shows the end result upfront so readers know what they're building
- Breaks the work into logical parts
- Explains the "why" alongside the "how"
- Ends with clear next steps
## Quick Tips
When using these templates:
1. **Delete sections you don't need.** Templates are starting points, not rigid structures.
2. **Add sections that make sense.** If your doc needs something not in the template, add it.
3. **Keep the reader moving forward.** Every section should lead naturally to the next.
4. **Test your own instructions.** Follow your guide from scratch to catch missing steps.
<Tip>
Use the `/write-docs` workflow to generate documentation from these templates automatically.
Cline helps you fill in each section based on your project.
</Tip>
+200
View File
@@ -0,0 +1,200 @@
---
title: "Documentation Guide"
sidebarTitle: "Documentation Guide"
description: "How to write and contribute to Cline documentation"
---
Cline's documentation lives in the `docs/` directory and uses [Mintlify](https://mintlify.com) for rendering. This guide covers how to write docs that match Cline's established style.
## Using the Documentation Workflow
The fastest way to create documentation is using the `/write-docs` workflow. Type `/write-docs` in Cline and describe what you want to document. Cline guides you through a 4-step process:
1. **Research**: Examine existing docs structure and patterns
2. **Scope**: Clarify audience, doc type, and key use cases
3. **Outline**: Select a template and create structure
4. **Write**: Generate documentation following style guidelines
The workflow file lives at `.clinerules/workflows/write-docs.md` and contains templates, style rules, and examples.
## Documentation Principles
### Write for Developers
Your audience is developers who value their time. Get to the point. Every sentence should either help them understand something or help them do something.
```markdown
# Good
Switch to bash in Cline Settings → Terminal → Default Terminal Profile.
# Bad
Users who are experiencing issues may find it helpful to navigate to the
Cline settings menu where they can locate the terminal configuration
options and subsequently modify the default terminal profile setting.
```
### Show Real Examples
Abstract descriptions don't help anyone. Show actual code, real file paths, and concrete implementations.
```markdown
# Good
I use `/deep-planning` whenever I'm building features that touch multiple
parts of the codebase. For example, when adding authentication, Cline
mapped every endpoint and created a migration plan that avoided breaking changes.
# Bad
The deep planning feature can be utilized for various complex tasks
that may require careful consideration and planning.
```
### Use Active Voice
Cline does things. Files don't get created by Cline, Cline creates files.
```markdown
# Good
Cline reads your project files and builds context automatically.
# Bad
Project files are read and context is built automatically.
```
### Use Neutral Pronouns for Cline
Refer to Cline as "it" not "he". Cline is software, not a person.
```markdown
# Good
When Cline encounters an error, it suggests fixes.
# Bad
When Cline encounters an error, he suggests fixes.
```
## File Format
All documentation uses MDX format with YAML frontmatter:
```yaml
---
title: "Full Page Title"
sidebarTitle: "Shorter Nav Title" # optional
description: "One sentence for SEO" # optional but recommended
---
```
### Adding New Pages
After creating a new `.mdx` file, add it to `docs/docs.json` in the appropriate navigation group:
```json
{
"group": "Features",
"pages": [
"features/existing-page",
"features/your-new-page"
]
}
```
## Mintlify Components
Use these components appropriately throughout your docs.
### Frame
Wrap all images and videos:
```jsx
<Frame>
<img
src="https://storage.googleapis.com/cline_public_images/docs/assets/filename.png"
alt="Descriptive alt text"
/>
</Frame>
```
### Callouts
Use sparingly and purposefully:
```jsx
<Tip>Helpful suggestions that improve the experience.</Tip>
<Note>Important information the reader needs to know.</Note>
<Warning>Something that could cause problems if ignored.</Warning>
```
### Steps
For sequential procedures:
```jsx
<Steps>
<Step title="Install the Extension">
Search for "Cline" in the VS Code marketplace.
</Step>
<Step title="Configure Your Model">
Open settings and add your API key.
</Step>
</Steps>
```
### Cards
For navigation and feature overviews:
```jsx
<CardGroup cols={2}>
<Card title="Getting Started" icon="rocket" href="/getting-started/installing-cline">
Install Cline and set up your first project.
</Card>
<Card title="Features" icon="wand-magic-sparkles" href="/core-workflows/plan-and-act">
Explore what Cline can do.
</Card>
</CardGroup>
```
## Style Rules
Quick reference for consistent documentation:
| Do | Don't |
|---|---|
| Use "use" | Use "utilize" |
| Keep sentences under 25 words | Write run-on sentences |
| Use bullet points for lists | Write walls of text |
| Show where things are in the UI | Assume users can find features |
| Cross-link related docs | Leave readers stranded |
| Use code blocks with language tags | Use inline code for long snippets |
### Avoid These Patterns
- Em dashes and emojis
- Starting with "This document explains..."
- The **Bold Text**: description pattern
- Explaining obvious things
- Passive voice
## Previewing Changes
Run the docs locally to preview your changes:
```bash
cd docs
npm install # first time only
npm run dev
```
Open `http://localhost:3000` to see your changes in real time.
## Related Resources
<CardGroup cols={2}>
<Card title="Documentation Templates" icon="file-lines" href="/contributing/doc-templates">
Templates for different documentation types.
</Card>
<Card title="Workflows" icon="diagram-project" href="/customization/workflows">
Learn about Cline's workflow system.
</Card>
</CardGroup>
+26 -8
View File
@@ -1,6 +1,6 @@
---
title: "Model Selection Guide"
description: "Last updated: August 20, 2025."
description: "Choose the right AI model for your workflow based on reliability, speed, cost, and context window size."
---
New models drop constantly, so this guide focuses on what's working well with Cline right now. We'll keep it updated as the landscape shifts.
@@ -59,12 +59,17 @@ Choose your preferred AI provider from the dropdown menu.
| **Cline** | Easiest setup | No API keys needed, access to multiple models including stealth models |
| **OpenRouter** | Value seekers | Multiple models, competitive pricing |
| **Anthropic** | Reliability | Claude models, most dependable tool usage |
| **OpenAI** | Latest tech | GPT models |
| **Google Gemini** | Large context | Google's AI models |
| **OpenAI** | Latest tech | GPT-5, o3, o4-mini models |
| **OpenAI Codex** | ChatGPT subscribers | Use your ChatGPT subscription — no API key needed |
| **Google Gemini** | Large context | Gemini 3/2.5 with up to 2M context |
| **DeepSeek** | Budget reasoning | V3.2, R1 models at low cost |
| **Alibaba Qwen** | Open source coding | Qwen3 Coder with 1M context |
| **Moonshot** | Agentic coding | Kimi K2.5 with 262K context |
| **Cerebras** | Speed | Up to 2,600 tokens/sec |
| **AWS Bedrock** | Enterprise | Advanced features |
| **Ollama** | Privacy | Run models locally |
See the [full provider list](/provider-config) for more options including Cerebras, Vertex AI, Azure, and more.
See the [full provider list](/getting-started/authorizing-with-cline) for all 30+ supported providers including xAI Grok, Mistral, Groq, Fireworks, Together, Baseten, SambaNova, Nebius, Hugging Face, and more.
<Info>
**Recommended for beginners:** Start with **Cline** as your provider - no API key management needed, instant access to multiple models, and occasional free inferencing through partner providers.
@@ -81,6 +86,19 @@ The next step depends on which provider you selected.
- You'll be redirected to [app.cline.bot](https://app.cline.bot) to authenticate
- After signing in, return to your IDE
<Note>
For detailed information about the Cline authentication flow, OAuth tokens, and troubleshooting, see [Authorizing with Cline](/getting-started/authorizing-with-cline).
</Note>
#### If you selected **OpenAI Codex** as your provider:
- **No API key needed!** If you have a ChatGPT subscription (Plus, Pro, or Team), you can use it directly in Cline
- Click **"Sign in with OpenAI"** to authenticate via your browser
- Once authorized, all models available on your OpenAI plan will appear automatically
- Usage is governed by your ChatGPT subscription — no separate API billing
See the full [OpenAI Codex setup guide](/provider-config/openai-codex) for details.
#### If you selected any other provider:
You'll need to get an API key from your chosen provider:
@@ -90,7 +108,7 @@ You'll need to get an API key from your chosen provider:
- **OpenRouter**: [openrouter.ai/keys](https://openrouter.ai/keys)
- **OpenAI**: [platform.openai.com/api-keys](https://platform.openai.com/api-keys)
- **Google**: [aistudio.google.com/apikey](https://aistudio.google.com/apikey)
- **Others**: See [Provider Setup Guide](/provider-config)
- **Others**: See [Provider Setup Guide](/getting-started/authorizing-with-cline)
2. **Generate a new API key** on the provider's website
@@ -168,8 +186,7 @@ Selecting the right model involves balancing several factors. Use this framework
## Model Comparison Resources
For detailed model comparisons, pricing, and performance metrics, see:
- [**Model Comparison & Pricing**](/model-config/model-comparison) - Complete pricing tables and performance benchmarks
For detailed model comparisons and performance metrics, see:
- [**Context Window Guide**](/model-config/context-windows) - Understanding and optimizing context usage
## Open Source vs Closed Source
@@ -195,8 +212,9 @@ For detailed model comparisons, pricing, and performance metrics, see:
| Huge context windows | Gemini 2.5 Pro or Claude Sonnet 4.5 |
| Open source | Qwen3 Coder, Z AI GLM 4.5, or Kimi K2 |
| Latest tech | GPT-5 |
| To use your ChatGPT subscription | [OpenAI Codex](/provider-config/openai-codex) — sign in with your OpenAI account, no API key needed |
| Speed | Qwen3 Coder on Cerebras (fastest available) |
## What Others Are Using
Check [OpenRouter's Cline usage stats](https://openrouter.ai/apps?url=https%3A%2F%2Fcline.bot%2F) to see real usage patterns from the community.
Check [Vercel's leaderboard](https://vercel.com/ai-gateway/leaderboards) to see real usage patterns from the community.
+95
View File
@@ -0,0 +1,95 @@
---
title: "Checkpoints"
sidebarTitle: "Checkpoints"
description: "Roll back code changes while keeping your conversation. Experiment freely."
---
Checkpoints let you undo code changes without losing your conversation. Every time Cline modifies a file or runs a command, it saves a snapshot of your project files. You can restore to any checkpoint, keeping the context you've built while reverting the code.
This changes how you work with Cline. Instead of carefully reviewing every change before approving, you can let Cline move fast and roll back if something goes wrong. The cost of a mistake drops to nearly zero.
<Tip>
Checkpoints are enabled by default. See [Enable or Disable Checkpoints](#enable-or-disable-checkpoints) if you need to turn them off.
</Tip>
## How It Works
Cline maintains a shadow Git repository separate from your project's actual Git history. After each tool use (file edits, commands, etc.), Cline commits the current state of your files to this shadow repo. Your main Git repository stays untouched.
This means:
- Your Git history remains clean and under your control
- Checkpoints capture everything, including files not tracked by Git
- You can restore to any point in a task without affecting commits you've made
- Checkpoints persist across editor sessions
Each checkpoint captures the complete file state at that moment. If Cline edits three files in sequence, you get three checkpoints and can restore to any of them independently.
## Enable or Disable Checkpoints
Checkpoints are enabled by default. To toggle them:
1. Open Cline settings (gear icon in the Cline sidebar)
2. Scroll to the "Feature Settings" section
3. Toggle "Enable Checkpoints"
<Note>
For very large repositories, checkpoints may use significant storage and slow down Cline as it commits file snapshots after each tool use. Consider disabling them if you notice performance issues.
</Note>
## Viewing and Comparing Changes
After each tool use, a checkpoint indicator appears in your conversation. Look for a bookmark icon labeled "Checkpoint" with a dotted line connecting to **Compare** and **Restore** buttons.
Click **Compare** to open a diff view showing exactly what changed at that checkpoint. This opens in your editor's diff viewer, letting you see additions, deletions, and modifications across all affected files.
This is useful when Cline makes changes you want to understand before deciding whether to keep them. You can review the diff, then either continue or restore to undo.
## Restoring Checkpoints
Click **Restore** next to any step to open the restore menu. You have three options:
| Option | What It Does | When to Use It |
|--------|--------------|----------------|
| **Restore Files** | Reverts your project's files to the snapshot at this checkpoint | Undoing code changes while keeping the conversation |
| **Restore Task Only** | Deletes messages after this point, does not affect files | Trying a different prompt while keeping current code |
| **Restore Files & Task** | Reverts files and deletes messages after this point | Starting over completely from a known good state |
The right choice depends on what went wrong:
- If the conversation is productive but the code changes broke something, use **Restore Files**. Cline keeps all the context you've discussed and can try a different implementation.
- If Cline's code changes are good but the conversation went off track, use **Restore Task Only**. You keep the files and can guide the conversation differently.
- If you want to start over from a clean slate, use **Restore Files & Task**. This resets both your files and the conversation to that checkpoint.
## When to Use Checkpoints
| Scenario | Recommended Action |
|----------|-------------------|
| Cline refactored code and broke something | Restore Files, ask for a different approach |
| Experimenting with multiple solutions | Compare each checkpoint, restore to the best one |
| Cline misunderstood your intent | Restore Files & Task, rephrase your request |
| Want to try a different prompt | Restore Task Only, keep the files, resubmit |
| Reviewing changes before committing to Git | Use Compare to inspect, then commit manually |
| Testing risky changes | Let Cline proceed, restore if it fails |
## Working with Auto-Approve
Checkpoints make [auto-approve](/features/auto-approve) practical. Without checkpoints, auto-approve feels risky because Cline can make many changes before you notice a problem. With checkpoints, you can let Cline work autonomously and roll back if needed.
A typical workflow:
1. Enable auto-approve for file edits and commands
2. Let Cline work through your task quickly
3. Review the final result
4. If something is wrong, restore to the last good checkpoint
5. Give Cline more specific guidance
This approach is faster than reviewing every change individually, and checkpoints provide the safety net.
## Checkpoints and Message Editing
The message editing feature integrates with checkpoints. When you edit a previous message and select "Restore All," Cline restores your files to the checkpoint at that point before resubmitting your edited message.
This lets you fix a poorly worded prompt and undo all the changes that resulted from it in one action.
+120
View File
@@ -0,0 +1,120 @@
---
title: "Plan & Act Mode"
sidebarTitle: "Plan & Act Mode"
description: "Think first, then build. Cline's dual-mode system for structured development."
---
Plan & Act modes separate thinking from doing. Plan mode lets you explore and strategize without changing files. Act mode executes against your plan.
<Tip>
**New to Plan & Act?** Watch [Plan & Act Deep Dive](https://youtu.be/b7o6URFPp64) to see it in action.
</Tip>
## Plan Mode
Plan mode is where you and Cline figure out what you're building and how. In this mode, Cline can read your codebase, run searches, and discuss strategy, but cannot modify any files or execute commands.
This constraint is intentional. It keeps the conversation focused on understanding and planning, without the distraction of implementation details. You can explore freely, ask questions, and iterate on the approach before committing to changes.
Use Plan mode to:
- Explore unfamiliar codebases before making changes
- Discuss architecture decisions and tradeoffs
- Identify edge cases and potential issues upfront
- Create a clear implementation strategy
- Review code and understand complex workflows
## Act Mode
Once you have a plan, switch to Act mode. Cline retains the full context from your planning session and can now modify files, run commands, and execute your strategy.
The conversation history carries over when you switch modes. Cline remembers everything you discussed in Plan mode, so you don't need to repeat yourself. This makes the transition seamless.
<Note>
While you can start directly in Act mode, planning first is highly recommended. The planning phase intentionally builds context that Cline needs to implement changes effectively. Without it, Cline may lack the understanding required to make the right decisions.
</Note>
## Typical Workflow
1. Start in Plan mode and describe what you want to build
2. Let Cline explore relevant files and understand the codebase
3. Discuss the approach, considering edge cases and potential issues
4. When confident in the plan, switch to Act mode
5. Cline implements the solution based on your planning session
For complex projects, you may cycle between modes multiple times. Return to Plan mode when you hit unexpected complexity or need to rethink the approach, then switch back to Act mode to continue implementation.
## When to Use Each Mode
| Scenario | Recommended Mode |
|----------|-----------------|
| Starting new features where the approach isn't obvious | Plan |
| Debugging tricky issues where you're unsure what's wrong | Plan |
| Making architectural decisions affecting multiple files | Plan |
| Understanding complex workflows before modifying them | Plan |
| Code review and security analysis | Plan |
| Learning a new codebase | Plan |
| Implementing a solution you've already planned | Act |
| Making routine changes with a clear approach | Act |
| Following established patterns in the codebase | Act |
| Running tests and making adjustments | Act |
| Quick fixes where the solution is obvious | Act |
## Using Different Models for Each Mode
You can configure separate models for Plan and Act modes. This is useful when you want to use a stronger reasoning model for planning and a faster model for implementation.
To enable this:
1. Open Cline Settings
2. Enable "Use different models for Plan and Act"
3. Select your preferred model for each mode
When enabled, switching between Plan and Act mode automatically switches to the configured model for that mode. Your model selection is preserved when you switch back.
**Example configurations:**
| Use Case | Plan Mode | Act Mode |
|----------|-----------|----------|
| Cost optimization | GLM 4.6 | Grok Code Fast |
| Maximum quality | Claude Opus | Claude Sonnet |
| Speed-focused | Gemini 3 Flash | Cerebras |
## Using `/deep-planning`
For complex tasks that need thorough analysis, use the `/deep-planning` slash command. This triggers an extended planning session where Cline:
1. Explores the codebase systematically
2. Identifies all affected files and dependencies
3. Creates a detailed implementation plan
4. Asks clarifying questions before proceeding
The deep planning prompt is optimized for each model family, so it adapts to the strengths of whatever model you're using. See the [Deep Planning docs](/features/deep-planning) for more details.
## Choosing the Right Approach by Task Size
### Small tasks: Act mode only
For quick fixes like typos, simple bug fixes, or following established patterns, start directly in Act mode. Planning adds overhead when the solution is obvious.
**Examples:** Fix a typo, add a missing import, update a config value, rename a variable.
### Medium tasks: Plan → Act
For most development work, start in Plan mode to understand the scope and approach, then switch to Act mode to implement. This is the sweet spot for features that touch a few files and have some complexity.
**Examples:** Add a new API endpoint, implement a UI component, fix a bug that requires investigation, refactor a single module.
### Large tasks: Use `/deep-planning`
For complex features that span multiple files, require architectural decisions, or will take multiple sessions to complete, use the `/deep-planning` slash command. This creates a detailed implementation plan that Cline can reference throughout the work.
**Examples:** Add a new feature across frontend and backend, major refactoring across the codebase, implementing a new system or integration, multi-step migrations.
## Tips
- Have Cline write a markdown file summarizing the plan for future reference
- Use [file mentions](/core-workflows/working-with-files) to point Cline at relevant files during planning
- Switch back to Plan mode when encountering unexpected complexity rather than pushing through
- Enable [Checkpoints](/core-workflows/checkpoints) before Act mode so you can roll back if needed
- For large tasks, ask Cline to create a todo list during planning that you can track in Act mode

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