Compare commits

...

64 Commits

Author SHA1 Message Date
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
Saoud Rizwan fffe626b3a Bump CLI version from 2.0.3 to 2.0.4 2026-02-05 02:07:38 -08:00
Saoud Rizwan 2dccd6f6f6 fix(cli): use value import for React instead of type-only import
JSX requires React as a value when using jsx: react in tsconfig.
2026-02-05 02:04:22 -08:00
Saoud Rizwan 39971128cb fix(cli): use value import for React instead of type-only import 2026-02-05 02:00:43 -08:00
Saoud Rizwan b725490582 fix(cli): fix cursor position after pasting text
Use refs instead of state values in useInput callback to avoid stale
closures. Also manually update textInputRef before calling setCursorPos
so the bounds check uses the correct new text length.
2026-02-05 01:51:25 -08:00
Saoud Rizwan 8f78645154 fix(cli): show default model name when no model configured
ChatView was returning empty string when the model ID key didn't exist
in state, causing first-time CLI users to see a blank model name. Added
fallback to getProviderDefaultModelId() to match WelcomeView's behavior.
2026-02-05 01:33:24 -08:00
Saoud Rizwan 0ef1c0bf47 fix(cli): make robot animation static on click or drag
Previously the animated robot only became static when the user scrolled.
Now it also becomes static when clicking or dragging, giving users more
ways to dismiss the animation. Renamed onScroll to onInteraction to
reflect the broader scope.
2026-02-05 01:23:02 -08:00
Bee 4c07df370b chore: update biome configuration and linting rules (#9109)
* chore: update biome configuration and linting rules

Update @biomejs/biome package to latest version: 2.3.14

- Change $schema to point to local node_modules for better IDE performance and stability.
- Enable and promote several linting rules from "off" to "info" or "warn" across correctness, style, suspicious, and complexity categories.
- Update file inclusion/exclusion patterns to use more explicit formatting and set ignoreUnknown to true.
- Improve code quality enforcement by surfacing potential issues such as non-null assertions, useless constructors, and implicit any types.

* package-lock udpate

* includes tailwind

* useIterableCallbackReturn
2026-02-04 19:40:38 -08:00
ClineXDiego f440f3a5dd fix: use vscode.env.openExternal for auth in remote environments (#9111)
* fix: use vscode.env.openExternal for auth in remote environments

Fixes #5109

The OAuth authentication flow was broken in VS Code Server and remote
environments because the code used the npm 'open' package directly, which
tries to launch a browser on the server itself (which has no display).

This change routes browser URL opening through VS Code's native
vscode.env.openExternal() API via the HostBridge pattern, which properly
forwards URLs to the user's local machine in remote environments.

Changes:
- Added openExternal RPC to proto/host/env.proto
- Created VS Code handler using vscode.env.openExternal()
- Updated src/utils/env.ts to use HostProvider.env.openExternal()
- Added openExternal to CLI CliEnvServiceClient (uses npm 'open')
- Added openExternal to CLI ACPEnvServiceClient (uses npm 'open')

Related issues: #5394, #2152, #7971

* chore: add changeset for vscode server auth fix

* refactor: extract shared openUrlInBrowser utility for CLI
2026-02-04 19:31:20 -08:00
Tomás Barreiro 3ce1ad3504 Parse remotely configured R2 options (#9090)
* Parse remotely configured R2 options

* Fix R2 options
2026-02-05 03:53:44 +01:00
Tomás Barreiro 00bc38d4e0 Add Workspace Configuration to commit generation (#9107) 2026-02-05 02:25:17 +01:00
Tomás Barreiro a1f2601fe0 Replace the LiteLLM model selector with autocomplete (#9075)
* Replace the LiteLLM model selector with autocomplete

* Add changeset

* refactor
2026-02-04 12:42:08 -08:00
Max 8e3689a5d6 tag released cli versions (#9071)
Co-authored-by: Max Paulus 🥪 <max@cline.bot>
2026-02-04 12:10:02 -08:00
Tomás Barreiro a64f46a5f4 Lock remotely configured Anthropic options (#9087)
* Add Anthropic to the remote config provider settings

* Lock the Anthropic Base URL when it's remotely configured
2026-02-04 19:42:02 +01:00
Tomás Barreiro 507483b2c3 Add Anthropic to the remote config provider settings (#9084) 2026-02-04 19:34:18 +01:00
Marco Alejandro Chavez Santos 42ce100143 Add Authentication Button on HICAP provider to get API KEY (#9098)
* add auth option to get API-KEY for hicap from hicap dashboard website

* remove default hicap model selection

* change url hicap get api keys, add useEffect when update hicapApiKey

* add changeset
2026-02-04 10:29:03 -08:00
CandiedUniverse 7be4e6c6d3 Remove isExperimental flag from Parallel Tool Calls feature setting. (#9097) 2026-02-04 09:47:41 -08:00
Ara 09b91a1ea5 chore: update CODEOWNERS assignments (#9096)
- Remove /docs/ from code ownership
- Update /.github/ owners to @arafatkatze, @maxpaulus43, @candieduniverse
- Update /README.md owner to @juanpflores
- Remove former owners @garoth, @sjf, @nickbaumann98
2026-02-04 09:29:15 -08:00
Tomás Barreiro 7127a2ffa7 Clean old API keys that are stored in secrets (#9091) 2026-02-04 12:02:17 -03:00
Bee d6987d4578 chore: update package-lock.json (#9079) 2026-02-03 21:09:56 -08:00
Tomás Barreiro 7b59cbcb5c Add r2 Blob storage options (#9052) 2026-02-04 03:49:10 +01:00
Ara 0e26ba46d0 fix(ci): always run npm ci regardless of cache hit status (#9078)
Remove conditional checks that skipped dependency installation when
cache was hit. The npm cache speeds up npm ci but does not replace
the need to run it - node_modules still needs to be populated.
2026-02-03 16:34:57 -08:00
Max 3b313ae41f remove prepublish script (#9077)
- this was breaking the publish npm workflow when we try to run npm
publish from the dist-standalone folder (dist-standalone doesn't have
the esbuilt.ts file)
- we don't need this script anyway because we use the npm-main.yaml
workflow to publish the cli

Co-authored-by: Max Paulus 🥪 <max@cline.bot>
2026-02-03 15:54:37 -08:00
CandiedUniverse 7e74f35f23 feat(hooks): Call combineHookSequences() properly from the CLI. (#9066) 2026-02-03 14:19:56 -08:00
Saoud Rizwan 59c05f4b84 Update copyright year to 2026 2026-02-03 13:51:23 -08:00
Saoud Rizwan 5d6424682f Update README.md 2026-02-03 13:50:49 -08:00
Robin Newhouse b1a8db252a fix(cli): prevent hang when spawned without TTY (#9073)
* fix(cli): prevent hang when spawned without TTY

When the CLI is spawned as a child process without a TTY (e.g., from
spawn() in smoke tests or CI), process.stdin.isTTY is false even when
nothing is piped to stdin. This caused readStdinIfPiped() to wait up
to 5 minutes for input that would never arrive.

Fix by using fs.fstatSync(0) to check if stdin is actually a FIFO
(pipe) or file before waiting. This correctly handles:
- Spawned processes without TTY → returns immediately
- Actual piped input (echo "x" | cline) → waits and reads
- stdin from /dev/null → returns immediately

* chore: add changeset

* test(cli): add tests for stdin type detection
2026-02-03 13:45:56 -08:00
Tony Loehr 5ae47fb90b Update docs for CLI 2.0 and fix workflow (#9068)
* Add ACP editor integrations documentation with JetBrains and Neovim video demos

* Add Model Orchestration documentation with --config and --thinking flags

- Document --config and --thinking flags in CLI reference
- Create new model-orchestration.mdx sample page
- Add patterns for CI/CD review, task phase optimization, and multi-model consensus
- Link to production GitHub Actions workflow
- Update samples overview with new card
- Update docs navigation

* Add Worktree Workflows documentation with --cwd flag

- Document --cwd flag in CLI reference
- Create comprehensive worktree-workflows.mdx sample page
- Add patterns for parallel execution and cross-worktree piping
- Include real-world examples and best practices
- Add CLI section to features/worktrees.mdx for discoverability
- Update samples overview and navigation
- Cross-link between CLI and VS Code worktree docs

* Remove broken image references from worktrees documentation

- Remove worktrees-overview.png Frame (image not available)
- Remove worktrees-merge.png Frame (image not available)
- Documentation remains fully functional with comprehensive text explanations

* Remove accidentally committed local test file

- Delete src/test/verify-platformio-mcp.ts which was causing CI failures
- File contained TypeScript errors and hardcoded local paths
- Was meant for local testing only, should not have been committed

* Add native JetBrains plugin recommendation to ACP docs

- Add prominent Note recommending native JetBrains plugin
- Link directly to JetBrains installation section
- Position ACP setup as an alternative approach
- Keep all existing ACP content and video

* docs: refine CLI reference formatting and ACP title

Improve CLI reference readability with clearer headings and descriptions, and clarify the ACP editor integration page title for better discovery.docs: refine CLI reference formatting and ACP title

Improve CLI reference readability with clearer headings and descriptions, and clarify the ACP editor integration page title for better discovery.

* Fix CLI 2.0 syntax in model-orchestration.mdx

- Updated issue analysis pipeline to use shell variables for passing context
- Added explanatory note about why direct piping doesn't work
- Corrected example to complete each phase before starting the next
- All examples now use proper CLI 2.0 syntax

* Completely rewrite cli-reference.mdx with accurate CLI 2.0 information

- Removed all outdated CLI 1.0 content (instance management, Cline Core architecture, gRPC references)
- Added accurate CLI 2.0 commands: task, history, config, auth, update, version, dev
- Corrected all command flags and options based on actual man page
- Added proper examples for all commands
- Included environment variables documentation (CLINE_DIR, CLINE_COMMAND_PERMISSIONS)
- Added shell completion instructions
- Removed incorrect three-layer architecture description
- All content now matches cli/man/cline.1.md source of truth

Fixes outdated documentation issue mentioned in PR#9036

* Fix MDX syntax error in cli-reference.mdx

- Replace angle bracket URLs with proper markdown links
- MDX parser was interpreting <https://...> as invalid HTML tags
- Now uses [url](url) format which is proper MDX syntax

Fixes deployment validation error

* docs: Add GitHub PR Review sample and modernize Actions integration

* fix: Add cline installation step to PR review workflow

- Fix CI/CD failure by actually installing cline before running it
- Update docs model ID to match workflow (claude-opus-4-5-20251101)
- Change from 'npx cline version' to 'npm install -g cline' + 'cline version'

---------

Co-authored-by: Renee Huang <renee@cline.bot>
2026-02-03 13:31:35 -08:00
Tomás Barreiro 28c2697ae1 Refresh LiteLLM models (#9070)
* Refresh LiteLLM models

* return promise

* Disable button while loading

* Loading

* Await the fetch
2026-02-03 21:57:06 +01:00
Saoud Rizwan cf01317885 fix(cli): await applyProviderConfig in handleProviderSelect
applyProviderConfig is async and for Cline/OpenRouter providers it
awaits fetching model data before setting state. When switching to
an already-configured provider (Cline, OCA), the call wasn't awaited,
so refreshModelIds() ran before the model ID was set in state,
causing the model to not update to the default.
2026-02-03 12:56:32 -08:00
Saoud Rizwan 7d5eebe192 Bump CLI version from 2.0.2 to 2.0.3 2026-02-03 12:52:21 -08:00
Saoud Rizwan 91a3636356 refactor(cli): add applyBedrockConfig utility, simplify saveConfiguration
- Added applyBedrockConfig to provider-config.ts for AWS Bedrock setup
- AuthView saveConfiguration now uses applyProviderConfig/applyBedrockConfig
- SettingsPanelContent handleBedrockComplete now uses applyBedrockConfig
- Removed duplicate Bedrock config building code from both components
- Cleaned up unused imports

# Conflicts:
#	cli/src/components/SettingsPanelContent.tsx
2026-02-03 12:39:41 -08:00
Saoud Rizwan 5d02eea9cd refactor(cli): use applyProviderConfig in ImportView, remove legacy apiProvider
- ImportView now uses applyProviderConfig instead of manual config building
- Removed legacy apiProvider field from AuthView, ImportView, SettingsPanelContent
  (it's unused - runtime reads actModeApiProvider/planModeApiProvider instead)
2026-02-03 12:39:41 -08:00
Saoud Rizwan 45b2786dbf fix(cli): ensure welcomeViewCompleted is flushed after applyProviderConfig
applyProviderConfig calls flushPendingState internally, so any state
set after it needs its own flush. Added explicit flush after setting
welcomeViewCompleted in OCA and OpenAI Codex auth success handlers.
2026-02-03 12:39:41 -08:00
Saoud Rizwan 4924192b64 refactor(cli): use applyProviderConfig for auth success handlers
Simplifies OCA, Cline, and OpenAI Codex auth success handlers in
AuthView to use the shared applyProviderConfig utility instead of
manually constructing provider config objects.

This removes duplicated logic around mode-specific provider keys
and model ID keys that applyProviderConfig already handles.
2026-02-03 12:39:41 -08:00
Max 28c548b3ee simplify package-npm script (#9067)
cli/package.json is already formatted correctly for publishing

Co-authored-by: Max Paulus 🥪 <max@cline.bot>
2026-02-03 12:36:47 -08:00
Bee 9b9035ea4a feat: add authentication support to oca provider in CLI (#9059)
* feat: add authentication support to oca provider in CLI

This change integrates the OcaAuthService into the AuthView component. It adds a new 'oca_auth' step to the authentication flow, allowing users to select 'oca' as a provider and initiate the authentication request via OcaAuthService.

* fix(cli): add subscription to OCA auth status updates

The OCA auth flow was missing the subscription mechanism to know when
browser auth completes. Without this, the CLI would spin indefinitely
after opening the browser.

Added a useEffect that subscribes to OcaAuthService.subscribeToAuthStatusUpdate
when in oca_auth step. When auth succeeds (user.uid present), saves the
provider config and transitions to success.

* fix(cli): add OCA auth support to SettingsPanelContent

AuthView only handles onboarding. Users also need to be able to switch
to OCA provider from the settings panel after initial setup.

Added:
- handleOcaLogin callback to start OAuth flow
- useEffect subscription to OCA auth status updates
- Case in handleProviderSelect for "oca" provider
- Escape key handling to cancel OCA auth
- UI for "Waiting for OCA sign-in..." state
- isWaitingForOcaAuth to input disabled check

* refactor(cli): extract OCA auth logic into useOcaAuth hook

Reduces code duplication between AuthView and SettingsPanelContent by
extracting the OCA auth subscription and state management into a
reusable hook.

The hook handles:
- Starting the OAuth flow (initialize + createAuthRequest)
- Subscribing to auth status updates
- Tracking waiting state
- Calling onSuccess callback when auth completes
- Exposing isAuthenticated for checking existing sessions

Both components now use the hook with their own onSuccess handlers
for component-specific state updates.

---------

Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
2026-02-03 12:28:26 -08:00
Bee e4b39aeb22 fix: apply models cache retrieval across model refresh functions (#8976)
* fix: apply models cache retrieval across model refresh functions

This change introduces a unified caching mechanism for model information retrieved from various API providers (Groq, OpenRouter, Vercel). Each service now first checks if the data is available in the shared StateManager's cache before making an API request. This improves performance by leveraging cached results and reduces redundant network calls when refreshing models multiple times. The cache is stored in memory for quick access during subsequent calls within a single execution context.

Changes made:
1. Added import of `StateManager` to each relevant model refresh file.
2. Implemented initial cache check logic at the beginning of each function.
3. Updated error handling and logging consistency across services.
4. Added storage back into StateManager's cache after successful API retrieval for Groq, Vercel AI Gateway only (OpenRouter update already handled).

* promises

* add vercelModels

* feat: add 1-hour TTL to model cache

Adds a time-to-live mechanism to the model info cache so that:
- Duplicate fetches are still prevented within a reasonable window
- Users can get new models after 1 hour without restarting VS Code

Changes:
- Add MODEL_CACHE_TTL_MS constant (1 hour)
- Update cache structure to include timestamp alongside data
- Update setModelsCache to store timestamp with data
- Update getModelsCache to check TTL and invalidate expired cache
- Update getModelInfo to also respect TTL

---------

Co-authored-by: Arafatkatze <arafat.da.khan@gmail.com>
2026-02-03 12:18:21 -08:00
Max f7c54e964f cli version bump (#9064)
Co-authored-by: Max Paulus 🥪 <max@cline.bot>
2026-02-03 11:42:46 -08:00
Bee d116ac5dcf feat: render markdown table in UI (#9056)
* feat: display markdown table in UI

Simplify the handlePartialBlock method in AttemptCompletionHandler by:
- Removing conditional logic for command vs no-command cases
- Always displaying partial result if present
- Deferring command handling to the final execution step
This fixes an issue where attempt completion response doesn't get streamed to the UI during partial result.

Also replaced react-remark with react-markdown and remark-gfm dependencies to MarkdownBlock in UI for enhanced markdown rendering support with GitHub Flavored Markdown features, including displaying table.

* add changeset

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

handlePartialBlock hard-codes the partial flag to true when calling uiHelpers.say(...). For consistency with other tool handlers and to avoid incorrect behavior if this method is ever invoked with a non-partial block, pass block.partial through instead.

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

---------

Co-authored-by: Robin Newhouse <robin@cline.bot>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-03 11:41:25 -08:00
Bee ac22d5d81a chore: add CLI type checking and caching to ci workflow (#9049)
* chore: add CLI type checking and caching to ci workflow

- Added a new cache step for CLI dependencies in the GitHub Actions test workflow to improve build performance.
- Included a step to install CLI dependencies using `npm ci`.
- Updated the `ci:check-all` script in `package.json` to include CLI type checking.
- Added a `cli:typecheck` script to handle type checking within the CLI directory.

* Fix type and import issues for cli

* Includes CI tests in test workflow

* use npx npm-run-all

* update ci:check-all

* ci: skip npm ci steps on cache hit in test workflow

Update the test workflow to conditionally run npm installation steps only when a cache hit is not found. This optimization reduces CI execution time by avoiding redundant dependency installations when the node_modules are already restored from cache.

* ci: update cache keys and add dependency verification in test workflow

Updated the cache keys for root, webview-ui, cli, and testing-platform dependencies by adding a version prefix (v1). This ensures a clean cache state and helps avoid potential corruption or mismatch issues.

Additionally, added a verification step in the test job to log cache hit status and check for the presence of key dependencies like biome and globby. This helps diagnose issues where the cache might be restored but dependencies are not correctly available for subsequent steps.

* update Verify and fix root dependencies

* fix type check script

* add isSettingsKey check

* update settingskey set

* apply feedback

* npx

* feat: flashing dot for streaming chat messages in CI (#9054)

Introduce an ink-spinner to the DotRow component to provide visual feedback when messages are being streamed. This improves the CLI user experience by clearly indicating that a tool call or message is currently in progress.

- Add `flashing` prop to `DotRow` component
- Replace static dot with `toggle8` spinner when `flashing` is true
- Update `ChatMessage` to pass `flashing` state based on `isStreaming` and `partial` message properties

Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>

* ci: simplify dependency caching using built-in npm cache

Replace manual actions/cache steps with setup-node's built-in npm caching feature across all workflow jobs. This change:

- Removes redundant cache action steps for root, webview-ui, cli, and testing-platform dependencies
- Uses setup-node's native `cache: 'npm'` option with `cache-dependency-path` to handle multiple package-lock.json files
- Eliminates conditional installation steps based on cache hits
- Reduces workflow complexity and maintenance overhead while maintaining caching functionality

The built-in caching provides the same performance benefits with less configuration and better integration with the Node.js setup action.

---------

Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
2026-02-04 03:34:14 +08:00
Juan Pablo Flores b57aefb5a1 Cli 2.0 docs (#9060)
* docs: restructure CLI reference to web-friendly format

Replace embedded man page format with structured markdown sections
for better readability. Simplify description, reorganize commands and
options into clear categories, and update Next Steps navigation cards.

* Add ACP editor integrations documentation (#9036)

* Add ACP editor integrations documentation with JetBrains and Neovim video demos

* Add Model Orchestration documentation with --config and --thinking flags

- Document --config and --thinking flags in CLI reference
- Create new model-orchestration.mdx sample page
- Add patterns for CI/CD review, task phase optimization, and multi-model consensus
- Link to production GitHub Actions workflow
- Update samples overview with new card
- Update docs navigation

* Add Worktree Workflows documentation with --cwd flag

- Document --cwd flag in CLI reference
- Create comprehensive worktree-workflows.mdx sample page
- Add patterns for parallel execution and cross-worktree piping
- Include real-world examples and best practices
- Add CLI section to features/worktrees.mdx for discoverability
- Update samples overview and navigation
- Cross-link between CLI and VS Code worktree docs

* Remove broken image references from worktrees documentation

- Remove worktrees-overview.png Frame (image not available)
- Remove worktrees-merge.png Frame (image not available)
- Documentation remains fully functional with comprehensive text explanations

* Remove accidentally committed local test file

- Delete src/test/verify-platformio-mcp.ts which was causing CI failures
- File contained TypeScript errors and hardcoded local paths
- Was meant for local testing only, should not have been committed

* Add native JetBrains plugin recommendation to ACP docs

- Add prominent Note recommending native JetBrains plugin
- Link directly to JetBrains installation section
- Position ACP setup as an alternative approach
- Keep all existing ACP content and video

* docs: refine CLI reference formatting and ACP title

Improve CLI reference readability with clearer headings and descriptions, and clarify the ACP editor integration page title for better discovery.docs: refine CLI reference formatting and ACP title

Improve CLI reference readability with clearer headings and descriptions, and clarify the ACP editor integration page title for better discovery.

* Fix CLI 2.0 syntax in model-orchestration.mdx

- Updated issue analysis pipeline to use shell variables for passing context
- Added explanatory note about why direct piping doesn't work
- Corrected example to complete each phase before starting the next
- All examples now use proper CLI 2.0 syntax

* Completely rewrite cli-reference.mdx with accurate CLI 2.0 information

- Removed all outdated CLI 1.0 content (instance management, Cline Core architecture, gRPC references)
- Added accurate CLI 2.0 commands: task, history, config, auth, update, version, dev
- Corrected all command flags and options based on actual man page
- Added proper examples for all commands
- Included environment variables documentation (CLINE_DIR, CLINE_COMMAND_PERMISSIONS)
- Added shell completion instructions
- Removed incorrect three-layer architecture description
- All content now matches cli/man/cline.1.md source of truth

Fixes outdated documentation issue mentioned in PR#9036

* Fix MDX syntax error in cli-reference.mdx

- Replace angle bracket URLs with proper markdown links
- MDX parser was interpreting <https://...> as invalid HTML tags
- Now uses [url](url) format which is proper MDX syntax

Fixes deployment validation error

---------

Co-authored-by: Renee Huang <renee@cline.bot>

* docs: enhance interactive mode documentation with structured settings overview

* docs: restructure and improve CLI reference documentation

- Reorganize command structure with clearer global options section
- Add mode behavior table explaining interactive vs plain text modes
- Improve option descriptions with consistent formatting
- Add horizontal rules between sections for better readability
- Document timeout option and environment variables more clearly
- Add Tips & Tricks section for common usage patterns
- Update frontmatter description to reflect content changes

* docs: improve ACP editor integrations page with editor descriptions

- Update page title to be more concise ("ACP: Editor Integrations")
- Remove redundant H1 header that duplicated the title
- Add introductory descriptions for JetBrains, Neovim, and Zed sections
- Rename "Zed Editor" section to just "Zed" for consistency

* docs: expand CLI reference with modes of operation and agent behavior

* Update docs/cline-cli/cli-reference-deprecated.mdx

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

---------

Co-authored-by: Tony Loehr <turingxo@gmail.com>
Co-authored-by: Renee Huang <renee@cline.bot>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-03 11:29:15 -08:00
Max 11da3ee89e add windows to cli publish package json (#9063)
Co-authored-by: Max Paulus 🥪 <max@cline.bot>
2026-02-03 11:24:07 -08:00
Bee e4e63912dd feat: add API key support for Cline provider (#9057)
* feat: add API key support for Cline provider

Add support for authenticating with Cline provider using an API key as an alternative to account-based authentication. This change allows users to configure Cline with either a direct API key or through the existing account authentication flow.

Changes:
- Add `clineApiKey` option to ClineHandler and pass through API configuration
- Update authentication check to accept either API key or account ID
- Modify provider configuration detection to check both auth methods
- Remove automatic Cline auth flow trigger on provider selection
- Add `clineApiKey` to provider-to-API-key mapping for proper key management

This provides more flexibility in authentication methods while maintaining backward compatibility with existing account-based authentication.

* promise all

---------

Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
2026-02-03 10:56:04 -08:00
Tomás Barreiro f17d523b2e Fix OTEL endpoints (#9050)
* Fix OTEL endpoints

* refactor
2026-02-03 19:24:01 +01:00
Max edbba8b7f6 return empty mcp config if cline_mcp_settings.json doesn't exist or is empty file (#9061)
Co-authored-by: Max Paulus 🥪 <max@cline.bot>
2026-02-03 10:12:52 -08:00
Max 7b09999fdf add man page to cli/package.json (#9044) 2026-02-02 21:45:00 -08:00
Tomás Barreiro 01240744a2 Remove reliance on the extensionEnabled flag and verify the source of truth (#9046)
* Remove reliance on the extensionEnabled flag and verify the source of truth

* Fix tests

* Add try block

* Fix telemetrySetting checks
2026-02-03 06:40:17 +01:00
Saoud Rizwan 2944416758 feat(cli): show contextual hints when in settings subpages
When navigating to subpages within the Settings panel (model picker,
provider picker, language picker, etc.), the Panel header now shows
"Esc to go back" instead of "Esc to close" and hides the arrow key
navigation hint since tabs cannot be switched while in a subpage.
2026-02-02 21:08:36 -08:00
alex-lum b5b503dd50 adding in org and member tracking (#9037) 2026-02-02 18:54:58 -08:00
148 changed files with 14212 additions and 11175 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"cline": patch
---
Replace the LiteLLM model list with a selector
+5
View File
@@ -0,0 +1,5 @@
---
"cline": patch
---
fix(cli): prevent hang when spawned without TTY
+5
View File
@@ -0,0 +1,5 @@
---
"cline": patch
---
Add Claude Opus 4.6 model support
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": patch
---
Fix decimal input crash in OpenAI Compatible price fields (#8129)
+5
View File
@@ -0,0 +1,5 @@
---
"cline": patch
---
Supports rendering markdown table in chat view.
+5
View File
@@ -0,0 +1,5 @@
---
"cline": patch
---
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.
+7
View File
@@ -0,0 +1,7 @@
---
"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
@@ -0,0 +1,5 @@
---
"cline": patch
---
Fix Bedrock model id
-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
-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": minor
---
Add Generate API Key on Hicap Provider selection
+2 -3
View File
@@ -1,3 +1,2 @@
/docs/
/.github/ @saoudrizwan @garoth @sjf
/README.md @saoudrizwan @nickbaumann98
/.github/ @saoudrizwan @arafatkatze @maxpaulus43 @candieduniverse
/README.md @saoudrizwan @juanpflores
+2 -1
View File
@@ -62,7 +62,8 @@ jobs:
- name: Install and Verify Cline CLI
run: |
npx cline version # verify installation
npm install -g cline
cline version # verify installation
- name: Configure Cline with Anthropic
run: |
+8 -1
View File
@@ -9,7 +9,7 @@ on:
type: string
permissions:
contents: read
contents: write # Required for pushing tags
checks: write # Required by test workflow
pull-requests: write # Required by test workflow
@@ -88,6 +88,13 @@ jobs:
cd dist-standalone
npm publish --tag latest --access public
- name: Tag release
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git tag "v${{ steps.version.outputs.version }}-cli"
git push origin "v${{ steps.version.outputs.version }}-cli"
- name: Summary
run: |
echo "✅ Successfully published cline@${{ steps.version.outputs.version }} to NPM with tag 'latest'"
-2
View File
@@ -55,11 +55,9 @@ jobs:
key: ${{ runner.os }}-npm-webview-${{ hashFiles('webview-ui/package-lock.json') }}
- name: Install root dependencies
if: steps.root-cache.outputs.cache-hit != 'true'
run: npm ci --include=optional
- name: Install webview-ui dependencies
if: steps.webview-cache.outputs.cache-hit != 'true'
run: cd webview-ui && npm ci --include=optional
- name: Install Publishing Tools
+18 -50
View File
@@ -28,20 +28,10 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: 22
- name: Cache root dependencies
uses: actions/cache@v4
id: root-cache
with:
path: node_modules
key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json') }}
- name: Cache webview-ui dependencies
uses: actions/cache@v4
id: webview-cache
with:
path: webview-ui/node_modules
key: ${{ runner.os }}-npm-webview-${{ hashFiles('webview-ui/package-lock.json') }}
cache: 'npm'
cache-dependency-path: |
package-lock.json
webview-ui/package-lock.json
- name: Install root dependencies
run: npm ci
@@ -73,20 +63,10 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: 22
- name: Cache root dependencies
uses: actions/cache@v4
id: root-cache
with:
path: node_modules
key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json') }}
- name: Cache webview-ui dependencies
uses: actions/cache@v4
id: webview-cache
with:
path: webview-ui/node_modules
key: ${{ runner.os }}-npm-webview-${{ hashFiles('webview-ui/package-lock.json') }}
cache: 'npm'
cache-dependency-path: |
package-lock.json
webview-ui/package-lock.json
- name: Install root dependencies
run: npm ci
@@ -135,6 +115,11 @@ jobs:
cd webview-ui
npm run test:coverage
- name: CLI Tests
id: cli_tests
if: ${{ !cancelled() && steps.build_step.outcome == 'success' }}
run: cd cli && npm run test:run
- name: Save Coverage Reports
uses: actions/upload-artifact@v4
# Only upload artifacts on Linux - We only need coverage from one OS
@@ -156,28 +141,11 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: 22
- name: Cache root dependencies
uses: actions/cache@v4
id: root-cache
with:
path: node_modules
key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json') }}
- name: Cache webview-ui dependencies
uses: actions/cache@v4
id: webview-cache
with:
path: webview-ui/node_modules
key: ${{ runner.os }}-npm-webview-${{ hashFiles('webview-ui/package-lock.json') }}
# Cache testing-platform dependencies
- name: Cache testing-platform dependencies
uses: actions/cache@v4
id: testing-platform-cache
with:
path: testing-platform/node_modules
key: ${{ runner.os }}-npm-testing-platform-${{ hashFiles('testing-platform/package-lock.json') }}
cache: 'npm'
cache-dependency-path: |
package-lock.json
webview-ui/package-lock.json
testing-platform/package-lock.json
- name: Install root dependencies
run: npm ci
+27
View File
@@ -1,5 +1,32 @@
# Changelog
## [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
+1 -1
View File
@@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2025 Cline Bot Inc.
Copyright 2026 Cline Bot Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
+1 -1
View File
@@ -148,4 +148,4 @@ Get the same Cline experience with enterprise-grade controls: SSO (SAML/OIDC), g
## License
[Apache 2.0 © 2025 Cline Bot Inc.](./LICENSE)
[Apache 2.0 © 2026 Cline Bot Inc.](./LICENSE)
+76 -67
View File
@@ -1,5 +1,5 @@
{
"$schema": "https://biomejs.dev/schemas/2.1.4/schema.json",
"$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
@@ -28,19 +28,19 @@
"rules": {
"recommended": true,
"correctness": {
"useExhaustiveDependencies": "off",
"useExhaustiveDependencies": "info",
"noUndeclaredVariables": "off",
"noEmptyPattern": "off",
"noEmptyPattern": "info",
"useJsxKeyInIterable": "off",
"noInnerDeclarations": "off",
"useHookAtTopLevel": "off",
"useYield": "off",
"useHookAtTopLevel": "info",
"useYield": "info",
"noConstructorReturn": "off",
"noInvalidPositionAtImportRule": "off",
"noSwitchDeclarations": "off",
"noUnusedImports": "error"
},
"a11y": "off",
"a11y": "info",
"style": {
"useNodejsImportProtocol": "off",
"useImportType": "off",
@@ -51,35 +51,36 @@
"noParameterAssign": "off",
"useAsConstAssertion": "off",
"useDefaultParameterLast": "off",
"noNonNullAssertion": "off",
"noNonNullAssertion": "info",
"useEnumInitializers": "off",
"useSelfClosingElements": "off",
"useSelfClosingElements": "info",
"useSingleVarDeclarator": "off",
"useNumberNamespace": "off",
"noInferrableTypes": "off",
"useTemplate": "off",
"noUselessElse": "off"
"useNumberNamespace": "info",
"noInferrableTypes": "info",
"useTemplate": "info",
"noUselessElse": "info"
},
"suspicious": {
"noDoubleEquals": "warn",
"noImplicitAnyLet": "info",
"noThenProperty": "off",
"noAsyncPromiseExecutor": "off",
"noAsyncPromiseExecutor": "info",
"noImportAssign": "off",
"noExplicitAny": "off",
"noControlCharactersInRegex": "off",
"noExplicitAny": "info",
"noControlCharactersInRegex": "warn",
"noShadowRestrictedNames": "off",
"noArrayIndexKey": "info",
"noAssignInExpressions": "info"
"noAssignInExpressions": "info",
"useIterableCallbackReturn": "info"
},
"complexity": {
"noUselessConstructor": "off",
"useOptionalChain": "off",
"noBannedTypes": "off",
"useLiteralKeys": "off",
"noUselessCatch": "off",
"noUselessSwitchCase": "off",
"noStaticOnlyClass": "off"
"noUselessConstructor": "info",
"useOptionalChain": "info",
"noBannedTypes": "warn",
"useLiteralKeys": "info",
"noUselessCatch": "info",
"noUselessSwitchCase": "info",
"noStaticOnlyClass": "info"
},
"security": {
"noDangerouslySetInnerHtml": "info"
@@ -94,6 +95,11 @@
"lineEnding": "lf",
"formatWithErrors": true
},
"css": {
"parser": {
"tailwindDirectives": true
}
},
"javascript": {
"formatter": {
"semicolons": "asNeeded",
@@ -112,19 +118,21 @@
}
},
"files": {
"ignoreUnknown": true,
"includes": [
"**",
"!**/dist",
"!**/dist-*",
"!**/out",
"!**/evals",
"!**/playwright",
"!**/test-results",
"!**/node_modules",
"!**/webview-ui/build",
"!**/generated",
"!**/proto",
"!**/tests/specs"
// explicitly force files to be ignored by the scanner with !!
"!!**/dist",
"!!**/dist-*",
"!!**/out",
"!!**/evals",
"!!**/playwright",
"!!**/test-results",
"!!**/node_modules",
"!!**/webview-ui/build",
"!!**/generated",
"!!**/proto",
"!!**/tests/specs"
]
},
"plugins": [
@@ -134,14 +142,15 @@
{
"includes": [
"**",
"!**/hosts/vscode/**",
"!**/test/**",
"!**/*.test.ts",
"!src/dev/**",
"!src/extension.ts",
"!src/integrations/git/commit-message-generator.ts",
"!src/integrations/terminal/**",
"!src/core/controller/ui/openWalkthrough.ts"
"!!**/dist",
"!!**/hosts/vscode/**",
"!!**/test/**",
"!!**/*.test.ts",
"!!src/dev/**",
"!!src/extension.ts",
"!!src/integrations/git/commit-message-generator.ts",
"!!src/integrations/terminal/**",
"!!src/core/controller/ui/openWalkthrough.ts"
],
"plugins": [
"src/dev/grit/vscode-api.grit"
@@ -154,37 +163,37 @@
],
"includes": [
"**",
"!**/esbuild.*",
"!**/*.mts",
"!**/webview-ui/**",
"!**/evals/**",
"!**/standalone/**",
"!**/cli/**",
"!**/e2e/**",
"!**/test/**",
"!**/__tests__/**",
"!**/*.test.ts",
"!**/*.stories.ts",
"!src/dev/**",
"!**/*.mjs",
"!**/*.js",
"!**/scripts/**",
"!**/*.tsx",
"!**/testing-platform/**",
"!!**/esbuild.*",
"!!**/*.mts",
"!!**/webview-ui/**",
"!!**/evals/**",
"!!**/standalone/**",
"!!**/cli/**",
"!!**/e2e/**",
"!!**/test/**",
"!!**/__tests__/**",
"!!**/*.test.ts",
"!!**/*.stories.ts",
"!!src/dev/**",
"!!**/*.mjs",
"!!**/*.js",
"!!**/scripts/**",
"!!**/*.tsx",
"!!**/testing-platform/**",
// ACP mode must redirect console to stderr - this is intentional
"!cli/src/acp/index.ts"
"!!cli/src/acp/index.ts"
]
},
{
"includes": [
"**",
"!src/core/storage/state-migrations.ts",
"!src/core/storage/FileContextTracker.ts",
"!src/core/context/context-tracking/FileContextTracker.ts",
"!src/common.ts",
"!src/services/logging/distinctId.ts",
"!src/core/storage/utils/state-helpers.ts",
"!src/extension.ts"
"!!src/core/storage/state-migrations.ts",
"!!src/core/storage/FileContextTracker.ts",
"!!src/core/context/context-tracking/FileContextTracker.ts",
"!!src/common.ts",
"!!src/services/logging/distinctId.ts",
"!!src/core/storage/utils/state-helpers.ts",
"!!src/extension.ts"
],
"plugins": [
"src/dev/grit/use-cache-service.grit"
+35 -13
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
@@ -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:
@@ -274,6 +282,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
@@ -348,20 +371,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
+20
View File
@@ -70,6 +70,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.
@@ -154,6 +156,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 +255,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
-2950
View File
File diff suppressed because it is too large Load Diff
+14 -5
View File
@@ -1,25 +1,34 @@
{
"name": "cline",
"version": "2.0.1",
"version": "2.0.5",
"description": "Autonomous coding agent CLI - capable of creating/editing files, running commands, using the browser, and more",
"main": "dist/cli.mjs",
"bin": {
"cline": "./dist/cli.mjs"
},
"os": [
"darwin",
"linux",
"win32"
],
"cpu": [
"x64",
"arm64"
],
"man": "./man/cline.1",
"type": "module",
"engines": {
"node": ">=20.0.0"
},
"scripts": {
"prepublishOnly": "npm run build:production",
"package:brew": "npx tsx ./scripts/update-brew-formula.mts",
"package": "npm pack --pack-destination ./dist",
"build": "npx tsx esbuild.mts",
"build:production": "npx tsx esbuild.mts --production",
"build": "npm run typecheck && npx tsx esbuild.mts",
"build:production": "npm run typecheck && npx tsx esbuild.mts --production",
"watch": "npx tsx esbuild.mts --watch",
"dev": "IS_DEV=true && npm run link && npm run watch ; npm run unlink",
"clean": "rimraf dist",
"typecheck": "tsc --noEmit",
"typecheck": "npx tsc --noEmit",
"link": "npm run build && npm link",
"unlink": "npm unlink -g cline",
"test": "vitest",
@@ -172,6 +172,16 @@ class ACPEnvServiceClient implements EnvServiceClientInterface {
Logger.debug("[ACPEnvServiceClient] shutdown called (stub)")
return proto.cline.Empty.create()
}
async openExternal(request: proto.cline.StringRequest): Promise<proto.cline.Empty> {
const url = request.value || ""
if (url) {
Logger.debug(`[ACPEnvServiceClient] openExternal: ${url}`)
const { openUrlInBrowser } = await import("../utils/browser")
await openUrlInBrowser(url)
}
return proto.cline.Empty.create()
}
}
/**
+3 -5
View File
@@ -337,9 +337,7 @@ export class ClineAgent implements acp.Agent {
// Use provider-specific model ID key (e.g., cline uses actModeOpenRouterModelId)
const modelKey = currentProvider ? getProviderModelIdKey(currentProvider, mode) : null
const currentModelId = modelKey
? (stateManager.getGlobalSettingsKey(modelKey as string) as string | undefined)
: undefined
const currentModelId = modelKey ? stateManager.getGlobalSettingsKey(modelKey) : undefined
// Build the current model ID in provider/model format
const currentFullModelId =
@@ -1160,8 +1158,8 @@ export class ClineAgent implements acp.Agent {
if (currentProvider === "cline") {
// For Cline provider, check if we have stored auth data
const authData = await secretStorage.get("cline:clineAccountId")
return !!authData
const values = await Promise.all(["clineApiKey", "clineAccountId"].map((key) => secretStorage.get(key)))
return values.some(Boolean)
}
// For OpenAI Codex provider, check OAuth credentials
+14 -8
View File
@@ -34,7 +34,7 @@ type AsciiMotionCliProps = {
autoPlay?: boolean;
loop?: boolean;
onReady?: (api: PlaybackAPI) => void;
onScroll?: () => void; // Called when user scrolls (scroll wheel)
onInteraction?: () => void; // Called when user scrolls, clicks, or drags
};
const FRAMES: FrameData[] = [
@@ -333364,7 +333364,7 @@ const FRAME_BOTTOM_RIGHT = 128;
export const AsciiMotionCli: React.FC<AsciiMotionCliProps> = ({
hasDarkBackground = true,
onScroll,
onInteraction,
}) => {
const [frameIndex, setFrameIndex] = useState(0);
const [targetFrame, setTargetFrame] = useState(0);
@@ -333390,13 +333390,13 @@ export const AsciiMotionCli: React.FC<AsciiMotionCliProps> = ({
// Stop animation on terminal resize to prevent visual glitches
useEffect(() => {
const handleResize = () => {
onScroll?.();
onInteraction?.();
};
process.stdout.on("resize", handleResize);
return () => {
process.stdout.off("resize", handleResize);
};
}, [onScroll]);
}, [onInteraction]);
// Mouse tracking - gracefully handle environments without tty support
useEffect(() => {
@@ -333417,13 +333417,19 @@ export const AsciiMotionCli: React.FC<AsciiMotionCliProps> = ({
const handleData = (data: Buffer) => {
const str = data.toString();
// Parse mouse events: \x1b[<button;x;yM
// Parse mouse events: \x1b[<button;x;yM (M=press, m=release)
const mouseMatch = str.match(/\x1b\[<(\d+);(\d+);(\d+)([Mm])/);
if (mouseMatch) {
const button = parseInt(mouseMatch[1], 10);
// Button 64 = scroll up, 65 = scroll down
if (button === 64 || button === 65) {
onScroll?.();
const isPress = mouseMatch[4] === "M";
// Button 64/65 = scroll up/down
// Button 0-2 = left/middle/right click (on press)
// Button 32-34 = drag with left/middle/right button held
const isScroll = button === 64 || button === 65;
const isClick = isPress && button >= 0 && button <= 2;
const isDrag = button >= 32 && button <= 34;
if (isScroll || isClick || isDrag) {
onInteraction?.();
}
// Throttle cursor updates to ~20fps to reduce re-renders
const now = Date.now();
+71 -2
View File
@@ -3,7 +3,7 @@
* Handles different types of user interactions (text input, confirmations, choices)
*/
import type { ClineAsk } from "@shared/ExtensionMessage"
import type { ClineAsk, ClineMessage } from "@shared/ExtensionMessage"
import { Box, Text, useApp, useInput } from "ink"
import React, { useCallback, useEffect, useRef, useState } from "react"
import { useStdinContext } from "../context/StdinContext"
@@ -11,7 +11,6 @@ import { useTaskController } from "../context/TaskContext"
import { useLastCompletedAskMessage } from "../hooks/useStateSubscriber"
import { isMouseEscapeSequence } from "../utils/input"
import { jsonParseSafe } from "../utils/parser"
import { getCliMessagePrefixIcon } from "./MessageRow"
interface AskPromptProps {
onRespond?: (response: string) => void
@@ -372,3 +371,73 @@ export const AskPrompt: React.FC<AskPromptProps> = ({ onRespond }) => {
return null
}
}
/**
* Get emoji icon for message type
*/
function getCliMessagePrefixIcon(message: ClineMessage): string {
if (message.type === "ask") {
switch (message.ask) {
case "followup":
return "❓"
case "command":
case "command_output":
return "⚙️"
case "tool":
return "🔧"
case "completion_result":
return "✅"
case "api_req_failed":
return "❌"
case "resume_task":
case "resume_completed_task":
return "▶️"
case "browser_action_launch":
return "🌐"
case "use_mcp_server":
return "🔌"
case "plan_mode_respond":
return "📋"
default:
return "❔"
}
} else {
switch (message.say) {
case "task":
return "📋"
case "error":
return "❌"
case "text":
return "💬"
case "reasoning":
return "🧠"
case "completion_result":
return "✅"
case "user_feedback":
return "👤"
case "command":
case "command_output":
return "⚙️"
case "tool":
return "🔧"
case "browser_action":
case "browser_action_launch":
case "browser_action_result":
return "🌐"
case "mcp_server_request_started":
case "mcp_server_response":
return "🔌"
case "api_req_started":
case "api_req_finished":
return "🔄"
case "checkpoint_created":
return "💾"
case "info":
return "️"
case "generate_explanation":
return "📝"
default:
return " "
}
}
}
+66 -93
View File
@@ -5,20 +5,21 @@
import { Box, Text, useApp, useInput } from "ink"
import Spinner from "ink-spinner"
// biome-ignore lint/style/useImportType: React is used as a value by JSX (jsx: "react" in tsconfig)
import React, { useCallback, useEffect, useMemo, useState } from "react"
import { StateManager } from "@/core/storage/StateManager"
import { openAiCodexOAuthManager } from "@/integrations/openai-codex/oauth"
import { AuthService } from "@/services/auth/AuthService"
import type { ApiProvider } from "@/shared/api"
import { openAiCodexDefaultModelId, openRouterDefaultModelId } from "@/shared/api"
import { getProviderModelIdKey, ProviderToApiKeyMap } from "@/shared/storage"
import { liteLlmDefaultModelId, openAiCodexDefaultModelId, openRouterDefaultModelId } from "@/shared/api"
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 { type BedrockConfig, BedrockSetup } from "./BedrockSetup"
@@ -30,7 +31,7 @@ import {
} from "./FeaturedModelPicker"
import { ImportView } from "./ImportView"
import { getDefaultModelId, hasModelPicker, ModelPicker } from "./ModelPicker"
import { CLI_EXCLUDED_PROVIDERS, getProviderLabel, getProviderOrder } from "./ProviderPicker"
import { getProviderLabel } from "./ProviderPicker"
type AuthStep =
| "menu"
@@ -42,14 +43,12 @@ type AuthStep =
| "success"
| "error"
| "cline_auth"
| "oca_auth"
| "cline_model"
| "openai_codex_auth"
| "bedrock"
| "import"
// Featured models loaded from shared constants
const featuredModels = getAllFeaturedModels()
interface AuthViewProps {
controller: any
onComplete?: () => void
@@ -148,6 +147,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 ||
@@ -166,11 +168,29 @@ export const AuthView: React.FC<AuthViewProps> = ({ controller, onComplete, onEr
const [importSource, setImportSource] = useState<ImportSource | null>(null)
const [bedrockConfig, setBedrockConfig] = useState<BedrockConfig | null>(null)
// Use providers.json order, filtered to exclude CLI-incompatible providers
const sortedProviders = useMemo(() => {
return getProviderOrder().filter((p) => !CLI_EXCLUDED_PROVIDERS.has(p))
// OCA auth hook - enabled when step is oca_auth
const handleOcaAuthSuccess = useCallback(async () => {
await applyProviderConfig({ providerId: "oca", controller })
const stateManager = StateManager.get()
stateManager.setGlobalState("welcomeViewCompleted", true)
await stateManager.flushPendingState()
setSelectedProvider("oca")
setModelId(liteLlmDefaultModelId)
setStep("success")
}, [controller])
const handleOcaAuthError = useCallback((error: Error) => {
setErrorMessage(error.message)
setStep("error")
}, [])
const { startAuth: initiateOcaAuth } = useOcaAuth({
controller,
enabled: step === "oca_auth",
onSuccess: handleOcaAuthSuccess,
onError: handleOcaAuthError,
})
// Main menu items - conditionally include import options
const mainMenuItems: SelectItem[] = useMemo(() => {
const items: SelectItem[] = [{ label: "Sign in with Cline", value: "cline_auth" }]
@@ -196,15 +216,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
@@ -251,22 +269,8 @@ export const AuthView: React.FC<AuthViewProps> = ({ controller, onComplete, onEr
}
if (authState.user && authState.user.email) {
// Auth succeeded - save configuration and transition to success
const stateManager = StateManager.get()
const mode = stateManager.getGlobalSettingsKey("mode") || "act"
const providerKey = mode === "act" ? "actModeApiProvider" : "planModeApiProvider"
// Use provider-specific model ID key (cline uses OpenRouterModelId)
const modelIdKey = getProviderModelIdKey("cline" as ApiProvider, mode as "act" | "plan")
const config: Record<string, string> = {
actModeApiProvider: "cline",
[providerKey]: "cline",
}
if (modelIdKey) {
config[modelIdKey] = openRouterDefaultModelId
}
stateManager.setApiConfiguration(config)
stateManager.flushPendingState()
// Auth succeeded - save configuration and transition to model selection
await applyProviderConfig({ providerId: "cline", controller })
setSelectedProvider("cline")
setModelId(openRouterDefaultModelId)
setStep("cline_model")
@@ -295,23 +299,10 @@ export const AuthView: React.FC<AuthViewProps> = ({ controller, onComplete, onEr
await openAiCodexOAuthManager.waitForCallback()
// Success - save configuration
await applyProviderConfig({ providerId: "openai-codex", controller })
const stateManager = StateManager.get()
const mode = stateManager.getGlobalSettingsKey("mode") || "act"
const providerKey = mode === "act" ? "actModeApiProvider" : "planModeApiProvider"
// Use provider-specific model ID key (openai-codex uses generic apiModelId)
const modelIdKey = getProviderModelIdKey("openai-codex" as ApiProvider, mode as "act" | "plan")
const config: Record<string, string> = {
actModeApiProvider: "openai-codex",
planModeApiProvider: "openai-codex",
[providerKey]: "openai-codex",
}
if (modelIdKey) {
config[modelIdKey] = openAiCodexDefaultModelId
}
stateManager.setApiConfiguration(config)
stateManager.setGlobalState("welcomeViewCompleted", true)
await stateManager.flushPendingState()
setSelectedProvider("openai-codex")
setModelId(openAiCodexDefaultModelId)
setStep("success")
@@ -334,6 +325,12 @@ export const AuthView: React.FC<AuthViewProps> = ({ controller, onComplete, onEr
}
}, [controller])
const startOcaAuth = useCallback(() => {
setStep("oca_auth")
setAuthStatus("Starting authentication...")
initiateOcaAuth()
}, [initiateOcaAuth])
const handleMainMenuSelect = useCallback(
(value: string) => {
if (value === "exit") {
@@ -360,8 +357,8 @@ export const AuthView: React.FC<AuthViewProps> = ({ controller, onComplete, onEr
const handleProviderSelect = useCallback(
(value: string) => {
setSelectedProvider(value)
if (value === "cline") {
startClineAuth()
if (value === "oca") {
startOcaAuth()
} else if (value === "openai-codex") {
setStep("openai_codex_auth")
startOpenAiCodexAuth()
@@ -371,7 +368,7 @@ export const AuthView: React.FC<AuthViewProps> = ({ controller, onComplete, onEr
setStep("apikey")
}
},
[startClineAuth, startOpenAiCodexAuth],
[startOcaAuth, startOpenAiCodexAuth],
)
const handleApiKeySubmit = useCallback(
@@ -391,52 +388,23 @@ export const AuthView: React.FC<AuthViewProps> = ({ controller, onComplete, onEr
const saveConfiguration = useCallback(
async (model: string, base: string) => {
try {
const stateManager = StateManager.get()
// Use provider-specific model ID keys (e.g., cline uses actModeOpenRouterModelId)
const actModelKey = getProviderModelIdKey(selectedProvider as ApiProvider, "act")
const planModelKey = getProviderModelIdKey(selectedProvider as ApiProvider, "plan")
const config: Record<string, string> = {
actModeApiProvider: selectedProvider,
planModeApiProvider: selectedProvider,
apiProvider: selectedProvider,
}
if (actModelKey) config[actModelKey] = model
if (planModelKey) config[planModelKey] = model
// For cline/openrouter, also set model info (required for getModel() to return correct model)
if (selectedProvider === "cline" || selectedProvider === "openrouter") {
const openRouterModels = await controller?.readOpenRouterModels()
const modelInfo = openRouterModels?.[model]
if (modelInfo) {
stateManager.setGlobalState("actModeOpenRouterModelInfo", modelInfo)
stateManager.setGlobalState("planModeOpenRouterModelInfo", modelInfo)
}
}
// Add API key or Bedrock-specific config
if (selectedProvider === "bedrock" && bedrockConfig) {
const bedrockFields: Record<string, unknown> = {
awsAuthentication: bedrockConfig.awsAuthentication,
awsRegion: bedrockConfig.awsRegion,
awsUseCrossRegionInference: bedrockConfig.awsUseCrossRegionInference,
}
if (bedrockConfig.awsProfile !== undefined) bedrockFields.awsProfile = bedrockConfig.awsProfile
if (bedrockConfig.awsAccessKey) bedrockFields.awsAccessKey = bedrockConfig.awsAccessKey
if (bedrockConfig.awsSecretKey) bedrockFields.awsSecretKey = bedrockConfig.awsSecretKey
if (bedrockConfig.awsSessionToken) bedrockFields.awsSessionToken = bedrockConfig.awsSessionToken
Object.assign(config, bedrockFields)
} else if (apiKey) {
const keyField = ProviderToApiKeyMap[selectedProvider as keyof typeof ProviderToApiKeyMap]
if (keyField) {
const fields = Array.isArray(keyField) ? keyField : [keyField]
config[fields[0]] = apiKey
}
await applyBedrockConfig({
bedrockConfig,
modelId: model,
controller,
})
} else {
await applyProviderConfig({
providerId: selectedProvider,
apiKey,
modelId: model,
baseUrl: base,
controller,
})
}
if (base) {
config.openAiBaseUrl = base
}
stateManager.setApiConfiguration(config)
const stateManager = StateManager.get()
stateManager.setGlobalState("welcomeViewCompleted", true)
await stateManager.flushPendingState()
@@ -566,6 +534,9 @@ export const AuthView: React.FC<AuthViewProps> = ({ controller, onComplete, onEr
setBaseUrl("")
setStep("modelid")
break
case "oca_auth":
setStep("provider")
break
case "cline_auth":
setStep("menu")
break
@@ -704,6 +675,7 @@ export const AuthView: React.FC<AuthViewProps> = ({ controller, onComplete, onEr
</Box>
)
case "oca_auth":
case "cline_auth":
return (
<Box flexDirection="column">
@@ -793,6 +765,7 @@ export const AuthView: React.FC<AuthViewProps> = ({ controller, onComplete, onEr
"modelid",
"baseurl",
"cline_auth",
"oca_auth",
"cline_model",
"openai_codex_auth",
"bedrock",
@@ -885,7 +858,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>
))}
+32 -21
View File
@@ -10,6 +10,7 @@ import { CLINE_ACCOUNT_AUTH_ERROR_MESSAGE } from "@shared/ClineAccount"
import { COMMAND_OUTPUT_STRING } from "@shared/combineCommandSequences"
import type { ClineAskUseMcpServer, ClineMessage } from "@shared/ExtensionMessage"
import { Box, Text } from "ink"
import Spinner from "ink-spinner"
import React from "react"
import { COLORS } from "../constants/colors"
import { useTerminalSize } from "../hooks/useTerminalSize"
@@ -126,10 +127,20 @@ interface ChatMessageProps {
* For this to work properly, parent containers must have width="100%"
* so flexGrow={1} on the content box has a reference width to fill.
*/
const DotRow: React.FC<{ children: React.ReactNode; color?: string }> = ({ children, color }) => (
const DotRow: React.FC<{ children: React.ReactNode; color?: string; flashing?: boolean }> = ({
children,
color,
flashing = false,
}) => (
<Box flexDirection="row">
<Box width={2}>
<Text color={color}></Text>
{flashing ? (
<Text color={color}>
<Spinner type="toggle8" />
</Text>
) : (
<Text color={color}></Text>
)}
</Box>
<Box flexGrow={1}>{children}</Box>
</Box>
@@ -223,8 +234,8 @@ function formatToolResult(result: string, maxLines: number = 5): string[] {
return displayLines
}
export const ChatMessage: React.FC<ChatMessageProps> = ({ message, mode }) => {
const { type, ask, say, text } = message
export const ChatMessage: React.FC<ChatMessageProps> = ({ message, mode, isStreaming }) => {
const { type, ask, say, text, partial } = message
const toolColor = mode === "plan" ? "yellow" : COLORS.primaryBlue
const { columns: terminalWidth } = useTerminalSize()
@@ -280,11 +291,11 @@ export const ChatMessage: React.FC<ChatMessageProps> = ({ message, mode }) => {
if (isFileEditTool(toolInfo.toolName) && filePath && toolInfo.args.content) {
return (
<Box flexDirection="column" marginBottom={1} width="100%">
<DotRow color={toolColor}>
<DotRow color={toolColor} flashing={partial === true && isStreaming}>
<ToolCallText args={toolInfo.args} isAsk={isToolAsk} mode={mode} toolName={toolInfo.toolName} />
</DotRow>
<Box marginLeft={2}>
<DiffView content={toolInfo.args.content} filePath={filePath as string | undefined} />
<DiffView content={toolInfo.args.content as string} filePath={filePath as string | undefined} />
</Box>
</Box>
)
@@ -299,7 +310,7 @@ export const ChatMessage: React.FC<ChatMessageProps> = ({ message, mode }) => {
return (
<Box flexDirection="column" marginBottom={1} width="100%">
<DotRow color={toolColor}>
<DotRow color={toolColor} flashing={partial === true && isStreaming}>
<ToolCallText args={toolInfo.args} isAsk={isToolAsk} mode={mode} toolName={toolInfo.toolName} />
</DotRow>
{contentLines.length > 0 && (
@@ -318,7 +329,7 @@ export const ChatMessage: React.FC<ChatMessageProps> = ({ message, mode }) => {
if (isToolSay) {
return (
<Box flexDirection="column" marginBottom={1} width="100%">
<DotRow color={toolColor}>
<DotRow color={toolColor} flashing={partial === true && isStreaming}>
<Text color={toolColor}>{truncate(text, 100)}</Text>
</DotRow>
</Box>
@@ -340,7 +351,7 @@ export const ChatMessage: React.FC<ChatMessageProps> = ({ message, mode }) => {
return (
<Box flexDirection="column" marginBottom={1} width="100%">
<DotRow color={toolColor}>
<DotRow color={toolColor} flashing={partial === true && isStreaming}>
<Text>
<Text color={toolColor}>{label}</Text>
<Text>{truncate(command, 120)}</Text>
@@ -379,12 +390,12 @@ export const ChatMessage: React.FC<ChatMessageProps> = ({ message, mode }) => {
if ((type === "ask" && ask === "use_mcp_server") || say === "use_mcp_server") {
const isAsk = type === "ask"
const parsed = text
? jsonParseSafe<ClineAskUseMcpServer>(text, {
type: undefined as ClineAskUseMcpServer["type"] | undefined,
? jsonParseSafe<Partial<ClineAskUseMcpServer> & { serverName: string }>(text, {
type: undefined,
serverName: "unknown server",
toolName: undefined as string | undefined,
arguments: undefined as string | undefined,
uri: undefined as string | undefined,
toolName: undefined,
arguments: undefined,
uri: undefined,
})
: undefined
@@ -410,7 +421,7 @@ export const ChatMessage: React.FC<ChatMessageProps> = ({ message, mode }) => {
return (
<Box flexDirection="column" marginBottom={1} width="100%">
<DotRow color={toolColor}>
<DotRow color={toolColor} flashing={partial === true && isStreaming}>
<Text>
<Text color={toolColor}>{actionLabel}</Text>
<Text>{`: ${serverName}`}</Text>
@@ -440,7 +451,7 @@ export const ChatMessage: React.FC<ChatMessageProps> = ({ message, mode }) => {
const lines = formatToolResult(text, 8)
return (
<Box flexDirection="column" marginBottom={1} width="100%">
<DotRow color={toolColor}>
<DotRow color={toolColor} flashing={partial === true && isStreaming}>
<Text color={toolColor}>MCP response</Text>
</DotRow>
<Box flexDirection="column" marginLeft={2} width="100%">
@@ -581,7 +592,7 @@ export const ChatMessage: React.FC<ChatMessageProps> = ({ message, mode }) => {
if (say === "browser_action" || say === "browser_action_launch") {
return (
<Box flexDirection="column" marginBottom={1} width="100%">
<DotRow color={toolColor}>
<DotRow color={toolColor} flashing={partial === true && isStreaming}>
<Text>
<Text color={toolColor}>Cline used the browser</Text>
{text && (
@@ -600,7 +611,7 @@ export const ChatMessage: React.FC<ChatMessageProps> = ({ message, mode }) => {
if (say === "mcp_server_request_started") {
return (
<Box flexDirection="column" marginBottom={1} width="100%">
<DotRow color={toolColor}>
<DotRow color={toolColor} flashing={partial === true && isStreaming}>
<Text>
<Text color={toolColor}>Cline is using an MCP tool</Text>
{text && (
@@ -728,7 +739,7 @@ export const ChatMessage: React.FC<ChatMessageProps> = ({ message, mode }) => {
if (type === "ask" && ask === "condense" && text) {
return (
<Box flexDirection="column" marginBottom={1} width="100%">
<DotRow color={COLORS.primaryBlue}>
<DotRow color={COLORS.primaryBlue} flashing={partial === true && isStreaming}>
<Text bold color={COLORS.primaryBlue}>
Cline wants to condense your conversation:
</Text>
@@ -744,7 +755,7 @@ export const ChatMessage: React.FC<ChatMessageProps> = ({ message, mode }) => {
if (type === "ask" && ask === "summarize_task" && text) {
return (
<Box flexDirection="column" marginBottom={1} width="100%">
<DotRow color={COLORS.primaryBlue}>
<DotRow color={COLORS.primaryBlue} flashing={partial === true && isStreaming}>
<Text bold color={COLORS.primaryBlue}>
Cline wants to summarize the task:
</Text>
@@ -760,7 +771,7 @@ export const ChatMessage: React.FC<ChatMessageProps> = ({ message, mode }) => {
if (type === "ask" && ask === "report_bug" && text) {
return (
<Box flexDirection="column" marginBottom={1} width="100%">
<DotRow color={COLORS.primaryBlue}>
<DotRow color={COLORS.primaryBlue} flashing={partial === true && isStreaming}>
<Text bold color={COLORS.primaryBlue}>
Cline wants to create a Github issue:
</Text>
+43 -41
View File
@@ -1,7 +1,7 @@
/**
* Tests for ChatView component exit and cleanup behavior
*
* These tests verify that when the user exits (via Ctrl+C or other means),
* These tests verify that when the user exits (via shutdown event or other means),
* the input field is properly hidden before the app terminates.
*/
@@ -12,7 +12,6 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"
import { ChatView } from "./ChatView"
// Helper to wait for async state updates
// Using 60ms since handleExit has a 50ms setTimeout
const delay = (ms: number = 60) => new Promise((resolve) => setTimeout(resolve, ms))
// Type for our exit mock function
@@ -175,12 +174,21 @@ vi.mock("@shared/getApiMetrics", () => ({
totalTokensOut: 0,
totalCost: 0,
})),
getLastApiReqTotalTokens: vi.fn(() => 0),
}))
vi.mock("child_process", () => ({
exec: vi.fn(),
execSync: vi.fn(() => "main"),
}))
// Mock telemetry service to prevent HostProvider errors in shutdown handler
vi.mock("@/services/telemetry", () => ({
telemetryService: {
captureHostEvent: vi.fn(),
},
}))
// Helper to create a typed mock for onExit
const createExitMock = (): ExitMockFn => vi.fn() as ExitMockFn
@@ -213,33 +221,6 @@ describe("ChatView Exit and Cleanup", () => {
})
})
describe("Ctrl+C exit handling", () => {
it("should hide input but keep footer, then call onExit", async () => {
const { lastFrame, stdin } = render(<ChatView onExit={mockOnExit} />)
// Verify UI visible before Ctrl+C
expect(lastFrame()).toContain("Input:")
expect(lastFrame()).toContain("@ for files")
// Simulate Ctrl+C
stdin.write("\x03")
// onExit should not be called immediately
expect(mockOnExit).not.toHaveBeenCalled()
// Wait for state update and callback
await delay()
// Input should be hidden, but footer should remain
const frameAfter = lastFrame()
expect(frameAfter).not.toContain("Input:")
expect(frameAfter).toContain("@ for files")
// onExit should have been called
expect(mockOnExit).toHaveBeenCalledTimes(1)
})
})
describe("Shutdown event handling", () => {
it("should subscribe on mount and unsubscribe on unmount", () => {
const { unmount } = render(<ChatView onExit={mockOnExit} />)
@@ -249,39 +230,59 @@ describe("ChatView Exit and Cleanup", () => {
expect(shutdownMockState.listeners.length).toBe(0)
})
it("should hide UI when shutdown event fires", async () => {
it("should hide input when shutdown event fires", async () => {
const { lastFrame } = render(<ChatView onExit={mockOnExit} />)
// Input should be visible initially
expect(lastFrame()).toContain("Input:")
// Fire shutdown event (simulates Ctrl+C)
shutdownMockState.fire()
await delay()
// Input should be hidden after shutdown
expect(lastFrame()).not.toContain("Input:")
})
it("should preserve footer when shutdown event fires", async () => {
const { lastFrame } = render(<ChatView onExit={mockOnExit} />)
// Footer should be visible initially
expect(lastFrame()).toContain("@ for files")
// Fire shutdown event
shutdownMockState.fire()
await delay()
// Footer should still be present (only input is hidden)
expect(lastFrame()).toContain("@ for files")
})
})
describe("Edge cases", () => {
it("should handle exit when onExit prop is undefined", async () => {
const { lastFrame, stdin } = render(<ChatView />)
it("should handle shutdown event when onExit prop is undefined", async () => {
const { lastFrame } = render(<ChatView />)
stdin.write("\x03")
// Fire shutdown event
shutdownMockState.fire()
await delay()
// Should not throw, UI should still hide
expect(lastFrame()).not.toContain("Input:")
})
it("should handle multiple Ctrl+C presses gracefully", async () => {
const { stdin } = render(<ChatView onExit={mockOnExit} />)
it("should handle multiple shutdown events gracefully", async () => {
const { lastFrame } = render(<ChatView onExit={mockOnExit} />)
stdin.write("\x03")
stdin.write("\x03")
stdin.write("\x03")
// Fire multiple shutdown events
shutdownMockState.fire()
shutdownMockState.fire()
shutdownMockState.fire()
await delay()
expect(mockOnExit).toHaveBeenCalled()
// UI should still hide properly
expect(lastFrame()).not.toContain("Input:")
})
})
})
@@ -294,14 +295,15 @@ describe("ChatView UI State During Exit", () => {
it("should preserve static content and footer, only hide input during exit", async () => {
const onExit = createExitMock()
const { lastFrame, stdin } = render(<ChatView onExit={onExit} />)
const { lastFrame } = render(<ChatView onExit={onExit} />)
// Footer contains auto-approve toggle
expect(lastFrame()).toContain("Auto-approve")
expect(lastFrame()).toContain("What can I do for you?")
expect(lastFrame()).toContain("Input:")
stdin.write("\x03")
// Fire shutdown event
shutdownMockState.fire()
await delay()
const frameAfter = lastFrame()
+68 -29
View File
@@ -103,15 +103,17 @@
import type { ApiProvider, ModelInfo } from "@shared/api"
import { combineCommandSequences } from "@shared/combineCommandSequences"
import { combineHookSequences } from "@shared/combineHookSequences"
import type { ClineAsk, ClineMessage } from "@shared/ExtensionMessage"
import { getApiMetrics, getLastApiReqTotalTokens } from "@shared/getApiMetrics"
import { EmptyRequest, StringRequest } from "@shared/proto/cline/common"
import type { SlashCommandInfo } from "@shared/proto/cline/slash"
import { CLI_ONLY_COMMANDS } from "@shared/slashCommands"
import { getProviderModelIdKey } from "@shared/storage"
import { getProviderDefaultModelId, getProviderModelIdKey } from "@shared/storage"
import type { Mode } from "@shared/storage/types"
import { execSync } from "child_process"
import { Box, Static, Text, useApp, useInput } from "ink"
// biome-ignore lint/style/useImportType: JSX requires React as a value (jsx: "react" in tsconfig)
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"
import { getAvailableSlashCommands } from "@/core/controller/slash/getAvailableSlashCommands"
import { showTaskWithId } from "@/core/controller/task/showTaskWithId"
@@ -136,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"
@@ -208,9 +211,9 @@ function getGitDiffStats(cwd?: string): GitDiffStats | null {
const delMatch = output.match(/(\d+) deletion/)
return {
files: filesMatch ? parseInt(filesMatch[1], 10) : 0,
additions: addMatch ? parseInt(addMatch[1], 10) : 0,
deletions: delMatch ? parseInt(delMatch[1], 10) : 0,
files: filesMatch ? Number.parseInt(filesMatch[1], 10) : 0,
additions: addMatch ? Number.parseInt(addMatch[1], 10) : 0,
deletions: delMatch ? Number.parseInt(delMatch[1], 10) : 0,
}
} catch {
return null
@@ -221,7 +224,7 @@ function getGitDiffStats(cwd?: string): GitDiffStats | null {
* Create a progress bar for context window usage
* Returns { filled, empty } strings to allow different coloring
*/
function createContextBar(used: number, total: number, width: number = 8): { filled: string; empty: string } {
function createContextBar(used: number, total: number, width = 8): { filled: string; empty: string } {
const ratio = Math.min(used / total, 1)
// Use ceil so any usage > 0 shows at least one bar
const filledCount = used > 0 ? Math.max(1, Math.ceil(ratio * width)) : 0
@@ -311,7 +314,7 @@ function parseAskOptions(text: string): string[] {
*/
function expandPastedTexts(text: string, pastedTexts: Map<number, string>): string {
return text.replace(/\[Pasted text #(\d+) \+\d+ lines\]/g, (match, num) => {
const content = pastedTexts.get(parseInt(num, 10))
const content = pastedTexts.get(Number.parseInt(num, 10))
return content ?? match
})
}
@@ -348,9 +351,11 @@ export const ChatView: React.FC<ChatViewProps> = ({
insertText: insertTextAtCursor,
} = useTextInput()
// Ref for text input (used by useHomeEndKeys)
// Refs for text input and cursor position (used by useHomeEndKeys and to avoid stale closures in useInput)
const textInputRef = useRef(textInput)
textInputRef.current = textInput
const cursorPosRef = useRef(cursorPos)
cursorPosRef.current = cursorPos
const [fileResults, setFileResults] = useState<FileSearchResult[]>([])
const [selectedIndex, setSelectedIndex] = useState(0) // For file menu
@@ -423,7 +428,7 @@ export const ChatView: React.FC<ChatViewProps> = ({
return stateManager.getGlobalSettingsKey("mode") || "act"
})
const [yolo, setYolo] = useState<boolean>(() => StateManager.get().getGlobalSettingsKey("yoloModeToggled") ?? false)
const [yolo, _setYolo] = useState<boolean>(() => StateManager.get().getGlobalSettingsKey("yoloModeToggled") ?? false)
const [autoApproveAll, setAutoApproveAll] = useState<boolean>(
() => StateManager.get().getGlobalSettingsKey("autoApproveAllToggled") ?? false,
)
@@ -451,11 +456,12 @@ export const ChatView: React.FC<ChatViewProps> = ({
// Get model ID based on current mode and provider
// Different providers use different state keys (e.g., cline uses actModeOpenRouterModelId)
// Re-read when activePanel changes (settings panel closes) to pick up changes
// Falls back to provider's default model if no model has been explicitly set
const modelId = useMemo(() => {
if (!provider) return ""
const stateManager = StateManager.get()
const modelKey = getProviderModelIdKey(provider as ApiProvider, mode)
return (stateManager.getGlobalSettingsKey(modelKey as string) as string) || ""
return (stateManager.getGlobalSettingsKey(modelKey) as string) || getProviderDefaultModelId(provider as ApiProvider) || ""
}, [mode, provider, activePanel])
const toggleMode = useCallback(async () => {
@@ -604,8 +610,10 @@ export const ChatView: React.FC<ChatViewProps> = ({
return true
})
// Combine command messages with their output (like webview does)
return combineCommandSequences(filtered)
// Combine hook messages with their output, then command messages (like webview does)
// CLI always has hooks enabled, so we always apply combineHookSequences
const withHooks = combineHookSequences(filtered)
return combineCommandSequences(withHooks)
}, [messages])
// Detect task switches by watching first message timestamp change.
@@ -881,6 +889,8 @@ export const ChatView: React.FC<ChatViewProps> = ({
)
// 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)) {
@@ -901,8 +911,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?.()
}
@@ -998,11 +1032,11 @@ export const ChatView: React.FC<ChatViewProps> = ({
// 3. Handle Option+arrow via key.meta (backup - Ink sometimes parses these instead of passing raw sequence)
if (key.meta) {
if (key.leftArrow) {
setCursorPos(findWordStart(textInput, cursorPos))
setCursorPos(findWordStart(textInputRef.current, cursorPosRef.current))
return
}
if (key.rightArrow) {
setCursorPos(findWordEnd(textInput, cursorPos))
setCursorPos(findWordEnd(textInputRef.current, cursorPosRef.current))
return
}
}
@@ -1188,7 +1222,8 @@ export const ChatView: React.FC<ChatViewProps> = ({
if (hasPrimary && buttonConfig.primaryAction) {
handleButtonAction(buttonConfig.primaryAction, true)
return
} else if (hasSecondary && !hasPrimary && buttonConfig.secondaryAction) {
}
if (hasSecondary && !hasPrimary && buttonConfig.secondaryAction) {
handleButtonAction(buttonConfig.secondaryAction, false)
return
}
@@ -1209,7 +1244,7 @@ export const ChatView: React.FC<ChatViewProps> = ({
}
// Number selection for options (only when no text typed yet)
if (askType === "options") {
const num = parseInt(input, 10)
const num = Number.parseInt(input, 10)
if (textInput === "" && !Number.isNaN(num) && num >= 1 && num <= askOptions.length) {
const selectedOption = askOptions[num - 1]
sendAskResponse("messageResponse", selectedOption)
@@ -1251,10 +1286,10 @@ export const ChatView: React.FC<ChatViewProps> = ({
}
pasteUpdateTimeoutRef.current = setTimeout(() => {
const newPlaceholder = `[Pasted text #${pasteNum} +${activePasteLinesRef.current} lines]`
setTextInput((prev) => {
const pattern = new RegExp(`\\[Pasted text #${pasteNum} \\+\\d+ lines\\]`)
return prev.replace(pattern, newPlaceholder)
})
const pattern = new RegExp(`\\[Pasted text #${pasteNum} \\+\\d+ lines\\]`)
const newText = textInputRef.current.replace(pattern, newPlaceholder)
textInputRef.current = newText // Update ref immediately so setCursorPos bounds check works
setTextInput(newText)
// Update cursor to be right after the placeholder
setCursorPos(activePasteStartPosRef.current + newPlaceholder.length)
Logger.info(`Paste #${pasteNum} complete: ${activePasteLinesRef.current} lines`)
@@ -1267,7 +1302,8 @@ export const ChatView: React.FC<ChatViewProps> = ({
pasteCounterRef.current += 1
const pasteNum = pasteCounterRef.current
activePasteNumRef.current = pasteNum
activePasteStartPosRef.current = cursorPos // Track where placeholder starts
const currentCursorPos = cursorPosRef.current // Use ref to avoid stale closure
activePasteStartPosRef.current = currentCursorPos // Track where placeholder starts
// Count line breaks in the pasted content (handle both \n and \r)
const extraLines = input.match(/[\r\n]/g)?.length || 0
activePasteLinesRef.current = extraLines // Track total lines
@@ -1279,8 +1315,11 @@ export const ChatView: React.FC<ChatViewProps> = ({
return next
})
setTextInput((prev) => prev.slice(0, cursorPos) + placeholder + prev.slice(cursorPos))
setCursorPos(cursorPos + placeholder.length)
const newText =
textInputRef.current.slice(0, currentCursorPos) + placeholder + textInputRef.current.slice(currentCursorPos)
textInputRef.current = newText // Update ref immediately so setCursorPos bounds check works
setTextInput(newText)
setCursorPos(currentCursorPos + placeholder.length)
return // Exit early - don't also add the raw input via normal handling below
}
@@ -1309,15 +1348,15 @@ export const ChatView: React.FC<ChatViewProps> = ({
return
}
if (key.rightArrow && !inSlashMenu && !inFileMenu) {
setCursorPos((pos) => Math.min(textInput.length, pos + 1))
setCursorPos((pos) => Math.min(textInputRef.current.length, pos + 1))
return
}
if (key.upArrow && !inSlashMenu && !inFileMenu) {
setCursorPos(moveCursorUp(textInput, cursorPos))
setCursorPos(moveCursorUp(textInputRef.current, cursorPosRef.current))
return
}
if (key.downArrow && !inSlashMenu && !inFileMenu) {
setCursorPos(moveCursorDown(textInput, cursorPos))
setCursorPos(moveCursorDown(textInputRef.current, cursorPosRef.current))
return
}
// Normal input (single char or short paste)
@@ -1383,10 +1422,10 @@ export const ChatView: React.FC<ChatViewProps> = ({
{/* Dynamic region - only current streaming message + input */}
<Box flexDirection="column" width="100%">
{/* Animated robot and welcome text - only shown before messages start and user hasn't scrolled */}
{/* Animated robot and welcome text - only shown before messages start and user hasn't interacted */}
{isWelcomeState && (
<Box flexDirection="column" marginBottom={1}>
<AsciiMotionCli onScroll={() => setUserScrolled(true)} />
<AsciiMotionCli onInteraction={() => setUserScrolled(true)} />
<Text> </Text>
<Text bold color="white">
{centerText("What can I do for you?")}
+8 -8
View File
@@ -27,12 +27,12 @@ export const FeaturedModelPicker: React.FC<FeaturedModelPickerProps> = ({
return (
<Box flexDirection="column">
{title && (
<>
<Text>
<Text bold color={COLORS.primaryBlue}>
{title}
</Text>
<Text> </Text>
</>
</Text>
)}
{featuredModels.map((model, i) => {
@@ -45,15 +45,15 @@ export const FeaturedModelPicker: React.FC<FeaturedModelPickerProps> = ({
<Text bold color={isSelected ? COLORS.primaryBlue : "white"}>
{model.name}
</Text>
{model.label && (
<>
{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>
+6 -20
View File
@@ -6,8 +6,6 @@
import { Box, Text, useInput } from "ink"
import React, { useCallback, useEffect, useState } from "react"
import { StateManager } from "@/core/storage/StateManager"
import type { ApiProvider } from "@/shared/api"
import { getProviderModelIdKey } from "@/shared/storage"
import { COLORS } from "../constants/colors"
import { useStdinContext } from "../context/StdinContext"
import {
@@ -18,6 +16,7 @@ import {
importFromCodex,
importFromOpenCode,
} from "../utils/import-configs"
import { applyProviderConfig } from "../utils/provider-config"
type ImportStep = "select" | "confirm" | "saving" | "error"
@@ -61,25 +60,12 @@ export const ImportView: React.FC<ImportViewProps> = ({ source, onComplete, onCa
return
}
await applyProviderConfig({
providerId: selectedKey.provider,
apiKey: selectedKey.key,
modelId: selectedKey.modelId,
})
const stateManager = StateManager.get()
const config: Record<string, string> = {
actModeApiProvider: selectedKey.provider,
planModeApiProvider: selectedKey.provider,
apiProvider: selectedKey.provider,
}
// Set API key
config[selectedKey.keyField] = selectedKey.key
// Set model ID if available (use provider-specific keys)
if (selectedKey.modelId) {
const actModelKey = getProviderModelIdKey(selectedKey.provider as ApiProvider, "act")
const planModelKey = getProviderModelIdKey(selectedKey.provider as ApiProvider, "plan")
if (actModelKey) config[actModelKey] = selectedKey.modelId
if (planModelKey) config[planModelKey] = selectedKey.modelId
}
stateManager.setApiConfiguration(config)
stateManager.setGlobalState("welcomeViewCompleted", true)
await stateManager.flushPendingState()
+5 -3
View File
@@ -20,11 +20,13 @@ interface PanelProps {
tabs?: PanelTab[]
/** Current tab key - required when tabs are provided */
currentTab?: string
/** Whether currently in a subpage (shows "Esc to go back" and hides arrow key hint) */
isSubpage?: boolean
/** Panel content */
children: ReactNode
}
export const Panel: React.FC<PanelProps> = ({ label, tabs, currentTab, children }) => {
export const Panel: React.FC<PanelProps> = ({ label, tabs, currentTab, isSubpage, children }) => {
const { columns } = useTerminalSize()
const currentTabIndex = currentTab && tabs ? tabs.findIndex((t) => t.key === currentTab) : 0
@@ -35,7 +37,7 @@ export const Panel: React.FC<PanelProps> = ({ label, tabs, currentTab, children
<Text bold color={COLORS.primaryBlue}>
{label}
</Text>
<Text color="gray"> (Esc to close)</Text>
<Text color="gray"> (Esc to {isSubpage ? "go back" : "close"})</Text>
</Box>
{/* Tab bar if tabs are provided */}
@@ -53,7 +55,7 @@ export const Panel: React.FC<PanelProps> = ({ label, tabs, currentTab, children
</Text>
)
})}
<Text color="gray"> (/)</Text>
{!isSubpage && <Text color="gray"> (/)</Text>}
</Box>
)}
+7 -8
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)
@@ -18,8 +18,8 @@ export { CLI_EXCLUDED_PROVIDERS, getProviderLabel, getProviderOrder }
function isProviderConfigured(providerId: string, config: ApiConfiguration): boolean {
switch (providerId) {
case "cline":
// Check if user has Cline account auth data stored
return !!(config as Record<string, unknown>)["cline:clineAccountId"]
// Check if user has Cline API key or Cline account auth data stored
return !!(config.clineApiKey ?? config["cline:clineAccountId"])
case "anthropic":
return !!config.apiKey
case "openrouter":
@@ -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)} />
}
+113 -70
View File
@@ -5,8 +5,8 @@
import type { AutoApprovalSettings } from "@shared/AutoApprovalSettings"
import { DEFAULT_AUTO_APPROVAL_SETTINGS } from "@shared/AutoApprovalSettings"
import type { ApiProvider } from "@shared/api"
import { getProviderModelIdKey, ProviderToApiKeyMap } from "@shared/storage"
import type { ApiProvider, ModelInfo } from "@shared/api"
import { getProviderModelIdKey, isSettingsKey, ProviderToApiKeyMap } from "@shared/storage"
import type { TelemetrySetting } from "@shared/TelemetrySetting"
import { Box, Text, useInput } from "ink"
import Spinner from "ink-spinner"
@@ -21,8 +21,9 @@ import { openExternal } from "@/utils/env"
import { version as CLI_VERSION } from "../../package.json"
import { COLORS } from "../constants/colors"
import { useStdinContext } from "../context/StdinContext"
import { useOcaAuth } from "../hooks/useOcaAuth"
import { isMouseEscapeSequence } from "../utils/input"
import { applyProviderConfig } from "../utils/provider-config"
import { applyBedrockConfig, applyProviderConfig } from "../utils/provider-config"
import { ApiKeyInput } from "./ApiKeyInput"
import { type BedrockConfig, BedrockSetup } from "./BedrockSetup"
import { Checkbox } from "./Checkbox"
@@ -33,7 +34,7 @@ import {
isBrowseAllSelected,
} from "./FeaturedModelPicker"
import { LanguagePicker } from "./LanguagePicker"
import { getDefaultModelId, hasModelPicker, ModelPicker } from "./ModelPicker"
import { hasModelPicker, ModelPicker } from "./ModelPicker"
import { OrganizationPicker } from "./OrganizationPicker"
import { Panel, PanelTab } from "./Panel"
import { getProviderLabel, ProviderPicker } from "./ProviderPicker"
@@ -68,41 +69,35 @@ const TABS: PanelTab[] = [
// Settings configuration for simple boolean toggles
const FEATURE_SETTINGS = {
autoCondense: {
stateKey: "useAutoCondense" as const,
stateKey: "useAutoCondense",
default: false,
label: "Auto-condense",
description: "Automatically summarize long conversations",
},
webTools: {
stateKey: "clineWebToolsEnabled" as const,
stateKey: "clineWebToolsEnabled",
default: true,
label: "Web tools",
description: "Enable web search and fetch tools",
},
strictPlanMode: {
stateKey: "strictPlanModeEnabled" as const,
stateKey: "strictPlanModeEnabled",
default: true,
label: "Strict plan mode",
description: "Require explicit mode switching",
},
nativeToolCall: {
stateKey: "nativeToolCallEnabled" as const,
stateKey: "nativeToolCallEnabled",
default: true,
label: "Native tool call",
description: "Use model's native tool calling API",
},
parallelToolCalling: {
stateKey: "enableParallelToolCalling" as const,
stateKey: "enableParallelToolCalling",
default: false,
label: "Parallel tool calling",
description: "Allow multiple tools in a single response",
},
skillsEnabled: {
stateKey: "skillsEnabled" as const,
default: false,
label: "Skills",
description: "Enable reusable agent instructions",
},
} as const
type FeatureKey = keyof typeof FEATURE_SETTINGS
@@ -150,7 +145,11 @@ export const SettingsPanelContent: React.FC<SettingsPanelContentProps> = ({
const [features, setFeatures] = useState<Record<FeatureKey, boolean>>(() => {
const initial: Record<string, boolean> = {}
for (const [key, config] of Object.entries(FEATURE_SETTINGS)) {
initial[key] = stateManager.getGlobalSettingsKey(config.stateKey) ?? config.default
if (isSettingsKey(config.stateKey)) {
initial[key] = stateManager.getGlobalSettingsKey(config.stateKey)
} else {
initial[key] = stateManager.getGlobalStateKey(config.stateKey)
}
}
return initial as Record<FeatureKey, boolean>
})
@@ -201,6 +200,23 @@ export const SettingsPanelContent: React.FC<SettingsPanelContentProps> = ({
const [modelRefreshKey, setModelRefreshKey] = useState(0)
const refreshModelIds = useCallback(() => setModelRefreshKey((k) => k + 1), [])
// OCA auth hook
const handleOcaAuthSuccess = useCallback(async () => {
await applyProviderConfig({ providerId: "oca", controller })
setProvider("oca")
refreshModelIds()
}, [controller, refreshModelIds])
const {
isWaiting: isWaitingForOcaAuth,
startAuth: startOcaAuth,
cancelAuth: cancelOcaAuth,
isAuthenticated: isOcaAuthenticated,
} = useOcaAuth({
controller,
onSuccess: handleOcaAuthSuccess,
})
// Read model IDs from state (re-reads when refreshKey changes)
const { actModelId, planModelId } = useMemo(() => {
const apiConfig = stateManager.getApiConfiguration()
@@ -209,11 +225,11 @@ export const SettingsPanelContent: React.FC<SettingsPanelContentProps> = ({
if (!actProvider && !planProvider) {
return { actModelId: "", planModelId: "" }
}
const actKey = actProvider ? getProviderModelIdKey(actProvider as ApiProvider, "act") : null
const planKey = planProvider ? getProviderModelIdKey(planProvider as ApiProvider, "plan") : null
const actKey = actProvider ? getProviderModelIdKey(actProvider, "act") : null
const planKey = planProvider ? getProviderModelIdKey(planProvider, "plan") : null
return {
actModelId: actKey ? (stateManager.getGlobalSettingsKey(actKey as string) as string) || "" : "",
planModelId: planKey ? (stateManager.getGlobalSettingsKey(planKey as string) as string) || "" : "",
actModelId: actKey ? (stateManager.getGlobalSettingsKey(actKey) as string) || "" : "",
planModelId: planKey ? (stateManager.getGlobalSettingsKey(planKey) as string) || "" : "",
}
}, [modelRefreshKey, stateManager])
@@ -477,7 +493,7 @@ export const SettingsPanelContent: React.FC<SettingsPanelContentProps> = ({
key: parentKey,
label: parentLabel,
type: "checkbox",
value: actions[parentKey as keyof typeof actions],
value: actions[parentKey as keyof typeof actions] ?? false,
description: parentDesc,
})
if (actions[parentKey as keyof typeof actions]) {
@@ -721,9 +737,9 @@ export const SettingsPanelContent: React.FC<SettingsPanelContentProps> = ({
const actProvider = apiConfig.actModeApiProvider
const planProvider = apiConfig.planModeApiProvider || actProvider
if (actProvider) {
const actKey = getProviderModelIdKey(actProvider as ApiProvider, "act")
const planKey = planProvider ? getProviderModelIdKey(planProvider as ApiProvider, "plan") : null
const actModel = stateManager.getGlobalSettingsKey(actKey as string)
const actKey = getProviderModelIdKey(actProvider, "act")
const planKey = planProvider ? getProviderModelIdKey(planProvider, "plan") : null
const actModel = stateManager.getGlobalSettingsKey(actKey)
if (planKey) stateManager.setGlobalState(planKey, actModel)
}
}
@@ -823,11 +839,11 @@ export const SettingsPanelContent: React.FC<SettingsPanelContentProps> = ({
: actProvider || planProvider
if (!providerForSelection) return
// Use provider-specific model ID keys (e.g., cline uses actModeOpenRouterModelId)
const actKey = actProvider ? getProviderModelIdKey(actProvider as ApiProvider, "act") : null
const planKey = planProvider ? getProviderModelIdKey(planProvider as ApiProvider, "plan") : null
const actKey = actProvider ? getProviderModelIdKey(actProvider, "act") : null
const planKey = planProvider ? getProviderModelIdKey(planProvider, "plan") : null
// For cline/openrouter providers, also set model info (like webview does)
let modelInfo
let modelInfo: ModelInfo | undefined
if (providerForSelection === "cline" || providerForSelection === "openrouter") {
const openRouterModels = await controller?.readOpenRouterModels()
modelInfo = openRouterModels?.[modelId]
@@ -914,7 +930,7 @@ export const SettingsPanelContent: React.FC<SettingsPanelContentProps> = ({
}, [controller])
const handleProviderSelect = useCallback(
(providerId: string) => {
async (providerId: string) => {
// Special handling for Cline - uses OAuth (but skip if already logged in)
if (providerId === "cline") {
setIsPickingProvider(false)
@@ -922,7 +938,7 @@ export const SettingsPanelContent: React.FC<SettingsPanelContentProps> = ({
const authInfo = AuthService.getInstance(controller).getInfo()
if (authInfo?.user?.email) {
// Already logged in - just set the provider
applyProviderConfig({ providerId: "cline", controller })
await applyProviderConfig({ providerId: "cline", controller })
setProvider("cline")
refreshModelIds()
} else {
@@ -939,6 +955,22 @@ export const SettingsPanelContent: React.FC<SettingsPanelContentProps> = ({
return
}
// Special handling for OCA - uses OAuth (but skip if already logged in)
if (providerId === "oca") {
setIsPickingProvider(false)
// Check if already logged in
if (isOcaAuthenticated) {
// Already logged in - just set the provider
await applyProviderConfig({ providerId: "oca", controller })
setProvider("oca")
refreshModelIds()
} else {
// Not logged in - trigger OAuth
startOcaAuth()
}
return
}
// Special handling for Bedrock - needs multi-field configuration
if (providerId === "bedrock") {
setPendingProvider(providerId)
@@ -948,7 +980,7 @@ export const SettingsPanelContent: React.FC<SettingsPanelContentProps> = ({
}
// Check if this provider needs an API key
const keyField = ProviderToApiKeyMap[providerId as keyof typeof ProviderToApiKeyMap]
const keyField = ProviderToApiKeyMap[providerId as ApiProvider]
if (keyField) {
// Provider needs an API key - go to API key entry mode
// Pre-fill with existing key if configured
@@ -961,13 +993,13 @@ export const SettingsPanelContent: React.FC<SettingsPanelContentProps> = ({
setIsEnteringApiKey(true)
} else {
// Provider doesn't need an API key (rare) - just set it
applyProviderConfig({ providerId, controller })
await applyProviderConfig({ providerId, controller })
setProvider(providerId)
refreshModelIds()
setIsPickingProvider(false)
}
},
[stateManager, startCodexAuth, handleClineLogin, controller, refreshModelIds],
[stateManager, startCodexAuth, handleClineLogin, startOcaAuth, isOcaAuthenticated, controller, refreshModelIds],
)
// Handle API key submission after provider selection
@@ -990,47 +1022,16 @@ export const SettingsPanelContent: React.FC<SettingsPanelContentProps> = ({
// Handle Bedrock configuration complete
const handleBedrockComplete = useCallback(
(bedrockConfig: BedrockConfig) => {
const config: Record<string, unknown> = {
actModeApiProvider: "bedrock",
planModeApiProvider: "bedrock",
apiProvider: "bedrock",
awsAuthentication: bedrockConfig.awsAuthentication,
awsRegion: bedrockConfig.awsRegion,
awsUseCrossRegionInference: bedrockConfig.awsUseCrossRegionInference,
}
const defaultModelId = getDefaultModelId("bedrock")
if (defaultModelId) {
// Use provider-specific model ID keys
const actModelKey = getProviderModelIdKey("bedrock" as ApiProvider, "act")
const planModelKey = getProviderModelIdKey("bedrock" as ApiProvider, "plan")
if (actModelKey) config[actModelKey] = defaultModelId
if (planModelKey) config[planModelKey] = defaultModelId
}
if (bedrockConfig.awsProfile !== undefined) config.awsProfile = bedrockConfig.awsProfile
if (bedrockConfig.awsAccessKey) config.awsAccessKey = bedrockConfig.awsAccessKey
if (bedrockConfig.awsSecretKey) config.awsSecretKey = bedrockConfig.awsSecretKey
if (bedrockConfig.awsSessionToken) config.awsSessionToken = bedrockConfig.awsSessionToken
stateManager.setApiConfiguration(config as Record<string, string>)
// Close Bedrock config first, then flush state async
// Update UI state first for responsiveness
setProvider("bedrock")
refreshModelIds()
setIsConfiguringBedrock(false)
setPendingProvider(null)
// Flush state and rebuild API handler in background
stateManager.flushPendingState().then(() => {
if (controller?.task) {
const currentMode = stateManager.getGlobalSettingsKey("mode")
const apiConfig = stateManager.getApiConfiguration()
controller.task.api = buildApiHandler({ ...apiConfig, ulid: controller.task.ulid }, currentMode)
}
})
// Apply config and rebuild API handler in background
applyBedrockConfig({ bedrockConfig, controller })
},
[stateManager, controller],
[controller, refreshModelIds],
)
// Handle saving edited value
@@ -1046,8 +1047,8 @@ export const SettingsPanelContent: React.FC<SettingsPanelContentProps> = ({
const actProvider = apiConfig.actModeApiProvider
const planProvider = apiConfig.planModeApiProvider || actProvider
if (!actProvider && !planProvider) break
const actKey = actProvider ? getProviderModelIdKey(actProvider as ApiProvider, "act") : null
const planKey = planProvider ? getProviderModelIdKey(planProvider as ApiProvider, "plan") : null
const actKey = actProvider ? getProviderModelIdKey(actProvider, "act") : null
const planKey = planProvider ? getProviderModelIdKey(planProvider, "plan") : null
if (separateModels) {
// Only update the selected mode's model
@@ -1204,6 +1205,14 @@ export const SettingsPanelContent: React.FC<SettingsPanelContentProps> = ({
return
}
// OCA OAuth waiting mode - escape to cancel
if (isWaitingForOcaAuth) {
if (key.escape) {
cancelOcaAuth()
}
return
}
if (isEditing) {
if (key.escape) {
setIsEditing(false)
@@ -1424,6 +1433,25 @@ export const SettingsPanelContent: React.FC<SettingsPanelContentProps> = ({
)
}
if (isWaitingForOcaAuth) {
return (
<Box flexDirection="column">
<Box>
<Text color={COLORS.primaryBlue}>
<Spinner type="dots" />
</Text>
<Text color="white"> Waiting for OCA sign-in...</Text>
</Box>
<Box marginTop={1}>
<Text color="gray">Complete sign-in in your browser.</Text>
</Box>
<Box marginTop={1}>
<Text color="gray">Esc to cancel</Text>
</Box>
</Box>
)
}
// Account tab - loading state
if (currentTab === "account" && isAccountLoading) {
return (
@@ -1559,8 +1587,23 @@ export const SettingsPanelContent: React.FC<SettingsPanelContentProps> = ({
)
}
// Determine if we're in a subpage (picker, editor, or waiting state)
const isSubpage =
isPickingProvider ||
isPickingModel ||
isPickingFeaturedModel ||
isPickingLanguage ||
isEnteringApiKey ||
isConfiguringBedrock ||
isWaitingForCodexAuth ||
!!codexAuthError ||
isPickingOrganization ||
isWaitingForClineAuth ||
isWaitingForOcaAuth ||
isEditing
return (
<Panel currentTab={currentTab} label="Settings" tabs={TABS}>
<Panel currentTab={currentTab} isSubpage={isSubpage} label="Settings" tabs={TABS}>
{renderContent()}
</Panel>
)
+5 -2
View File
@@ -8,7 +8,7 @@ import { Box, Text, useInput } from "ink"
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"
import { StateManager } from "@/core/storage/StateManager"
import type { ApiProvider } from "@/shared/api"
import { getProviderDefaultModelId, getProviderModelIdKey, Mode } from "@/shared/storage"
import { getProviderDefaultModelId, getProviderModelIdKey, Mode, SettingsKey } from "@/shared/storage"
import { useStdinContext } from "../context/StdinContext"
import {
checkAndWarnRipgrepMissing,
@@ -79,7 +79,10 @@ export const WelcomeView: React.FC<WelcomeViewProps> = ({ onSubmit, onExit, cont
const modelId = useMemo(() => {
const stateManager = StateManager.get()
const modelKey = getProviderModelIdKey(provider as ApiProvider, mode)
return (stateManager.getGlobalSettingsKey(modelKey as string) as string) || getProviderDefaultModelId(provider)
return (
(stateManager.getGlobalSettingsKey(modelKey as SettingsKey) as string) ||
getProviderDefaultModelId(provider as ApiProvider)
)
}, [mode, provider])
const toggleMode = useCallback(() => {
+15 -9
View File
@@ -7,48 +7,54 @@ export interface FeaturedModel {
id: string
name: string
description: string
label: string
labels: string[]
}
export const FEATURED_MODELS = {
recommended: [
{
id: "anthropic/claude-opus-4.5",
name: "Claude Opus 4.5",
id: "anthropic/claude-opus-4.6",
name: "Claude Opus 4.6",
description: "State-of-the-art for complex coding",
label: "Best",
labels: ["BEST"],
},
{
id: "openai/gpt-5.2-codex",
name: "GPT 5.2 Codex",
description: "OpenAI's latest with strong coding abilities",
label: "New",
labels: ["NEW"],
},
{
id: "google/gemini-3-pro-preview",
name: "Gemini 3 Pro",
description: "1M context window for large codebases",
label: "Trending",
labels: ["TRENDING"],
},
] as FeaturedModel[],
free: [
{
id: "minimax/minimax-m2.1",
name: "MiniMax M2.1",
description: "Exceptional Multi-Programming Language Capabilities",
labels: ["FREE"],
},
{
id: "moonshotai/kimi-k2.5",
name: "Kimi K2.5",
description: "State-of-the-art model topping benchmarks",
label: "FREE",
labels: ["FREE"],
},
{
id: "kwaipilot/kat-coder-pro",
name: "KAT Coder Pro",
description: "Advanced agentic coding model",
label: "FREE",
labels: ["FREE"],
},
{
id: "arcee-ai/trinity-large-preview:free",
name: "Trinity Large Preview",
description: "US built open source coding model",
label: "FREE",
labels: ["FREE"],
},
] as FeaturedModel[],
}
+11
View File
@@ -142,6 +142,17 @@ export class CliEnvServiceClient implements EnvServiceClientInterface {
printInfo("Shutting down...")
return proto.cline.Empty.create()
}
async openExternal(request: proto.cline.StringRequest): Promise<proto.cline.Empty> {
const url = request.value || ""
if (url) {
printInfo(`🌐 Opening: ${url}`)
// Dynamically import 'open' to open URL in default browser
const { default: open } = await import("open")
await open(url)
}
return proto.cline.Empty.create()
}
}
/**
+92
View File
@@ -0,0 +1,92 @@
/**
* Hook for OCA OAuth authentication flow in the CLI.
* Handles starting auth, subscribing to status updates, and notifying on success.
*/
import type { OcaAuthState } from "@shared/proto/cline/oca_account"
import { useCallback, useEffect, useRef, useState } from "react"
import type { Controller } from "@/core/controller"
import { OcaAuthService } from "@/services/auth/oca/OcaAuthService"
interface UseOcaAuthOptions {
controller: Controller | undefined
/** If provided, controls when subscription is active (for external state management like AuthView's step) */
enabled?: boolean
onSuccess?: () => void | Promise<void>
onError?: (error: Error) => void
}
interface UseOcaAuthResult {
/** Whether we're waiting for auth to complete (only relevant when not using `enabled` prop) */
isWaiting: boolean
/** Start the OAuth flow - opens browser */
startAuth: () => void
/** Cancel waiting for auth */
cancelAuth: () => void
/** The authenticated user, if any */
user: OcaAuthState["user"] | null
/** Whether user is currently authenticated */
isAuthenticated: boolean
}
export function useOcaAuth({ controller, enabled, onSuccess, onError }: UseOcaAuthOptions): UseOcaAuthResult {
const [isWaiting, setIsWaiting] = useState(false)
const [user, setUser] = useState<OcaAuthState["user"] | null>(null)
const onSuccessRef = useRef(onSuccess)
onSuccessRef.current = onSuccess
// Determine if subscription should be active
// If `enabled` is provided, use it; otherwise use internal `isWaiting` state
const isSubscriptionActive = enabled !== undefined ? enabled : isWaiting
const startAuth = useCallback(() => {
if (!controller) {
return
}
setIsWaiting(true)
OcaAuthService.initialize(controller)
OcaAuthService.getInstance()
.createAuthRequest()
.catch((error) => {
setIsWaiting(false)
onError?.(error instanceof Error ? error : new Error(String(error)))
})
}, [controller, onError])
const cancelAuth = useCallback(() => {
setIsWaiting(false)
}, [])
// Check if already authenticated
const isAuthenticated = !!user?.uid
// Subscribe to auth status updates when active
useEffect(() => {
if (!isSubscriptionActive || !controller) {
return
}
let cancelled = false
const responseHandler = async (authState: OcaAuthState) => {
if (cancelled) {
return
}
if (authState.user?.uid) {
setUser(authState.user)
setIsWaiting(false)
await onSuccessRef.current?.()
}
}
// Ensure OcaAuthService is initialized before subscribing
OcaAuthService.initialize(controller)
OcaAuthService.getInstance().subscribeToAuthStatusUpdate({}, responseHandler, `cli-oca-auth-${Date.now()}`)
return () => {
cancelled = true
}
}, [isSubscriptionActive, controller])
return { isWaiting, startAuth, cancelAuth, user, isAuthenticated }
}
+10 -4
View File
@@ -76,7 +76,7 @@ export interface UseTextInputReturn {
cursorPos: number
// Text manipulation
setText: (text: string) => void
setText: (text: string | ((prev: string) => string)) => void
insertText: (text: string) => void
setCursorPos: (pos: number | ((prev: number) => number)) => void
@@ -102,9 +102,15 @@ export function useTextInput(): UseTextInputReturn {
cursorRef.current = cursorPos
// Text manipulation
const setText = useCallback((newText: string) => {
setTextState(newText)
setCursorPosState(newText.length)
const setText = useCallback((newText: string | ((prev: string) => string)) => {
setTextState((prev) => {
const resolved = typeof newText === "function" ? newText(prev) : newText
// Only update cursor to end if setting a direct value (not functional update)
if (typeof newText !== "function") {
setCursorPosState(resolved.length)
}
return resolved
})
}, [])
const insertText = useCallback((insertedText: string) => {
+261 -122
View File
@@ -19,6 +19,7 @@ import { BannerService } from "@/services/banner/BannerService"
import { ErrorService } from "@/services/error/ErrorService"
import { initializeDistinctId } from "@/services/logging/distinctId"
import { telemetryService } from "@/services/telemetry"
import { HistoryItem } from "@/shared/HistoryItem"
import { Logger } from "@/shared/services/Logger"
import { Session } from "@/shared/services/Session"
import { getProviderModelIdKey, ProviderToApiKeyMap } from "@/shared/storage"
@@ -36,11 +37,166 @@ import { CLINE_CLI_DIR, getCliBinaryPath } from "./utils/path"
import { readStdinIfPiped } from "./utils/piped"
import { runPlainTextTask } from "./utils/plain-text-task"
import { applyProviderConfig } from "./utils/provider-config"
import { selectOutputMode } from "./utils/mode-selection"
import { getValidCliProviders, isValidCliProvider } from "./utils/providers"
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
yolo?: boolean
timeout?: string
json?: boolean
stdinWasPiped?: boolean
}
/**
* 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
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")
}
}
/**
* 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 ctx.controller.stateManager.flushPendingState()
await ctx.controller.dispose()
await ErrorService.get().dispose()
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 ? 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)
}
/**
* Create the standard cleanup function for Ink apps.
*/
function createInkCleanup(ctx: CliContext, onTaskError?: () => boolean): () => Promise<void> {
return async () => {
await ctx.controller.stateManager.flushPendingState()
await ctx.controller.dispose()
await ErrorService.get().dispose()
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
@@ -228,24 +384,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 +401,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",
@@ -373,16 +434,7 @@ async function runTask(
exit(0)
},
}),
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),
)
}
@@ -438,9 +490,6 @@ async function showConfig(options: { config?: string }) {
// Dynamically import the wrapper to avoid circular dependencies
const { ConfigViewWrapper } = await import("./components/ConfigViewWrapper")
// Check feature flags
const skillsEnabled = stateManager.getGlobalSettingsKey("skillsEnabled") ?? false
telemetryService.captureHostEvent("config_command", "executed")
await runInkApp(
@@ -450,7 +499,7 @@ async function showConfig(options: { config?: string }) {
globalState: stateManager.getAllGlobalStateEntries(),
workspaceState: stateManager.getAllWorkspaceStateEntries(),
hooksEnabled: true,
skillsEnabled,
skillsEnabled: true,
isRawModeSupported: checkRawModeSupport(),
}),
async () => {
@@ -599,7 +648,13 @@ program
.option("--config <path>", "Path to Cline configuration directory")
.option("--thinking", "Enable extended thinking (1024 token budget)")
.option("--json", "Output messages as JSON instead of styled text")
.action((prompt, options) => runTask(prompt, options))
.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")
@@ -686,7 +741,7 @@ async function checkAnyProviderConfigured(): Promise<boolean> {
const config = stateManager.getApiConfiguration() as Record<string, unknown>
// Check Cline account (stored as "cline:clineAccountId" in secrets, loaded into config)
if (config["cline:clineAccountId"]) return true
if (config["clineApiKey"] || config["cline:clineAccountId"]) return true
// Check OpenAI Codex OAuth (stored in SECRETS_KEYS, loaded into config)
if (config["openai-codex-oauth-credentials"]) return true
@@ -712,6 +767,69 @@ 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 ctx.controller.stateManager.flushPendingState()
await ctx.controller.dispose()
await ErrorService.get().dispose()
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: () => {
exit(0)
},
}),
createInkCleanup(ctx, () => taskError),
)
}
/**
* Show welcome prompt and wait for user input
* If auth is not configured, show auth flow first
@@ -761,6 +879,7 @@ program
.option("--thinking", "Enable extended thinking (1024 token budget)")
.option("--json", "Output messages as JSON instead of styled text")
.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) {
@@ -775,8 +894,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)
}
@@ -799,9 +928,19 @@ 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)
+10
View File
@@ -0,0 +1,10 @@
/**
* Opens a URL in the user's default browser.
* Uses dynamic import of the 'open' package to open URLs.
*
* @param url - The URL to open in the browser
*/
export async function openUrlInBrowser(url: string): Promise<void> {
const { default: open } = await import("open")
await open(url)
}
+2 -2
View File
@@ -13,6 +13,6 @@ import { Fzf } from "fzf"
*/
export function fuzzyFilter<T>(items: readonly T[], query: string, selector: (item: T) => string): T[] {
if (!query) return [...items]
const fzf = new Fzf(items, { selector })
return fzf.find(query).map((result) => result.item)
const fzf = new Fzf(items as any, { selector } as any)
return fzf.find(query).map((result) => result.item) as T[]
}
+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" }
}
+2 -2
View File
@@ -38,9 +38,9 @@ export async function fetchOpenRouterModels(): Promise<string[]> {
throw new Error(`Failed to fetch: ${response.status}`)
}
const data = await response.json()
const data = (await response.json()) as { data?: OpenRouterModel[] }
if (data?.data) {
const models = (data.data as OpenRouterModel[]).map((m) => m.id).sort((a, b) => a.localeCompare(b))
const models = data.data.map((m) => m.id).sort((a, b) => a.localeCompare(b))
cachedModels = models
return models
}
-1
View File
@@ -1,7 +1,6 @@
import { execFileSync } from "node:child_process"
import os from "node:os"
import path from "node:path"
// @ts-expect-error - @vscode/ripgrep has no type declarations
import { rgPath } from "@vscode/ripgrep"
const data = process.env.CLINE_DATA_DIR ?? path.join(os.homedir(), ".cline", "data")
+125 -31
View File
@@ -1,54 +1,145 @@
import { EventEmitter } from "node:events"
import * as fs from "node:fs"
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"
import { readStdinIfPiped } from "./piped"
// Mock the fs module
vi.mock("node:fs", () => ({
readFileSync: vi.fn(),
}))
// Mock fs.fstatSync to simulate pipe detection
vi.mock("node:fs", async () => {
const actual = await vi.importActual("node:fs")
return {
...actual,
fstatSync: vi.fn(),
}
})
describe("readStdinIfPiped", () => {
const mockReadFileSync = fs.readFileSync as ReturnType<typeof vi.fn>
let originalIsTTY: boolean | undefined
let originalStdin: typeof process.stdin
let mockStdin: EventEmitter & {
isTTY?: boolean
setEncoding: ReturnType<typeof vi.fn>
resume: ReturnType<typeof vi.fn>
}
beforeEach(() => {
vi.clearAllMocks()
originalIsTTY = process.stdin.isTTY
originalStdin = process.stdin
// Create a mock stdin
mockStdin = Object.assign(new EventEmitter(), {
isTTY: undefined as boolean | undefined,
setEncoding: vi.fn(),
resume: vi.fn(),
})
// Default: simulate a real pipe (FIFO)
vi.mocked(fs.fstatSync).mockReturnValue({
isFIFO: () => true,
isFile: () => false,
} as fs.Stats)
})
afterEach(() => {
vi.restoreAllMocks()
// Restore original isTTY value
Object.defineProperty(process.stdin, "isTTY", {
value: originalIsTTY,
// Restore original stdin
Object.defineProperty(process, "stdin", {
value: originalStdin,
writable: true,
configurable: true,
})
})
function setTTY(value: boolean | undefined) {
Object.defineProperty(process.stdin, "isTTY", {
value,
mockStdin.isTTY = value
Object.defineProperty(process, "stdin", {
value: mockStdin,
writable: true,
configurable: true,
})
}
function emitData(data: string) {
mockStdin.emit("data", data)
}
function emitEnd() {
mockStdin.emit("end")
}
function emitError(error: Error) {
mockStdin.emit("error", error)
}
describe("TTY detection", () => {
it("should return null when stdin is a TTY (interactive terminal)", async () => {
setTTY(true)
const result = await readStdinIfPiped()
expect(result).toBeNull()
expect(mockReadFileSync).not.toHaveBeenCalled()
})
it("should attempt to read when stdin is not a TTY (piped input)", async () => {
setTTY(false)
mockReadFileSync.mockReturnValue("")
const promise = readStdinIfPiped()
emitEnd()
const result = await promise
expect(result).toBe("")
expect(mockStdin.setEncoding).toHaveBeenCalledWith("utf8")
expect(mockStdin.resume).toHaveBeenCalled()
})
})
describe("stdin type detection (fstat)", () => {
it("should return null when stdin is not a FIFO or file (spawned without TTY)", async () => {
setTTY(false)
// Simulate a character device or socket (not a pipe)
vi.mocked(fs.fstatSync).mockReturnValue({
isFIFO: () => false,
isFile: () => false,
} as fs.Stats)
const result = await readStdinIfPiped()
expect(result).toBeNull()
expect(mockReadFileSync).toHaveBeenCalledWith(0, "utf8")
})
it("should return null when fstatSync throws (detached stdin)", async () => {
setTTY(false)
vi.mocked(fs.fstatSync).mockImplementation(() => {
throw new Error("EBADF: bad file descriptor")
})
const result = await readStdinIfPiped()
expect(result).toBeNull()
})
it("should read from stdin when it is a FIFO (pipe)", async () => {
setTTY(false)
vi.mocked(fs.fstatSync).mockReturnValue({
isFIFO: () => true,
isFile: () => false,
} as fs.Stats)
const promise = readStdinIfPiped()
emitData("piped content")
emitEnd()
const result = await promise
expect(result).toBe("piped content")
})
it("should read from stdin when it is a regular file (redirected)", async () => {
setTTY(false)
vi.mocked(fs.fstatSync).mockReturnValue({
isFIFO: () => false,
isFile: () => true,
} as fs.Stats)
const promise = readStdinIfPiped()
emitData("file content")
emitEnd()
const result = await promise
expect(result).toBe("file content")
})
})
@@ -76,14 +167,14 @@ describe("readStdinIfPiped", () => {
{
name: "empty string",
input: "",
expected: null,
description: "should return null for empty input",
expected: "",
description: "should return empty string for empty input",
},
{
name: "whitespace only",
input: " \n \t \n ",
expected: null,
description: "should return null for whitespace-only input",
expected: "",
description: "should return empty string for whitespace-only input",
},
{
name: "leading and trailing whitespace",
@@ -127,25 +218,25 @@ describe("readStdinIfPiped", () => {
it(`${name}${description ? ` - ${description}` : ""}`, async () => {
setTTY(false)
const data = Array.isArray(input) ? input.join("\n") : input
mockReadFileSync.mockReturnValue(data)
const result = await readStdinIfPiped()
const promise = readStdinIfPiped()
emitData(data)
emitEnd()
const result = await promise
expect(result).toBe(expected)
})
})
})
describe("error handling", () => {
it("should return null on fs.readFileSync error and fall back to async", async () => {
it("should return null on stdin error", async () => {
setTTY(false)
mockReadFileSync.mockImplementation(() => {
throw new Error("EAGAIN: resource temporarily unavailable")
})
// The async fallback will timeout since we can't easily mock process.stdin events
// But we can verify it doesn't throw
const result = await readStdinIfPiped()
// Result will be null because async path times out with no data
const promise = readStdinIfPiped()
emitError(new Error("EAGAIN: resource temporarily unavailable"))
const result = await promise
expect(result).toBeNull()
})
})
@@ -188,9 +279,12 @@ describe("readStdinIfPiped", () => {
useCases.forEach(({ name, input, expected }) => {
it(`should handle ${name}`, async () => {
setTTY(false)
mockReadFileSync.mockReturnValue(input)
const result = await readStdinIfPiped()
const promise = readStdinIfPiped()
emitData(input)
emitEnd()
const result = await promise
expect(result).toBe(expected)
})
})
+18 -1
View File
@@ -1,3 +1,5 @@
import * as fs from "node:fs"
/**
* Read piped input from stdin (non-blocking)
*
@@ -9,11 +11,26 @@
* for EOF which signals that the previous command has finished writing.
*/
export async function readStdinIfPiped(): Promise<string | null> {
// Check if stdin is a TTY (interactive) or piped
// Check if stdin is a TTY (interactive) - no piped input
if (process.stdin.isTTY) {
return null
}
// When spawned as a child process without TTY (e.g., from spawn()), stdin.isTTY
// is false but there's no actual piped input. Check if stdin is a real pipe/file
// by testing if we can get stats on fd 0. A real pipe will have stats, while
// a detached stdin may throw or have unusual properties.
try {
const stats = fs.fstatSync(0)
// If it's not a FIFO (pipe) or regular file, treat as no input
if (!stats.isFIFO() && !stats.isFile()) {
return null
}
} catch {
// If we can't stat stdin, treat as no input
return null
}
// Use async approach - more reliable for piped input from other commands
// The synchronous readFileSync(0) can fail with EAGAIN when the pipe
// isn't ready yet (common when piping from another cline command)
+41 -6
View File
@@ -12,18 +12,23 @@
// 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"
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) */
timeoutSeconds?: number
/** Task ID to resume an existing task */
taskId?: string
}
/**
@@ -39,9 +44,9 @@ 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
})
@@ -50,6 +55,13 @@ export async function runPlainTextTask(options: PlainTextTaskOptions): Promise<b
// 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()
// Helper to process a message and track completion state
const processMessage = (message: ClineMessage) => {
const ts = message.ts || 0
@@ -67,8 +79,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,8 +115,27 @@ export async function runPlainTextTask(options: PlainTextTaskOptions): Promise<b
)
try {
// Start the task
await controller.initTask(prompt, imageDataUrls)
// 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 }))
// 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)
} else {
throw new Error("Either taskId or prompt must be provided")
}
// Normal mode: wait for task completion
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])
+50
View File
@@ -8,6 +8,7 @@ import { getProviderModelIdKey, ProviderToApiKeyMap } from "@shared/storage"
import { buildApiHandler } from "@/core/api"
import type { Controller } from "@/core/controller"
import { StateManager } from "@/core/storage/StateManager"
import type { BedrockConfig } from "../components/BedrockSetup"
import { getDefaultModelId } from "../components/ModelPicker"
export interface ApplyProviderConfigOptions {
@@ -75,3 +76,52 @@ export async function applyProviderConfig(options: ApplyProviderConfigOptions):
controller.task.api = buildApiHandler({ ...apiConfig, ulid: controller.task.ulid }, currentMode)
}
}
export interface ApplyBedrockConfigOptions {
bedrockConfig: BedrockConfig
modelId?: string
controller?: Controller
}
/**
* Apply Bedrock provider configuration to state
* Handles AWS-specific fields (authentication, region, credentials)
*/
export async function applyBedrockConfig(options: ApplyBedrockConfigOptions): Promise<void> {
const { bedrockConfig, modelId, controller } = options
const stateManager = StateManager.get()
const config: Record<string, unknown> = {
actModeApiProvider: "bedrock",
planModeApiProvider: "bedrock",
awsAuthentication: bedrockConfig.awsAuthentication,
awsRegion: bedrockConfig.awsRegion,
awsUseCrossRegionInference: bedrockConfig.awsUseCrossRegionInference,
}
// Add model ID
const finalModelId = modelId || getDefaultModelId("bedrock")
if (finalModelId) {
const actModelKey = getProviderModelIdKey("bedrock" as ApiProvider, "act")
const planModelKey = getProviderModelIdKey("bedrock" as ApiProvider, "plan")
if (actModelKey) config[actModelKey] = finalModelId
if (planModelKey) config[planModelKey] = finalModelId
}
// Add optional AWS credentials
if (bedrockConfig.awsProfile !== undefined) config.awsProfile = bedrockConfig.awsProfile
if (bedrockConfig.awsAccessKey) config.awsAccessKey = bedrockConfig.awsAccessKey
if (bedrockConfig.awsSecretKey) config.awsSecretKey = bedrockConfig.awsSecretKey
if (bedrockConfig.awsSessionToken) config.awsSessionToken = bedrockConfig.awsSessionToken
// Save via StateManager
stateManager.setApiConfiguration(config as Record<string, string>)
await stateManager.flushPendingState()
// Rebuild API handler on active task if one exists
if (controller?.task) {
const currentMode = stateManager.getGlobalSettingsKey("mode")
const apiConfig = stateManager.getApiConfiguration()
controller.task.api = buildApiHandler({ ...apiConfig, ulid: controller.task.ulid }, currentMode)
}
}
+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])
}
+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)
})
}
+2 -1
View File
@@ -7,7 +7,8 @@
"jsx": "react",
"jsxFactory": "React.createElement",
"lib": [
"es2022"
"es2022",
"DOM"
],
"module": "esnext",
"moduleResolution": "Bundler",
+216
View File
@@ -0,0 +1,216 @@
---
title: "ACP: Editor Integrations"
description: "Use Cline in JetBrains, Neovim, Zed, and other editors via the Agent Client Protocol"
---
Cline CLI supports the [Agent Client Protocol (ACP)](https://agentclientprotocol.com/), an open standard that enables AI coding agents to work across different editors and IDEs. This means you can use the full Cline agent—with all its capabilities including Skills, Hooks, and MCP integrations—in your preferred development environment.
## Why ACP?
- **Editor flexibility**: Use Cline in JetBrains, Neovim, Zed, or any ACP-compatible editor
- **No feature compromises**: Full access to Cline's capabilities regardless of editor
- **Team consistency**: Same AI assistant across different developer workflows
- **Open standard**: Built on Zed's open Agent Client Protocol specification
## JetBrains IDEs
[JetBrains](https://www.jetbrains.com) IDEs include IntelliJ IDEA, PyCharm, WebStorm, and more. They offer built-in AI Assistant with ACP support.
<Note>
**Recommended: Native JetBrains Plugin**
For the best JetBrains experience, install the [native Cline plugin](/getting-started/installing-cline#jetbrains-ides) from the JetBrains Marketplace. It provides full IDE integration and the complete Cline experience.
The ACP setup below is an alternative way to use Cline CLI features in JetBrains IDEs.
</Note>
Alternatively, you can run Cline CLI in IntelliJ IDEA, PyCharm, WebStorm, and all other JetBrains IDEs through their built-in AI Assistant with ACP support.
<video
src="https://storage.googleapis.com/cline_public_images/cline-acp-jetbrains.mp4"
autoPlay
loop
muted
playsInline
style={{ width: "100%", borderRadius: "8px", marginTop: "16px", marginBottom: "16px" }}
/>
### Setup
1. **Install Cline CLI** (if not already installed):
```bash
npm i -g cline
```
2. **Authenticate with Cline**:
```bash
cline auth
```
3. **Configure JetBrains AI Assistant**:
- Open your JetBrains IDE
- Navigate to `Settings | Tools | AI Assistant | Agents`
- Click "Add Custom Agent"
- This opens/creates `~/.jetbrains/acp.json`
4. **Add Cline to `acp.json`**:
```json
{
"agent_servers": {
"Cline": {
"command": "cline",
"args": ["--acp"],
"env": {}
}
}
}
```
5. **Use Cline**:
- Open the AI Chat tool window
- Select "Cline" from the agent dropdown
- Start coding with Cline in your JetBrains IDE!
<Tip>
JetBrains AI Assistant can expose its built-in MCP server to Cline, giving Cline access to IDE-specific tools and context.
</Tip>
## Neovim
[Neovim](https://neovim.io) is a hyperextensible Vim-based text editor loved by developers for its speed and flexibility. Use Cline in Neovim through the [agentic.nvim](https://github.com/carlos-algms/agentic.nvim) or [avante.nvim](https://github.com/yetone/avante.nvim) plugins, which provide ACP integration.
<video
src="https://storage.googleapis.com/cline_public_images/cline-acp-neovim-avante.mp4"
autoPlay
loop
muted
playsInline
style={{ width: "100%", borderRadius: "8px", marginTop: "16px", marginBottom: "16px" }}
/>
### Setup with agentic.nvim
1. **Install Cline CLI** (if not already installed):
```bash
npm i -g cline
```
2. **Authenticate with Cline**:
```bash
cline auth
```
3. **Install agentic.nvim** using lazy.nvim:
```lua
{
"carlos-algms/agentic.nvim",
opts = {
provider = "cline-acp",
acp_providers = {
["cline-acp"] = {
command = "cline",
args = {"--acp"},
},
},
},
keys = {
{"<C-\\>", function() require("agentic").toggle() end, mode={"n","v","i"}, desc="Toggle Cline Chat"},
},
}
```
4. **Use Cline**:
- Press `<C-\>` to toggle Cline chat
- Start coding with Cline in Neovim!
### Setup with avante.nvim
Follow the [avante.nvim documentation](https://github.com/yetone/avante.nvim) for configuring external ACP agents and point it to `cline --acp`.
## Zed
[Zed](https://zed.dev) is a high-performance, multiplayer code editor built from the ground up for speed and collaboration. Zed's team created the Agent Client Protocol, making Cline a natural fit for this editor.
### Setup
1. **Install Cline CLI** (if not already installed):
```bash
npm i -g cline
```
2. **Authenticate with Cline**:
```bash
cline auth
```
3. **Configure Zed**:
- Open Zed settings (`Cmd/Ctrl + ,`)
- Add Cline to your `settings.json`:
```json
{
"agent_servers": {
"Cline": {
"type": "custom",
"command": "cline",
"args": ["--acp"],
"env": {}
}
}
}
```
4. **Use Cline**:
- Open the AI assistant panel
- Select "Cline" from the agent dropdown
- Start coding with Cline in Zed!
## Other Editors
Any editor that supports the Agent Client Protocol can run Cline. Check your editor's documentation for ACP configuration instructions, then point it to:
```bash
cline --acp
```
## Troubleshooting
### Agent not appearing
- Ensure Cline CLI is installed globally: `npm i -g cline`
- Verify authentication: `cline auth`
- Check that `cline --acp` runs without errors
- Restart your editor after configuration changes
### Permission errors
If Cline can't access files or run commands:
- Check that your editor's ACP integration passes the correct working directory
- Verify file permissions in your project
- Ensure Cline has approval settings configured correctly
### Connection issues
- Make sure no other Cline instance is using the same configuration directory
- Check editor logs for ACP-related errors
- Try running `cline --acp` manually to test the connection
## Learn More
<Columns cols={2}>
<Card title="CLI Overview" icon="terminal" href="/cline-cli/overview">
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>
<Card title="Skills" icon="graduation-cap" href="/features/skills">
Understand how Cline's Skills work across all editors via ACP.
</Card>
<Card title="Hooks" icon="link" href="/features/hooks/index">
Learn how to enforce policies with Hooks in any editor.
</Card>
</Columns>
+482
View File
@@ -0,0 +1,482 @@
---
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
+375 -431
View File
@@ -1,482 +1,426 @@
---
title: "CLI Reference"
description: "Complete command reference for Cline CLI including configuration, instance management, and task commands"
description: "Complete command reference for Cline CLI including all commands, flags, and configuration options"
---
Complete command reference for Cline CLI. Use this for detailed documentation on all commands, options, and configuration.
# CLI Reference
For quick help in your terminal:
This page documents all available commands, flags, and configuration options for Cline CLI. For quick help in your terminal, use:
```bash
cline --help # Show all commands
cline task --help # Show task-specific commands
man cline # View the full manual page
cline task --help # Show task command options
cline auth --help # Show auth command options
man cline # View the full manual page (if installed)
```
## Manual Page
## Synopsis
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.
```bash
cline [prompt] [options]
cline <command> [options] [arguments]
```
## JSON output (-F json)
## Global Options
When you run a command with `-F json` (or `--output-format json`), Cline prints each client message as JSON.
These options work with any command:
### ClineMessage schema
| Option | Description |
|--------|-------------|
| `--config <path>` | Use a custom configuration directory instead of `~/.cline/data/` |
| `-c, --cwd <path>` | Set the working directory for the task |
| `-v, --verbose` | Show detailed output including model reasoning |
| `--help` | Show help for the command |
| 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. |
## Modes of Operation
<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>
Cline CLI automatically detects the best output mode based on how you invoke it:
### Example
| Mode | When Activated | Description |
|------|----------------|-------------|
| **Interactive** | `cline` with no args, TTY connected | Rich terminal UI with real-time streaming, keyboard shortcuts, and visual feedback. |
| **Task** | `cline "prompt"` with TTY connected | Interactive UI starts immediately with your task. |
| **Plain Text** | stdin piped, stdout redirected, or `--yolo`/`--json` flags | Clean text output without UI, suitable for scripting and CI/CD. |
## Agent Behavior
Cline operates in two primary modes that control how it approaches tasks:
| Mode | Description |
|------|-------------|
| **Act Mode** (default) | Cline actively uses tools to accomplish tasks. It can read files, write code, execute commands, use a headless browser, and more. |
| **Plan Mode** | Cline gathers information and creates a detailed plan before implementation. It explores the codebase, asks clarifying questions, and presents a strategy for your approval before switching to Act Mode. |
Use `-a, --act` or `-p, --plan` flags to explicitly set the mode.
## Commands
### cline (default)
Run Cline without a subcommand to start a task or enter interactive mode.
```bash
# Interactive mode (no arguments)
cline
# Start a task directly
cline "your prompt here"
```
**Options:**
| Option | Description |
|--------|-------------|
| `-a, --act` | Start in Act mode (default). Cline executes actions directly. |
| `-p, --plan` | Start in Plan mode. Cline analyzes and creates a strategy before acting. |
| `-y, --yolo` | YOLO mode: auto-approve all actions, use plain text output, exit when complete. Ideal for CI/CD. |
| `-m, --model <id>` | Use a specific model (e.g., `claude-sonnet-4-5-20250929`, `gpt-4o`). |
| `-i, --images <paths...>` | Include image files with the prompt. |
| `--thinking` | Enable extended thinking with a 1024 token budget. |
| `--json` | Output messages as JSON (one object per line). Forces plain text mode. |
| `--timeout <seconds>` | Maximum execution time before the task is stopped. |
**Mode Behavior:**
| Invocation | Output Mode | Why |
|------------|-------------|-----|
| `cline` | Interactive UI | No arguments, TTY connected |
| `cline "prompt"` | Interactive UI | TTY connected |
| `cline -y "prompt"` | Plain text | YOLO flag forces plain text |
| `cline --json "prompt"` | JSON | JSON flag forces plain text |
| `cat file \| cline "prompt"` | Plain text | stdin is piped |
| `cline "prompt" > out.txt` | Plain text | stdout is redirected |
---
### cline task (alias: t)
Run a task with a prompt. This is equivalent to `cline "prompt"`.
```bash
cline task "Create a REST API endpoint"
cline t "Fix the bug in utils.js"
```
**Options:** Same as the default command above.
---
### cline auth
Configure authentication with an AI provider.
```bash
# Interactive wizard
cline auth
# Quick setup with flags
cline auth -p anthropic -k sk-ant-api-xxxxx -m claude-sonnet-4-5-20250929
```
**Options:**
| Option | Description |
|--------|-------------|
| `-p, --provider <id>` | Provider ID. See [Supported Providers](#supported-providers) below. |
| `-k, --apikey <key>` | API key for the provider. |
| `-m, --modelid <id>` | Model ID to use (e.g., `claude-sonnet-4-5-20250929`, `gpt-4o`). |
| `-b, --baseurl <url>` | Base URL for OpenAI-compatible providers. |
**Supported Providers:**
| Provider ID | Description |
|-------------|-------------|
| `anthropic` | Anthropic Claude (direct API) |
| `openai-native` | OpenAI GPT models |
| `openai-codex` | ChatGPT subscription via OAuth |
| `openrouter` | OpenRouter (access multiple providers) |
| `bedrock` | AWS Bedrock |
| `gemini` | Google Gemini |
| `xai` | X AI (Grok) |
| `cerebras` | Cerebras (fast inference) |
| `deepseek` | DeepSeek |
| `ollama` | Ollama (local models) |
| `lmstudio` | LM Studio (local models) |
| `openai` | OpenAI-compatible API (custom base URL) |
---
### cline history (alias: h)
Browse task history with pagination.
```bash
# Show recent tasks (default: 10)
cline history
# Show more tasks
cline history -n 20
# Paginate through history
cline history -n 10 -p 2
```
**Options:**
| Option | Description |
|--------|-------------|
| `-n, --limit <number>` | Number of tasks to show (default: 10) |
| `-p, --page <number>` | Page number, 1-based (default: 1) |
---
### cline config
View and manage configuration settings.
```bash
cline config
```
Opens an interactive configuration view with tabs for:
- **Settings** - Global and workspace-specific settings
- **Rules** - `.clinerules` files and imported rules
- **Workflows** - Available workflows (appear as slash commands)
- **Hooks** - Configured hook scripts
- **Skills** - Enabled skills
---
### cline update
Check for updates and install the latest version.
```bash
cline update
```
---
### cline version
Show the installed CLI version.
```bash
cline version
```
---
### cline dev
Developer tools for debugging.
```bash
# Open the log file
cline dev log
```
## Environment Variables
### CLINE_DIR
Override the default configuration directory:
```bash
export CLINE_DIR=/path/to/custom/config
cline "your task"
```
When set, all Cline data (settings, secrets, task history) is stored in this directory instead of `~/.cline/data/`.
**Use cases:**
- Running isolated Cline instances with different settings
- CI/CD environments with custom state directories
- Testing configuration changes without affecting your main setup
### CLINE_COMMAND_PERMISSIONS
Restrict which shell commands Cline can execute:
```bash
export CLINE_COMMAND_PERMISSIONS='{"allow": ["npm *", "git *"], "deny": ["rm -rf *"]}'
```
**Format:**
```json
{
"allow": ["pattern1", "pattern2"],
"deny": ["pattern3"],
"allowRedirects": true
}
```
| Field | Type | Description |
|-------|------|-------------|
| `allow` | `string[]` | Glob patterns for allowed commands. If set, **only** matching commands are permitted. |
| `deny` | `string[]` | Glob patterns for denied commands. Deny rules **always take precedence** over allow rules. |
| `allowRedirects` | `boolean` | Whether to allow shell redirects (`>`, `>>`, `<`). Default: `false`. |
**Examples:**
```bash
# Allow only npm and git commands (deny everything else)
export CLINE_COMMAND_PERMISSIONS='{"allow": ["npm *", "git *"]}'
# Allow dev commands but explicitly deny dangerous ones
export CLINE_COMMAND_PERMISSIONS='{"allow": ["npm *", "git *", "node *"], "deny": ["rm -rf *", "sudo *"]}'
# Allow file reading with redirects
export CLINE_COMMAND_PERMISSIONS='{"allow": ["cat *", "echo *"], "allowRedirects": true}'
```
**How commands are evaluated:**
1. Check for dangerous characters (backticks outside single quotes, unquoted newlines)
2. Parse command into segments split by operators (`&&`, `||`, `|`, `;`)
3. If redirects are detected and `allowRedirects` is not true, command is denied
4. Each segment is validated against deny rules first, then allow rules
5. Subshell contents (`$(...)` and `(...)`) are recursively validated
6. All segments must pass for the command to be allowed
## JSON Output Format
When using `--json`, each message is output as a JSON object (one per line):
```json
{
"type": "say",
"text": "Cline is about to run a command.",
"text": "I'll create the file now.",
"ts": 1760501486669,
"say": "command",
"partial": false
"say": "text"
}
```
### Shell Completion
**Required fields:**
Generate autocompletion scripts for various shells:
| Field | Type | Description |
|-------|------|-------------|
| `type` | `"ask"` \| `"say"` | Message category |
| `text` | `string` | Human-readable message content |
| `ts` | `number` | Unix timestamp in milliseconds |
#### Bash
**Optional fields:**
| Field | Type | Description |
|-------|------|-------------|
| `say` | `string` | Subtype when `type` is `"say"` (e.g., `"text"`, `"tool"`) |
| `ask` | `string` | Subtype when `type` is `"ask"` (e.g., `"tool"`, `"followup"`) |
| `reasoning` | `string` | Model reasoning (omitted when empty) |
| `partial` | `boolean` | `true` while streaming (omitted when complete) |
| `images` | `string[]` | Image URIs (omitted when empty) |
| `files` | `string[]` | File paths (omitted when empty) |
## Configuration Files
Cline stores all data in `~/.cline/` by default:
```
~/.cline/
├── data/ # Configuration directory
│ ├── globalState.json # Global settings
│ ├── secrets.json # API keys (stored securely)
│ ├── workspace/ # Workspace-specific state
│ └── tasks/ # Task history and conversations
└── log/ # Debug logs (view with cline dev log)
```
## Examples
### Interactive Development
```bash
# Generate bash completion
cline completion bash > /etc/bash_completion.d/cline
# Start interactive mode
cline
# Or for user-level installation
cline completion bash > ~/.local/share/bash-completion/completions/cline
# Start with a task and use interactive UI
cline "Help me refactor this codebase"
```
#### Zsh
### Direct Task Execution
```bash
# Generate zsh completion
cline completion zsh > "${fpath[1]}/_cline"
# Run a task directly
cline "Add error handling to utils.js"
# Or add to your .zshrc
echo 'source <(cline completion zsh)' >> ~/.zshrc
# Start in Plan mode to review strategy first
cline -p "Design a caching layer for the API"
# Use a specific model
cline -m gpt-4o "Explain this code"
```
#### Fish
### Piped Input
```bash
# Generate fish completion
cline completion fish > ~/.config/fish/completions/cline.fish
# Pipe file contents
cat README.md | cline "Summarize this document"
# Review git changes
git diff | cline "Review these changes"
# Analyze test output
npm test 2>&1 | cline "Fix any failing tests"
```
#### 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
### Automation and CI/CD
```bash
# Show version information
cline version
# YOLO mode for automated workflows
cline -y "Run tests and fix failures"
# JSON output for scripting
cline --json "List all TODO comments" | jq '.text'
# With timeout
cline -y --timeout 600 "Run the full test suite"
# Chain commands
git diff | cline -y "explain" | cline -y "write a commit message"
```
### Environment Variables
#### CLINE_DIR
Override the default Cline directory location:
### Authentication
```bash
# Override default Cline directory
export CLINE_DIR=/custom/path
# Interactive wizard
cline auth
# Default: ~/.cline
# Quick setup: Anthropic
cline auth -p anthropic -k sk-ant-api-xxxxx -m claude-sonnet-4-5-20250929
# Quick setup: OpenAI
cline auth -p openai-native -k sk-xxxxx -m gpt-4o
# Quick setup: OpenRouter
cline auth -p openrouter -k sk-or-xxxxx
# OpenAI-compatible with custom URL
cline auth -p openai -k your-key -b https://api.example.com/v1
```
This directory is used for:
- Instance registry database
- Configuration files
- Task history
- Checkpoints
## Support
- **Report bugs:** https://github.com/cline/cline/issues
- **Discord community:** https://discord.gg/cline
- **Documentation:** https://docs.cline.bot
## See Also
<Columns cols={2}>
<Card title="Installation & Setup" icon="download" href="/cline-cli/installation">
Install Cline CLI and configure authentication.
</Card>
<Card title="Interactive Mode" icon="terminal" href="/cline-cli/interactive-mode">
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>
<Card title="Configuration" icon="gear" href="/cline-cli/configuration">
Environment variables and advanced settings.
</Card>
</Columns>
+278
View File
@@ -0,0 +1,278 @@
---
title: "Configuration"
description: "Manage Cline CLI settings with cline config, environment variables, and configuration files"
---
Cline CLI provides multiple ways to configure settings, from the interactive `cline config` command to environment variables for automation.
## The Config Command
Launch the configuration interface:
```bash
cline config
```
This opens an interactive view with tabs for different configuration categories.
## Configuration Tabs
Navigate between tabs using arrow keys.
### Settings Tab
View and edit global and workspace-specific settings:
- **Global State**: Settings that apply across all workspaces
- **Workspace State**: Settings specific to the current directory
### Rules Tab
Manage Cline rules that guide AI behavior:
- **`.clinerules` files**: Project-specific rules in your workspace
- **Cursor rules**: Import rules from Cursor editor format
- **Windsurf rules**: Import rules from Windsurf editor format
Rules help Cline understand your project's conventions, coding standards, and preferences.
### Workflows Tab
View and manage [workflows](/features/slash-commands/workflows/index):
- List available workflows
- View workflow definitions
- Workflows appear as slash commands in interactive mode
### Hooks Tab
Configure [hooks](/features/hooks/index) for custom logic integration:
- Enable/disable hooks globally
- View configured hook scripts
- Hooks run at key points in Cline's workflow
<Note>
Hooks must be enabled via settings. Use `cline config` to toggle `hooks-enabled`.
</Note>
### Skills Tab
Manage [skills](/features/skills) that extend Cline's capabilities:
- View available skills
- Enable/disable specific skills
- Skills provide specialized instructions for specific tasks
## Configuration Directory
Cline stores configuration in `~/.cline/data/`:
```
~/.cline/
├── data/ # Configuration directory
│ ├── globalState.json # Global settings
│ ├── secrets.json # API keys (encrypted)
│ ├── workspace/ # Workspace-specific state
│ └── tasks/ # Task history and data
└── log/ # Log files
```
### Viewing Logs
For debugging, view the log file:
```bash
cline dev log
```
This opens the log file in your default editor.
## Environment Variables
### CLINE_DIR
Override the default configuration directory:
```bash
export CLINE_DIR=/custom/path/to/cline
cline "your task"
```
When set, all Cline data is stored in this directory instead of `~/.cline/data/`.
**Use cases:**
- Running multiple isolated Cline configurations
- Team-shared configurations
- CI/CD with custom state directories
### CLINE_COMMAND_PERMISSIONS
Restrict which shell commands Cline can execute:
```bash
export CLINE_COMMAND_PERMISSIONS='{"allow": ["npm *", "git *"], "deny": ["rm -rf *"]}'
```
**Format:**
```json
{
"allow": ["pattern1", "pattern2"],
"deny": ["pattern3"],
"allowRedirects": true
}
```
**Fields:**
| Field | Type | Description |
|-------|------|-------------|
| `allow` | `string[]` | Glob patterns for allowed commands. If set, only matching commands are permitted. |
| `deny` | `string[]` | Glob patterns for denied commands. Deny rules take precedence over allow. |
| `allowRedirects` | `boolean` | Whether to allow shell redirects (`>`, `>>`, `<`). Default: `false` |
**Examples:**
```bash
# Allow only npm and git commands
export CLINE_COMMAND_PERMISSIONS='{"allow": ["npm *", "git *"]}'
# Allow dev commands but deny dangerous ones
export CLINE_COMMAND_PERMISSIONS='{"allow": ["npm *", "git *", "node *"], "deny": ["rm -rf *", "sudo *"]}'
# Allow file operations with redirects
export CLINE_COMMAND_PERMISSIONS='{"allow": ["cat *", "echo *"], "allowRedirects": true}'
```
<Warning>
When `allow` is set, all commands not matching the allow patterns are denied. Use this for security-sensitive environments.
</Warning>
## Using --config Flag
Run Cline with a custom configuration directory:
```bash
cline --config /path/to/custom/config "your task"
```
This is useful for:
- Running isolated Cline instances
- Testing different configurations
- Separating work and personal setups
**Example: Multiple configurations**
```bash
# Work configuration
cline --config ~/.cline-work "review this PR"
# Personal projects
cline --config ~/.cline-personal "help me with this side project"
```
## Configuration for Local Providers
### Ollama
Configure context window size for Ollama:
```bash
# In settings or via config
cline config
# Navigate to Settings tab, find ollama-api-options-ctx-num
```
Or set via environment:
```bash
# Set context window to 32K tokens
cline -m ollama/llama3 "your task"
```
### LM Studio
Configure max tokens for LM Studio:
```bash
cline config
# Navigate to Settings tab, find lm-studio-max-tokens
```
## Importing Configuration
### From VS Code Extension
If you use the Cline VS Code extension, the CLI automatically detects and can share some settings. However, the CLI maintains its own configuration for terminal-specific features.
### From Other CLI Tools
See [Installation & Setup](/cline-cli/installation#option-3-import-from-existing-tools) for importing configurations from:
- Codex CLI
- OpenCode
## Configuration Best Practices
### For Development
Use the default configuration with workspace-specific rules:
```bash
# Add project-specific rules
echo "Use TypeScript strict mode" > .clinerules/typescript.md
```
### For CI/CD
Use environment variables and `--yolo` mode:
```bash
export CLINE_COMMAND_PERMISSIONS='{"allow": ["npm test", "npm run build"]}'
cline -y "run tests and fix any failures"
```
### For Teams
Share configuration via version control:
```bash
# Commit .clinerules/ to your repo
git add .clinerules/
git commit -m "Add Cline rules for team"
```
## Troubleshooting
### Configuration Not Persisting
1. Check write permissions on `~/.cline/data/`
2. Ensure `CLINE_DIR` isn't set to a read-only location
3. Verify the config directory exists
### Environment Variables Not Working
1. Ensure variables are exported: `export CLINE_DIR=/path`
2. Check for typos in variable names
3. Verify JSON syntax for `CLINE_COMMAND_PERMISSIONS`
### Reset Configuration
To start fresh, remove the configuration directory:
```bash
rm -rf ~/.cline/data/
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>
<Card title="CLI Reference" icon="terminal" href="/cline-cli/cli-reference">
Complete command documentation with all flags and options.
</Card>
</Columns>
+239 -15
View File
@@ -1,50 +1,274 @@
---
title: "Installation"
description: "Install Cline CLI and authenticate with your account"
title: "Installation & Setup"
description: "Install Cline CLI on macOS, Linux, or Windows and configure your AI provider"
---
Cline CLI brings the full power of Cline to your terminal. 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 using Node.js 22 for the best experience.
Cline CLI requires **Node.js version 20 or higher**. We recommend Node.js 22 for the best experience.
To check your Node.js version:
Check your Node.js version:
```bash
node --version
```
## Installation
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
```
After installation, authenticate with your Cline account:
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 starts an authentication wizard to sign you in and configure your preferred AI model provider.
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
Get started with Cline in seconds:
Now you're ready to use Cline. Choose how you want to work:
### Interactive Mode
Launch the interactive CLI for development:
```bash
cline
```
That's it! Running `cline` in any directory starts an interactive session where you can chat with the AI agent. Type your task, review the plan, and type `/act` when ready to execute.
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
For even faster execution without interaction:
[Learn more about interactive mode →](/cline-cli/interactive-mode)
### Direct Task Execution
Run a task directly from your shell:
```bash
cline "Add unit tests to utils.js"
cline "Add error handling to utils.js"
```
This runs Cline with a single command, perfect for quick tasks or automation.
For non-interactive execution (perfect for scripts and CI/CD):
<Tip>
New to Cline CLI? Start with interactive mode (`cline`) to see how it works. Once comfortable, explore [the three core flows](/cline-cli/three-core-flows) for advanced usage patterns.
</Tip>
```bash
cline -y "Run tests and fix any failures"
```
[Learn more about CLI workflows →](/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
<Columns cols={2}>
<Card title="Interactive Mode" icon="terminal" href="/cline-cli/interactive-mode">
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>
<Card title="Configuration" icon="gear" href="/cline-cli/configuration">
Configure settings, rules, workflows, and environment variables.
</Card>
<Card title="CLI Reference" icon="book" href="/cline-cli/cli-reference">
Complete command documentation with all flags and options.
</Card>
</Columns>
+252
View File
@@ -0,0 +1,252 @@
---
title: "Interactive Mode"
description: "Master the interactive CLI with keyboard shortcuts, slash commands, and file mentions"
---
Interactive mode is the primary way to work with Cline CLI when you want a collaborative, conversational experience. Unlike headless mode (which runs a single task and exits), interactive mode keeps a session open where you can have back-and-forth conversations with Cline, refine your requests, and guide the AI as it works.
## Why Use Interactive Mode?
Interactive mode is ideal when you:
- **Don't know exactly what you need yet** - Explore a codebase, ask questions, and let Cline help you understand the architecture before making changes
- **Want to review before acting** - Toggle Plan mode to see Cline's strategy, then switch to Act mode when you're ready
- **Need iterative refinement** - Build on previous responses, ask follow-up questions, and guide Cline to the right solution
- **Prefer human oversight** - Review each action, approve file changes, and maintain control over what Cline does
- **Working on complex tasks** - Multi-step refactoring, debugging sessions, or feature development that requires judgment calls
For automated workflows, scripts, or CI/CD pipelines, see [headless mode](/cline-cli/overview#headless-mode-non-interactive) instead.
## Prerequisites
Before using interactive mode, you need to have Cline CLI installed and authenticated. If you haven't done this yet, follow the [Installation & Setup guide](/cline-cli/installation) first.
## Launching Interactive Mode
Start interactive mode by running `cline` without any arguments:
```bash
cline
```
You'll see an animated welcome screen with the Cline robot. Start typing your task in the input field at the bottom of the screen.
## Keyboard Shortcuts
Keyboard shortcuts are the primary way to navigate and control the interactive CLI. Since there's no mouse interaction in the terminal, learning these shortcuts will help you work efficiently and switch between modes, manage input, and control your session without breaking your flow.
### Mode Controls
| Shortcut | Action |
|----------|--------|
| `Tab` | Toggle between Plan and Act mode |
| `Shift+Tab` | Toggle auto-approve all actions |
| `Esc` | Exit or cancel current operation |
### Input Controls
| Shortcut | Action |
|----------|--------|
| `Enter` | Submit your message |
| `↑` / `↓` | Navigate message history |
| `Home` / `End` | Move cursor to start/end of line |
| `Ctrl+A` | Move cursor to beginning |
| `Ctrl+E` | Move cursor to end |
| `Ctrl+W` | Delete word before cursor |
| `Ctrl+U` | Delete entire line |
### Session Controls
| Shortcut | Action |
|----------|--------|
| `Ctrl+C` | Exit with session summary |
## File Mentions with @
Reference files from your workspace by typing `@` followed by the filename:
```
@src/utils.ts can you add error handling to this file?
```
As you type after `@`, Cline shows a fuzzy search dropdown of matching files. Use arrow keys to navigate and `Enter` to select.
<Tip>
File search uses ripgrep for fast, fuzzy matching. You can type partial paths like `@utils` to find `src/utils/helpers.ts`.
</Tip>
### Multiple File Mentions
Include multiple files in a single message:
```
Compare @src/old-api.ts with @src/new-api.ts and list the breaking changes
```
## Slash Commands
Type `/` to see available commands. Slash commands provide quick access to settings, history, and workflows.
### Built-in Commands
| Command | Description |
|---------|-------------|
| `/settings` | Open the settings panel |
| `/models` | Quick model switching |
| `/history` | Browse and resume previous tasks |
| `/clear` | Start a fresh task (clears current conversation) |
| `/help` | Show help and available commands |
| `/exit` | Exit the CLI |
### 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:
```
/code-review
```
## Settings Panel
Access the settings panel with `/settings`. Navigate between tabs using arrow keys.
| Tab | Description | Settings |
|-----|-------------|----------|
| **API** | Configure your AI provider and model | Provider selection, model choice, extended thinking toggle, thinking budget |
| **Auto-approve** | Control which actions Cline can perform without prompting | Read files, write files, execute commands, browser actions, MCP tools |
| **Features** | Toggle Cline capabilities | Hooks, skills, auto-compact, sound notifications |
| **Account** | Manage your Cline account | View account status, sign in/out, manage subscription |
| **Other** | Additional preferences | Theme preferences, debug options |
## 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).
### Plan Mode
In Plan mode, Cline analyzes your request and creates a strategy before making changes. Use this when:
- Exploring a new codebase
- Working on complex refactoring
- You want to review the approach first
### Act Mode
In Act mode, Cline executes tasks directly. Use this when:
- You're confident in the task
- Making straightforward changes
- Running quick operations
<Tip>
Press `Tab` anytime to switch modes. Starting in Plan mode and switching to Act after reviewing is a common workflow.
</Tip>
## Auto-approve Toggle
Press `Shift+Tab` to toggle auto-approve for all actions. This removes the approval prompts that appear before each action, letting Cline work continuously without interruption.
### When to Enable Auto-approve
Auto-approve is useful when:
- **You trust the task** - Well-defined tasks where you're confident in the outcome
- **Speed matters** - Long-running tasks where constant approvals slow you down
- **You're watching anyway** - You can see Cline's work in real-time and can interrupt if needed
- **Iterating quickly** - Rapid prototyping where you want to see results fast
### What Gets Auto-approved
When enabled, these actions happen without prompting:
- File reads
- File writes
- Command execution
- Browser actions
- MCP tool calls
You can also configure granular auto-approve settings (e.g., auto-approve reads but not writes) via `/settings` → Auto-approve tab, or see the [Auto-approve documentation](/features/auto-approve) for more details.
<Warning>
Auto-approve gives Cline full autonomy. Use on a clean git branch so you can easily revert changes if needed. You can always press `Ctrl+C` to stop Cline immediately.
</Warning>
## Session Summary
When you exit with `Ctrl+C`, Cline displays a session summary showing:
- Tasks completed
- Files modified
- Commands executed
- Token usage
This helps you track what was accomplished during your session.
## Running Multiple Instances
By default, all CLI instances share the same settings and state. However, you may want to run isolated instances with separate configurations for scenarios like:
- **Different models for different tasks** - Use a fast, cheap model for quick questions in one terminal and a more capable model for complex refactoring in another
- **Separate work and personal projects** - Keep API keys, rules, and task history isolated between contexts
- **Testing configuration changes** - Experiment with new settings without affecting your main setup
- **Team vs. individual settings** - Use shared team configuration for work projects and personal preferences for side projects
To run isolated instances, use the `--config` flag with different directories:
```bash
# Work instance with team configuration
cline --config ~/.cline-work
# Personal instance with different model/provider
cline --config ~/.cline-personal
# Experimental instance for testing new settings
cline --config ~/.cline-test
```
Each config directory maintains its own provider settings, API keys, task history, and preferences.
<Tip>
Use terminal multiplexers like tmux or split terminals to run multiple Cline instances in parallel, each working on different parts of your project with different models or settings.
</Tip>
## Tips for Effective Usage
### Start with Context
Give Cline context about what you're working on:
```
I'm building a REST API with Express. The routes are in @src/routes/ and models in @src/models/. Help me add user authentication.
```
### Use Plan Mode for Exploration
When you're unsure about the best approach:
```
[Tab to Plan mode]
How should I structure the database schema for a multi-tenant SaaS app?
```
### Iterate with Follow-ups
The interactive CLI maintains conversation context. Build on previous messages:
```
> Add a login endpoint
[Cline creates the endpoint]
> Now add rate limiting to it
[Cline modifies the same endpoint]
> Add tests for both features
[Cline creates test files]
```
## 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>
<Card title="Configuration" icon="gear" href="/cline-cli/configuration">
Explore `cline config` and advanced configuration options.
</Card>
</Columns>
+201 -57
View File
@@ -1,84 +1,228 @@
---
title: "Overview"
description: "Install the CLI, run your first task, and learn to automate code reviews and integrate AI agents into your development workflow"
description: "Run Cline AI coding agents directly in your terminal with an interactive CLI or automated workflows"
---
<Warning>
**Preview Release - macOS and Linux Only**
Cline CLI is currently in preview and only available for macOS and Linux users. Windows support is coming soon.
</Warning>
## What is Cline CLI?
Cline CLI runs AI coding agents directly in your terminal. Pipe git diffs for automated code reviews in CI/CD, run multiple instances simultaneously for parallel development, or integrate Cline into your existing shell workflows.
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 tracks instances across your system and outputs in formats designed for both humans and scripts—JSON, plain text, or rich terminal output.
The CLI supports macOS, Linux, and Windows, and works with all the same AI providers as the VS Code extension.
<Tip>
Ready to get started? Check out the [installation guide](/cline-cli/installation) to install Cline CLI and run your first task.
</Tip>
## Supported Model Providers
## Two Ways to Use Cline CLI
Cline CLI supports multiple AI model providers, giving you flexibility in choosing the best model for your needs:
The CLI operates in two distinct modes, automatically selecting the appropriate one based on how you invoke it:
- **Anthropic**
- **OpenAI**
- **OpenAI Compatible**
- **OpenRouter**
- **X AI (Grok)**
- **AWS Bedrock**
- **Google Gemini**
- **Ollama**
- **Cerebras**
### Interactive Mode
During installation, you'll authenticate and configure your preferred provider using the `cline auth` command.
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.
## What you can build with this
**Automated code maintenance**
- Schedule daily runs to identify and fix linting issues across your codebase
- Create tasks that scan for security vulnerabilities and automatically patch them
- Build scripts that update deprecated dependencies and run tests
**Multi-instance development**
- Run separate Cline instances for frontend and backend simultaneously
- Spawn instances for different feature branches, each with isolated state
- Create parallel review processes for multiple PRs
**Custom workflows**
- Build shell scripts that combine Cline with git hooks for pre-commit analysis
- Create custom commands that pipe complex data structures through Cline for processing
- Integrate with your existing toolchain (jq, grep, awk) for sophisticated automation
**CI/CD integration**
- Add Cline to GitHub Actions for automatic code review on every PR
- Create GitLab pipelines that generate migration scripts from schema changes
- Build Jenkins jobs that use Cline to analyze test failures and suggest fixes
## Hooks integration
[Hooks](/features/hooks/index) let you inject custom logic into Cline's workflow to validate operations and enforce policies. You can enable hooks when running tasks from the command line:
**When it activates:** Running `cline` without arguments, or when stdin is a TTY (terminal).
```bash
# Enable hooks for a task
cline "What does this repo do?" -s hooks_enabled=true
# Configure hooks globally via CLI
cline config set hooks-enabled=true
cline
```
This allows you to integrate hooks into automated workflows, CI/CD pipelines, and headless task execution for consistent enforcement across all environments.
Key features:
## Learn more
- **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 CLI workflows →](/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 setup guide →](/cline-cli/installation#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 | - | ✓ |
## Learn More
<Columns cols={2}>
<Card title="Installation" icon="download" href="/cline-cli/installation">
Install Cline CLI and authenticate with your account to get started.
<Card title="Installation & Setup" icon="download" href="/cline-cli/installation">
Install Cline CLI and authenticate with your preferred provider.
</Card>
<Card title="Three Core Flows" icon="route" href="/cline-cli/three-core-flows">
Master the three ways to use Cline CLI: interactive mode, headless automation, and multi-instance parallelization.
<Card title="Interactive Mode" icon="terminal" href="/cline-cli/interactive-mode">
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>
<Card title="Configuration" icon="gear" href="/cline-cli/configuration">
Configure settings, rules, workflows, and environment variables.
</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>
<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>
</Columns>
+14 -29
View File
@@ -88,33 +88,24 @@ jobs:
if: steps.detect.outputs.hit == 'true'
uses: actions/checkout@v4
# Node v20 is needed for Cline CLI on GitHub Actions Linux
# Node v20+ is needed for Cline CLI on GitHub Actions Linux
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
node-version: '22'
cache: 'npm'
- name: Setup Cline CLI
- name: Install Cline CLI
if: steps.detect.outputs.hit == 'true'
run: |
# Install the Cline CLI
sudo npm install -g cline
run: npm install -g cline
- name: Create Cline Instance
- name: Configure Cline Authentication
if: steps.detect.outputs.hit == 'true'
env:
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
CLINE_DIR: ${{ runner.temp }}/cline
run: |
# Create instance and capture output
INSTANCE_OUTPUT=$(cline instance new 2>&1)
# Parse address from output (format: " Address: 127.0.0.1:36733")
CLINE_ADDRESS=$(echo "$INSTANCE_OUTPUT" | grep "Address:" | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}:[0-9]+')
echo "CLINE_ADDRESS=$CLINE_ADDRESS" >> $GITHUB_ENV
# Configure API key
cline config set open-router-api-key=$OPENROUTER_API_KEY --address $CLINE_ADDRESS -v
# Configure API key using the auth command
cline auth --provider openrouter --apikey "${{ secrets.OPENROUTER_API_KEY }}"
- name: Download analyze script
if: steps.detect.outputs.hit == 'true'
@@ -135,7 +126,7 @@ jobs:
run: |
set -euo pipefail
RESULT=$(./analyze-issue.sh "${ISSUE_URL}" "Analyze this issue. The user asked: ${COMMENT}" "$CLINE_ADDRESS")
RESULT=$(./analyze-issue.sh "${ISSUE_URL}" "Analyze this issue. The user asked: ${COMMENT}")
{
echo 'result<<EOF'
@@ -180,8 +171,7 @@ This tells the workflow where to download the analysis script from your reposito
</Warning>
The workflow will look for new or updated issues, check for `@cline` mentions, and then
start up an instance of the Cline CLI to dig into the issue, providing feedback
as a reply to the issue.
start up the Cline CLI to dig into the issue, providing feedback as a reply to the issue.
### 2. Configure API Keys
@@ -246,19 +236,16 @@ if [ -z "$1" ]; then
echo "Usage: $0 <github-issue-url> [prompt] [address]"
echo "Example: $0 https://github.com/owner/repo/issues/123"
echo "Example: $0 https://github.com/owner/repo/issues/123 'What is the root cause of this issue?'"
echo "Example: $0 https://github.com/owner/repo/issues/123 'What is the root cause of this issue?' 127.0.0.1:46529"
echo "Example: $0 https://github.com/owner/repo/issues/123 'What is the root cause of this issue?'"
exit 1
fi
# Gather the args
ISSUE_URL="$1"
PROMPT="${2:-What is the root cause of this issue?}"
if [ -n "$3" ]; then
ADDRESS="--address $3"
fi
# Ask Cline for its analysis, showing only the summary
cline -y "$PROMPT: $ISSUE_URL" --mode act $ADDRESS -F json | \
cline -y "$PROMPT: $ISSUE_URL" --mode act -F json | \
sed -n '/^{/,$p' | \
jq -r 'select(.say == "completion_result") | .text' | \
sed 's/\\n/\n/g'
@@ -311,12 +298,10 @@ The workflow (`cline-responder.yml`):
1. **Triggers** on issue comments (created or edited)
2. **Detects** `@cline` mentions (case-insensitive)
3. **Installs** Cline CLI globally using npm
4. **Creates** a Cline instance using `cline instance new`
5. **Configures** authentication using `cline config set open-router-api-key=...
--address ...`
4. **Configures** authentication using `cline config set open-router-api-key=...`
6. **Downloads** the reusable `analyze-issue.sh` script from the
`github-issue-rca` sample
7. **Runs** analysis with the instance address
7. **Runs** analysis in Cline CLI
8. **Posts** the analysis result as a comment
## Related Samples
+198
View File
@@ -0,0 +1,198 @@
---
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:
1. **Checks out** the code.
2. **Installs** Node.js and Cline CLI.
3. **Configures** authentication (e.g., Anthropic, OpenAI).
4. **Runs Cline** with a comprehensive system prompt to analyze the diff, context, and related issues using GitHub CLI (`gh`).
5. **Posts** a detailed review comment with inline code suggestions.
## Prerequisites
- **GitHub repository** with Actions enabled.
- **AI Provider API Key** (e.g., Anthropic, OpenRouter) added as a repository secret (e.g., `ANTHROPIC_API_KEY`).
- **GitHub Token** (automatically provided by Actions as `GITHUB_TOKEN`).
## Setup
### 1. Create the Workflow File
Create a file named `.github/workflows/cline-pr-review.yml` in your repository:
```yaml
name: Cline PR Code Review
on:
pull_request:
types: [opened, ready_for_review]
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:
if: |
(github.event_name == 'pull_request' && github.event.pull_request.draft == false) ||
github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
timeout-minutes: 60
permissions:
contents: read
pull-requests: write
issues: read
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: "npm"
- name: Install Cline CLI
run: npm install -g cline
- name: Configure Cline Authentication
# Replace 'anthropic' with your provider of choice (openai, openrouter, etc.)
# and ensure the corresponding secret is set in your repo settings.
run: |
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 }}
# Restrict Cline to only safe, read-only GitHub CLI commands
CLINE_COMMAND_PERMISSIONS: |
{
"allow": [
"gh pr diff *",
"gh pr view *",
"gh pr checks *",
"gh pr list *",
"gh issue list *",
"gh issue view *",
"git log *",
"gh pr comment ${{ 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: |
cline --yolo 'You are a GitHub PR reviewer for this 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
Use `gh` commands to fetch the PR diff, details, and checks.
```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}"'
```
## Deep code review
Analyze the code changes. Look for:
- Logic errors and edge cases
- Security vulnerabilities
- Performance issues
- adherence to patterns in the codebase
## Submit Review
Post a single comprehensive comment summarizing your review.
If you have specific code suggestions, use the GitHub API to post inline comments:
```bash
gh api repos/'"${GITHUB_REPO}"'/pulls/'"${PR_NUMBER}"'/reviews \
-X POST \
-f event="COMMENT" \
-f body="" \
-F comments='[{"path": "src/file.ts", "line": 10, "body": "Suggestion: ..."}]'
```
Start your main comment with "Reviewed by Cline".'
```
### 2. Configure Secrets
1. Go to your repository settings -> **Secrets and variables** -> **Actions**.
2. Add a **New repository secret**.
3. Name: `ANTHROPIC_API_KEY` (or match the key used in your workflow).
4. Value: Your actual API key.
## Key Components Explained
### Permissions
```yaml
permissions:
contents: read
pull-requests: write
issues: read
```
We grant `pull-requests: write` so Cline can post comments and inline reviews. `contents: read` ensures it can analyze the code but **cannot push changes directly**, providing a security boundary.
### Authentication
```bash
cline auth --provider anthropic --apikey "..."
```
The `auth` command configures Cline in the CI environment without interactive prompts. You can switch providers (e.g., `openai`, `openrouter`) by changing the flags.
### Autonomous Mode (`--yolo`)
```bash
cline --yolo '...'
```
The `--yolo` flag tells Cline to run autonomously, executing commands without waiting for user approval. This is essential for CI/CD workflows.
### Command Permissions
We explicitly restrict what commands Cline can run using `CLINE_COMMAND_PERMISSIONS`. This ensures Cline can only use `gh` and `git` commands relevant to reviewing, preventing any accidental or malicious system modifications.
## Customizing the Reviewer
The "System Prompt" passed to Cline in the final step is fully customizable. You can modify it to:
- Enforce specific style guides.
- Focus on security vs. performance.
- Ask for specific types of feedback (e.g., "Roast my code" vs. "Be gentle").
@@ -0,0 +1,227 @@
---
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
**Bias Reduction**
- Different models catch different issues in code reviews
- Cross-validate solutions with multiple AI perspectives
- Reduce blind spots from single-model thinking
**Specialization**
- Some models excel at code (Codex, DeepSeek)
- Others are better at documentation (GPT-4, Claude)
- Security analysis benefits from multiple viewpoints
## Pattern 1: CI/CD Code Review
See our production GitHub Actions workflow that uses Cline CLI for automated PR reviews: [cline-pr-review.yml](https://github.com/cline/cline/blob/main/.github/workflows/cline-pr-review.yml)
**Key capabilities demonstrated:**
- **Automated inline suggestions**: Creates GitHub suggestion blocks that authors can commit with one click
- **SME identification**: Analyzes git history to find subject matter experts for each file
- **Related issue discovery**: Searches for context from past issues and PRs
- **Security-first permissions**: Read-only codebase access, can only post reviews
- **Deep code analysis**: Understands intent, compares approaches, identifies edge cases
The workflow runs on every PR and provides maintainers with comprehensive context to make faster, more informed decisions.
## Pattern 2: Task Phase Optimization
Use different models for different phases of work. Route simple tasks to cheap models, complex reasoning to premium models.
### Example: Issue Analysis Pipeline
```bash
# Get latest issue content
ISSUE_CONTENT=$(gh issue view $(gh issue list -L 1 | awk '{print $1}'))
# Phase 1: Quick summary with cheap model
SUMMARY=$(echo "$ISSUE_CONTENT" | cline -y --config ~/.cline-haiku \
"summarize this issue in 2-3 sentences")
# Phase 2: Detailed plan with expensive model + thinking
PLAN=$(echo "$SUMMARY" | cline -y --thinking --config ~/.cline-opus \
"create detailed implementation plan with edge cases")
# Phase 3: Execute with mid-tier model
echo "$PLAN" | cline -y --config ~/.cline-sonnet \
"implement the plan from above"
```
<Note>
Each cline invocation needs to complete before passing output to the next phase. Use shell variables to store intermediate results rather than piping cline commands directly.
</Note>
**Cost impact:**
- Haiku: $0.80 per million input tokens
- Opus: $15 per million input tokens
- Sonnet: $3 per million input tokens
This pattern uses Opus only when needed for complex reasoning, saving ~10x on API costs compared to using Opus for everything.
### Setting Up Model Configs
Create separate configuration directories for each model:
```bash
# Create config directories
mkdir -p ~/.cline-haiku ~/.cline-sonnet ~/.cline-opus
# Configure each with different models
cline --config ~/.cline-haiku auth anthropic --modelid claude-haiku-4-20250514
cline --config ~/.cline-sonnet auth anthropic --modelid claude-sonnet-4-20250514
cline --config ~/.cline-opus auth anthropic --modelid claude-opus-4-5-20251101
# Or use different providers entirely
cline --config ~/.cline-gemini auth gemini --modelid gemini-2.0-flash-exp
cline --config ~/.cline-codex auth openai-codex --modelid gpt-5-latest
```
Now you can switch models per-task with `--config`:
```bash
cline --config ~/.cline-haiku "quick task"
cline --config ~/.cline-opus "complex reasoning task"
```
## Pattern 3: Multi-Model Review & Consensus
Get multiple AI perspectives on the same change, then synthesize their feedback.
### Example: Diff Review Pipeline
```bash
# Get the latest commit
DIFF=$(git show)
# Review 1: Gemini's perspective
echo "$DIFF" | cline -y --config ~/.cline-gemini \
"review this diff and write your analysis to gemini-review.md"
# Review 2: Codex's perspective
echo "$DIFF" | cline -y --config ~/.cline-codex \
"review this diff and write your analysis to codex-review.md"
# Review 3: Opus's perspective
echo "$DIFF" | cline -y --config ~/.cline-opus \
"review this diff and write your analysis to opus-review.md"
# Synthesize all reviews into a consensus
cat gemini-review.md codex-review.md opus-review.md | cline -y \
"summarize these 3 reviews and identify: 1) issues all models agree on, 2) issues only one model caught, 3) your final recommendation"
```
**Why this works:**
- **Redundancy**: Issues caught by all 3 models are high-confidence
- **Coverage**: Each model has blind spots; together they cover more ground
- **Prioritization**: Consensus issues should be fixed first
- **Learning**: See which model types catch which issue types
### Advanced: Parallel Reviews
Run reviews in parallel for faster feedback:
```bash
# Run all reviews simultaneously
git show | cline -y --config ~/.cline-gemini "review and save to gemini-review.md" &
git show | cline -y --config ~/.cline-codex "review and save to codex-review.md" &
git show | cline -y --config ~/.cline-opus "review and save to opus-review.md" &
# Wait for all to complete
wait
# Synthesize
cat *-review.md | cline -y "create consensus review"
```
<Note>
Parallel execution requires managing multiple Cline instances. See [Multi-instance workflows](/cline-cli/three-core-flows#3-multi-instance-run-parallel-agents) for details.
</Note>
## Extended Thinking for Complex Tasks
Use the `--thinking` flag when Cline needs to analyze multiple approaches:
```bash
# Without thinking: Fast but may miss nuances
cline -y "refactor this codebase"
# With thinking: Slower but more thorough
cline -y --thinking \
"refactor this codebase - consider: performance, maintainability, backward compatibility"
```
The `--thinking` flag allocates 1024 tokens for internal reasoning before Cline responds. Best for:
- Architectural decisions
- Security analysis
- Complex refactoring
- Multi-step planning
## Best Practices
1. **Profile your workload**: Track which tasks are simple vs. complex
2. **Match models to tasks**: Use fast models for summaries, powerful models for reasoning
3. **Automate switching**: Script model selection based on task type
4. **Monitor costs**: Different models have 10-100x price differences
5. **Validate important decisions**: Use multi-model consensus for critical changes
## Production Examples
### Cost-Optimized PR Review
```bash
# Haiku: Quick summary and issue identification
gh pr view $PR | cline -y --config ~/.cline-haiku \
"list all issues to fix, output as JSON"
# Opus with thinking: Deep analysis only if issues found
if [ -s issues.json ]; then
cline -y --thinking --config ~/.cline-opus \
"analyze these issues and recommend fixes"
fi
```
### Security-Focused Multi-Model Scan
```bash
# Different models have different security perspectives
git diff main | cline -y --config ~/.cline-gemini "security review" > gemini-sec.md &
git diff main | cline -y --config ~/.cline-opus "security review" > opus-sec.md &
git diff main | cline -y --config ~/.cline-codex "security review" > codex-sec.md &
wait
# High-priority: Issues all 3 models found
cat *-sec.md | cline -y "find security issues all 3 reviews mentioned"
```
## Related Documentation
<Columns cols={2}>
<Card title="CLI Reference" icon="terminal" href="/cline-cli/cli-reference">
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>
<Card title="Model Selection Guide" icon="brain" href="/core-features/model-selection-guide">
Compare models and choose the right one for your needs
</Card>
<Card title="CI/CD Integration" icon="github" href="/cline-cli/samples/github-integration">
Automate GitHub workflows with Cline CLI
</Card>
</Columns>
+24
View File
@@ -8,6 +8,22 @@ This section provides sample implementations that demonstrate various Cline CLI
## Available Samples
<CardGroup cols={1}>
<Card
title="Model Orchestration"
icon="layer-group"
href="/cline-cli/samples/model-orchestration"
>
Use multiple AI models strategically with --config and --thinking flags. Optimize costs by routing simple tasks to cheap models and complex reasoning to premium models. Includes patterns for CI/CD code review, task phase optimization, and multi-model consensus.
</Card>
<Card
title="Worktree Workflows"
icon="code-branch"
href="/cline-cli/samples/worktree-workflows"
>
Use Git worktrees with the --cwd flag to run parallel tasks, test different approaches, and pipe context between isolated environments. Includes patterns for parallel execution, cross-worktree piping, and combining with model orchestration.
</Card>
<Card
title="GitHub Root Cause Analysis"
icon="magnifying-glass-chart"
@@ -23,6 +39,14 @@ This section provides sample implementations that demonstrate various Cline CLI
>
Automatically respond to GitHub issues by mentioning @cline in comments. Uses Cline CLI in GitHub Actions to create an AI-powered issue assistant that analyzes and responds autonomously.
</Card>
<Card
title="GitHub PR Review (Actions)"
icon="code-pull-request"
href="/cline-cli/samples/github-pr-review"
>
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>
</CardGroup>
## Additional Resources
@@ -0,0 +1,275 @@
---
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>
New to Git worktrees? See our comprehensive [Worktrees guide](/features/worktrees) for the full concept explanation, VS Code integration, and best practices.
</Tip>
## Quick Worktree Setup
If you haven't used Git worktrees before, here's the essentials:
```bash
# Create a new worktree in ~/worktree-a on branch feature-a
git worktree add ~/worktree-a -b feature-a
# Create another worktree for a different feature
git worktree add ~/worktree-b -b feature-b
# List all worktrees
git worktree list
# Remove a worktree when done
git worktree remove ~/worktree-a
```
Each worktree is a separate folder with its own branch checked out. They all share the same Git history and `.git` directory, but have independent working directories.
## The `--cwd` Flag
The `-c, --cwd <path>` flag tells Cline to run in a specific directory without changing your current location:
```bash
# Run Cline in a different directory
cline --cwd ~/worktree-a -y "refactor the authentication code"
# Short form
cline -c ~/worktree-b -y "add unit tests"
```
This is the key to worktree workflows—you can run multiple Cline instances in different worktrees simultaneously from a single terminal.
## Pattern 1: Parallel Task Execution
Run different tasks in parallel across multiple worktrees. Each task works on a separate branch in complete isolation.
### Example: Parallel Feature Development
```bash
# Terminal 1: Update docs in worktree-a
cline --cwd ~/worktree-a -y "read the last 10 changes using git show and update our README with them" &
# Terminal 2: TypeScript migration in worktree-b
cline --cwd ~/worktree-b -y "update the index.js to use typescript" &
# Terminal 3: Refactoring in worktree-c
cline --cwd ~/worktree-c -y "refactor the cli/ folder to be more modular" &
# Wait for all to complete
wait
```
The `&` runs each command in the background, allowing all three to execute simultaneously.
### When to Use Parallel Execution
**Perfect for:**
- Multiple independent features
- Bulk refactoring across different modules
- Running tests in one worktree while developing in another
- Trying multiple approaches to the same problem
**Not ideal for:**
- Tasks that modify the same files (merge conflicts likely)
- Tasks that depend on each other's results
- When you need to monitor progress closely
## Pattern 2: Cross-Worktree Context Piping
Pipe output from one worktree as input to another. Use when a task in one worktree needs context from attempts in another worktree.
### Example: Learning from Failures
```bash
# Try approach A in worktree-a, capture only the failure summary
cline --cwd ~/worktree-a -y \
"edit the index.ts to be better and then npm run. if it fails, output ONLY the failure summary. nothing else but the failure summary" \
| cline --cwd ~/worktree-b -y \
"i've tried to edit the index.ts in a different worktree but it failed. use a different approach for this work tree"
```
**How it works:**
1. First Cline instance runs in `worktree-a`, attempts a change, tests it
2. If it fails, outputs just the failure summary
3. That summary is piped to a second Cline instance in `worktree-b`
4. Second instance sees the failure and tries a different approach
### When to Use Context Piping
**Perfect for:**
- A/B testing different solutions
- Learning from failed attempts
- Iterative refinement (try → analyze → try differently)
- Comparing outputs across approaches
**Not ideal for:**
- Simple tasks that don't need cross-context
- When both worktrees would succeed independently
- Real-time collaboration (use parallel execution instead)
## Combining with Other CLI Features
### Different Models Per Worktree
Use `--config` to run different models in different worktrees:
```bash
# Cheap model for simple docs update
cline --cwd ~/worktree-docs --config ~/.cline-haiku -y \
"update README with latest changes"
# Expensive model for complex refactoring
cline --cwd ~/worktree-refactor --config ~/.cline-opus --thinking -y \
"refactor authentication system for better security"
```
This optimizes costs while maintaining quality where it matters.
### Task Isolation
Keep long-running worktree sessions isolated by running each task against a different worktree path:
```bash
# Run tasks in dedicated worktrees
cline --cwd ~/worktree-a -y "long-running task"
cline --cwd ~/worktree-b -y "another task"
```
Each worktree has its own Git branch and working directory, so task history and changes stay separated without needing instance management.
### With YOLO Mode
The `-y` (YOLO) flag is essential for worktree workflows:
```bash
# Without -y: Opens interactive chat (blocks other tasks)
cline --cwd ~/worktree-a "refactor code"
# With -y: Runs autonomously (doesn't block)
cline --cwd ~/worktree-a -y "refactor code" &
```
For parallel execution, always use `-y` to avoid blocking on user approval.
## Real-World Workflow Example
Here's a complete workflow showing how these patterns work together:
```bash
# Setup: Create three worktrees
git worktree add ~/cline-worktrees/feature-auth -b feature/authentication
git worktree add ~/cline-worktrees/feature-api -b feature/api-endpoints
git worktree add ~/cline-worktrees/fix-tests -b fix/failing-tests
# Pattern 1: Run parallel independent tasks
cline -c ~/cline-worktrees/feature-auth -y --config ~/.cline-sonnet \
"implement JWT authentication" &
cline -c ~/cline-worktrees/feature-api -y --config ~/.cline-sonnet \
"create REST API endpoints for user management" &
cline -c ~/cline-worktrees/fix-tests -y --config ~/.cline-haiku \
"fix all failing unit tests" &
wait
echo "All parallel tasks complete!"
# Pattern 2: Use piping for iterative refinement
cline -c ~/cline-worktrees/feature-auth -y \
"test the authentication with curl. output only errors if any" \
| cline -c ~/cline-worktrees/feature-auth -y \
"fix the authentication issues described in the input"
# Merge successful changes back
cd ~/cline-worktrees/feature-auth
git checkout main
git merge feature/authentication
# Cleanup
git worktree remove ~/cline-worktrees/feature-auth
```
## Best Practices
<AccordionGroup>
<Accordion title="Worktree Organization">
- **Use a dedicated folder**: Create `~/cline-worktrees/` for all worktrees
- **Meaningful branch names**: Use `feature/`, `fix/`, `refactor/` prefixes
- **Clean up regularly**: Remove worktrees after merging branches
</Accordion>
<Accordion title="Task Isolation">
- **Independent features only**: Don't parallelize tasks that touch the same files
- **Test in isolation**: Each worktree should have its own test run
- **Separate configs**: Use `.worktreeinclude` to copy `node_modules` and build artifacts
</Accordion>
<Accordion title="Resource Management">
- **Monitor disk space**: Each worktree is a full checkout
- **Limit parallel tasks**: Running too many simultaneously can slow your system
- **Use background jobs wisely**: Track with `jobs` command, kill with `kill %1`, etc.
</Accordion>
<Accordion title="Error Handling">
- **Check exit codes**: Use `|| echo "Task failed"` to catch errors
- **Log outputs**: Redirect to files for debugging: `> worktree-a.log 2>&1`
- **Graceful cleanup**: Always remove worktrees after tasks complete
</Accordion>
</AccordionGroup>
## Troubleshooting
<AccordionGroup>
<Accordion title="&quot;Branch already checked out&quot; error">
Git doesn't allow the same branch in multiple worktrees. Solutions:
- Use different branch names for each worktree
- Remove the existing worktree first: `git worktree remove <path>`
</Accordion>
<Accordion title="Tasks not running in parallel">
Make sure you're using:
- `&` at the end of each command to background it
- `-y` flag so Cline doesn't wait for approval
- Different worktrees (not the same path)
</Accordion>
<Accordion title="Pipe not working as expected">
Verify:
- First command outputs to stdout (not stderr)
- Second command reads from stdin (use `--` separator if needed)
- Both commands use correct `--cwd` paths
</Accordion>
<Accordion title="Changes not appearing in worktree">
Check:
- You're in the right worktree: `git worktree list`
- Files aren't gitignored
- You committed/staged changes if needed
</Accordion>
</AccordionGroup>
## Related Documentation
<Columns cols={2}>
<Card title="Worktrees Overview" icon="code-branch" href="/features/worktrees">
Complete guide to Git worktrees, VS Code integration, and .worktreeinclude
</Card>
<Card title="Model Orchestration" icon="layer-group" href="/cline-cli/samples/model-orchestration">
Use different models strategically with --config and --thinking flags
</Card>
<Card title="CLI Reference" icon="terminal" href="/cline-cli/cli-reference">
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>
</Columns>
+219 -85
View File
@@ -1,158 +1,292 @@
---
title: "Three Core Flows"
description: "Learn the three ways to use Cline CLI: interactive mode, headless automation, and multi-instance parallelization"
title: "CLI Workflows"
description: "Learn the three ways to use Cline CLI: interactive mode, direct task execution, and automation"
---
Two concepts to understand:
Cline CLI supports three primary workflows, each optimized for different use cases. Choose the approach that best fits your needs.
**Task** - A single job for Cline to complete ("add tests to utils.js"). You describe what you want, Cline plans how to do it, then executes the plan. Tasks run on instances.
<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.
</Note>
**Instance** - An independent Cline workspace. Each instance runs one task at a time. Create multiple instances to run multiple tasks that work on different parts of your project in parallel.
## 1. Interactive Mode
## 1. Interactive mode: Plan first, then act
The interactive CLI provides the richest experience for interactive development.
Start here to see how Cline works. Interactive mode opens a chat session where you can review plans before execution.
### Getting Started
```bash
cline
```
Cline opens an interactive session in your current directory. Type your task as a message. Cline enters Plan mode and proposes a step-by-step strategy.
This launches an interactive session in your current directory. Type your task, and Cline will analyze and execute it.
Review or edit the plan in chat. When you're ready, switch to execution:
### Key Features
```bash
/act
**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
```
Cline executes the approved steps—reading files, writing code, running commands. You maintain control throughout the process.
**Session Summary** - When you exit with `Ctrl+C`, Cline displays a summary of your session including tasks completed, files modified, and token usage.
## 2. Headless single-shot: Complete a task without chat
### When to Use Interactive Mode
Use this for automation where you want a one-liner that just does the work.
- 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 instance new --default
cline task new -y "Generate unit tests for all Go files"
cline "Add unit tests to utils.js"
```
With the `-y` (YOLO) flag, Cline plans and executes autonomously without interactive chat. Perfect for CI, cron jobs, or scripts.
Cline analyzes your task, creates a plan, and executes it. You'll be prompted for approval at key decision points.
Examples:
### Piping Context
Pipe file contents or command output into Cline:
```bash
# Create a complete feature
cline task new -y "Create a REST API for user authentication"
# Explain a file
cat README.md | cline "Summarize this document"
# Generate documentation
cline task new -y "Add JSDoc comments to all functions in src/"
# Review git changes
git diff | cline "Review these changes and suggest improvements"
# Refactor code
cline task new -y "Convert all var declarations to const/let"
# Analyze command output
npm test 2>&1 | cline "Analyze these test failures and fix them"
```
Monitor your task with:
### Chaining Cline Commands
Pipe Cline's output into another Cline instance for creative workflows:
```bash
# View task status
cline task view
# Explain changes, then write a commit message
git diff | cline -y "explain these changes" | cline -y "write a commit message for this"
# Follow task progress in real-time
cline task view --follow
# 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"
```
Press Ctrl+C to exit the view.
### Including Images
<Note>
Run YOLO mode with care on a directory or a clean Git branch. You get speed in exchange for oversight, so be ready to revert if needed.
</Note>
## 3. Multi-instance: Run parallel agents
Multiple instances let you parallelize work on the same project without colliding contexts. Run frontend, backend, and infrastructure tasks simultaneously.
Create your first instance:
Attach images to your task:
```bash
cline instance new
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"
```
This returns an instance address you'll use to target tasks. Attach a task to this instance:
### Mode Selection
```bash
# Frontend work on first instance
cline task new -y "Build React components"
# 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"
```
Create a second instance and set it as default in one command:
### 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:
```bash
cline instance new --default
cline -y "Run the test suite and fix any failures"
```
Now you can create tasks without specifying the address—they automatically use the default instance:
In YOLO mode:
- All actions are auto-approved
- Output is plain text (non-interactive)
- Process exits automatically when complete
- 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.
</Warning>
### JSON Output
Use `--json` for machine-readable output:
```bash
# Backend work on the new default instance
cline task new -y "Implement API endpoints"
cline --json "List all TODO comments in the codebase" | jq '.text'
```
List all running instances:
JSON output follows the same format as task files in `~/.cline/data/tasks/<id>/ui_messages.json`.
**JSON Message Schema:**
| Field | Type | Description |
|-------|------|-------------|
| `type` | `"ask"` or `"say"` | Message category |
| `text` | `string` | Message content |
| `ts` | `number` | Unix timestamp (ms) |
| `reasoning` | `string` | (Optional) Model reasoning |
| `partial` | `boolean` | (Optional) Streaming flag |
### Timeout Control
Set a maximum execution time:
```bash
cline instances list
cline -y --timeout 600 "Run full test suite"
```
Stop all instances when done:
### Environment Variables
Control Cline behavior via environment variables:
**CLINE_DIR** - Custom configuration directory:
```bash
export CLINE_DIR=/path/to/config
cline -y "your task"
```
**CLINE_COMMAND_PERMISSIONS** - Restrict allowed commands:
```bash
export CLINE_COMMAND_PERMISSIONS='{"allow": ["npm *", "git *"], "deny": ["rm -rf *"]}'
cline -y "your task"
```
See [Configuration](/cline-cli/configuration#environment-variables) for full documentation.
### GitHub Actions Example
Automate PR reviews with Cline:
```yaml
name: AI Code Review
on:
pull_request:
types: [opened, synchronize]
jobs:
review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: '22'
- name: Install Cline
run: npm install -g cline
- name: Configure Cline
run: cline auth -p anthropic -k ${{ secrets.ANTHROPIC_API_KEY }}
- name: Review PR
run: |
git diff origin/main...HEAD | cline -y "Review this PR for:
- Potential bugs
- Security issues
- Performance concerns
- Code style violations
Provide a summary of findings."
```
### Shell Script Example
Create a code review script:
```bash
cline instances kill -a
#!/bin/bash
# review.sh - AI-powered code review
set -e
# Get the diff
DIFF=$(git diff HEAD~1)
if [ -z "$DIFF" ]; then
echo "No changes to review"
exit 0
fi
# Run Cline review
echo "$DIFF" | cline -y --json "Review this code diff for issues" | jq -r '.text'
```
<Tip>
Keep track of instance addresses returned by `cline instance new`. When scripting multiple agents, store these IDs and direct your tasks to the appropriate instance.
</Tip>
### When to Use Automation Mode
## Configuring context window for local providers
- CI/CD pipelines
- Scheduled maintenance tasks
- Batch processing
- Any workflow requiring non-interactive execution
For Ollama and LM Studio, you can configure the model context window via CLI:
## Choosing the Right Flow
```bash
# For Ollama
cline config s ollama-api-options-ctx-num=32768
| 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 |
# For 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-configurable—Cline uses each model's built-in context limits automatically.
## Choosing the right flow
- **Interactive mode**: Best for exploring new problems, learning how Cline works, or when you want to review plans before execution
- **Headless single-shot**: Perfect for automation, CI/CD, and tasks where you trust Cline to execute without supervision
- **Multi-instance**: Use when you need to parallelize work or maintain separate contexts for different parts of your project
<Tip>
For in-depth commands and flags, check out the [CLI reference](/cline-cli/cli-reference) page for complete documentation on all available options.
</Tip>
## Next steps
## Next Steps
<Columns cols={2}>
<Card title="CLI reference" icon="terminal" href="/cline-cli/cli-reference">
Complete command documentation including configuration, instance management, and task commands.
<Card title="Interactive Mode" icon="terminal" href="/cline-cli/interactive-mode">
Master keyboard shortcuts, slash commands, and file mentions.
</Card>
<Card title="Plan and Act" icon="brain" href="/features/plan-and-act">
Deep dive into Plan and Act modes, including when to use each and how to switch between them.
<Card title="CLI Reference" icon="book" href="/cline-cli/cli-reference">
Complete command documentation with all flags and options.
</Card>
<Card title="YOLO mode" icon="zap" href="/features/yolo-mode">
Understand how YOLO mode works and when to use full automation versus manual approval.
<Card title="Configuration" icon="gear" href="/cline-cli/configuration">
Environment variables, rules, and advanced settings.
</Card>
<Card title="Task management" icon="clipboard-check" href="/features/tasks/task-management">
Learn how Cline tracks and manages tasks, including saving and restoring state from checkpoints.
<Card title="YOLO Mode" icon="zap" href="/features/yolo-mode">
Deep dive into autonomous execution and safety considerations.
</Card>
</Columns>
+11 -1
View File
@@ -87,13 +87,19 @@
"pages": [
"cline-cli/overview",
"cline-cli/installation",
"cline-cli/interactive-mode",
"cline-cli/configuration",
"cline-cli/three-core-flows",
"cline-cli/acp-editor-integrations",
{
"group": "CLI Samples",
"pages": [
"cline-cli/samples/overview",
"cline-cli/samples/model-orchestration",
"cline-cli/samples/worktree-workflows",
"cline-cli/samples/github-issue-rca",
"cline-cli/samples/github-integration"
"cline-cli/samples/github-integration",
"cline-cli/samples/github-pr-review"
]
},
"cline-cli/cli-reference"
@@ -438,6 +444,10 @@
{
"source": "/features/conditional-rules",
"destination": "/features/cline-rules/conditional-rules"
},
{
"source": "/cline-cli/authentication",
"destination": "/cline-cli/installation"
}
],
"search": {
+18 -14
View File
@@ -5,13 +5,6 @@ sidebarTitle: "Worktrees"
Worktrees let you work on multiple branches simultaneously, each in its own folder. This enables Cline to work on tasks in parallel across separate VS Code windows, or lets Cline work independently while you continue coding in your main workspace.
<Frame>
<img
src="https://storage.googleapis.com/cline_public_images/docs/assets/worktrees-overview.png"
alt="Worktrees view showing multiple linked worktrees"
/>
</Frame>
## What Are Git Worktrees?
A Git worktree is a linked copy of your repository in a separate folder, checked out to a specific branch. All worktrees share the same Git history and `.git` directory, but each has its own working directory with different code checked out.
@@ -123,13 +116,6 @@ When you're done working in a worktree and ready to merge your changes back to t
3. Choose whether to delete the worktree after merging
4. Click **Merge**
<Frame>
<img
src="https://storage.googleapis.com/cline_public_images/docs/assets/worktrees-merge.png"
alt="Merge worktree modal"
/>
</Frame>
#### Handling Merge Conflicts
If your branch has conflicts with the main branch, Cline will detect them and show you the conflicting files. You have two options:
@@ -226,6 +212,24 @@ Worktrees are not available in certain workspace configurations:
The Worktrees view will display a message explaining the limitation if either of these applies to your workspace.
## Using Worktrees with Cline CLI
Cline CLI's `--cwd` flag unlocks powerful command-line worktree workflows:
- **Parallel execution**: Run multiple Cline instances simultaneously in different worktrees
- **Context piping**: Pipe output from one worktree as input to another for iterative refinement
- **Combined with other features**: Use with `--config` for different models per worktree, or `--thinking` for deep analysis
Example:
```bash
# Run parallel tasks in different worktrees
cline --cwd ~/worktree-a -y "refactor authentication" &
cline --cwd ~/worktree-b -y "add unit tests" &
wait
```
For complete CLI worktree patterns and examples, see [Worktree Workflows](/cline-cli/samples/worktree-workflows).
## Troubleshooting
<AccordionGroup>
+342
View File
@@ -0,0 +1,342 @@
# Implementation Plan: Cline CLI Documentation Update
[Overview]
Update the Cline CLI documentation to reflect the new CLI 2.0 architecture that removes instances, adds a rich TUI experience, and introduces streamlined authentication options.
The Cline CLI 2.0 has undergone significant changes. The previous architecture used explicit instance management (`cline instance new`, `cline instance list`, etc.) which has been completely removed. The new architecture simplifies the user experience:
1. **TUI Mode**: Running `cline` without arguments launches a full-featured terminal UI built with React Ink, featuring an animated robot, file mentions (@), slash commands (/), session summaries, and inline settings panels. This provides a "Claude Code-like" experience.
2. **CLI Mode**: Running `cline "prompt"` executes tasks directly. With `--yolo` flag, it runs non-interactively with output to stdout, making it ideal for CI/CD, piping, and bash scripts.
3. **Authentication**: Multiple options including Cline account OAuth, ChatGPT subscription OAuth (via Codex), import from existing CLI tools (Codex CLI, OpenCode), and BYO API keys. Supports all providers from the VS Code extension (superset).
The documentation must clearly separate these two user journeys (TUI interactive vs CLI automation) while documenting deprecated features for users migrating from older versions.
**Note:** The CLI is now generally available (no longer preview) and supports macOS, Linux, and Windows.
[Types]
No code type changes required - this is a documentation-only update.
This implementation plan only covers documentation files (`.mdx` files in `docs/cline-cli/`). No TypeScript interfaces, types, or code modifications are needed.
[Files]
Update existing files and create new documentation pages for comprehensive coverage.
**Files to UPDATE (in-place):**
- `docs/cline-cli/overview.mdx` - Remove instance references, reframe around TUI vs CLI modes
- `docs/cline-cli/installation.mdx` - Expand with prerequisites, post-install steps, authentication
- `docs/cline-cli/three-core-flows.mdx` - Complete rewrite to remove instances, replace with TUI/CLI/Automation flows
- `docs/cline-cli/cli-reference.mdx` - Replace outdated man page content with current man page from `cli/man/cline.1.md`
**Files to CREATE:**
- `docs/cline-cli/tui-guide.mdx` - New comprehensive guide for the TUI experience
- `docs/cline-cli/authentication.mdx` - New guide covering all auth options
- `docs/cline-cli/configuration.mdx` - New guide for `cline config` and settings management
**Files to MODIFY:**
- `docs/docs.json` - Add new pages to navigation under CLI group
[Functions]
No function changes required - documentation only.
This is a documentation update with no code changes to functions, methods, or handlers.
[Classes]
No class changes required - documentation only.
This is a documentation update with no code changes to classes or components.
[Dependencies]
No dependency changes required.
This is a documentation update with no package changes.
[Testing]
Documentation should be verified for accuracy by cross-referencing with source code.
**Verification steps:**
1. Cross-reference all documented features against `cli/src/index.ts` entry point
2. Verify keyboard shortcuts against `cli/src/components/ChatView.tsx`
3. Verify auth options against `cli/src/components/AuthView.tsx`
4. Verify slash commands against `cli/src/components/HelpPanelContent.tsx`
5. Verify config options against `cli/src/components/ConfigView.tsx` and `SettingsPanelContent.tsx`
6. Verify import sources against `cli/src/utils/import-configs.ts`
7. Run `npm run docs:dev` (if available) to preview documentation locally
**Content accuracy checks:**
- [ ] All keyboard shortcuts match source code
- [ ] All command flags match `cli/src/index.ts`
- [ ] Auth provider list matches `AuthView.tsx`
- [ ] Import sources correctly documented (Codex CLI, OpenCode - NOT "Claude Code")
- [ ] Deprecated features clearly marked
[Implementation Order]
Execute documentation updates in dependency order to ensure consistency.
1. **Update `docs/docs.json`** - Add new page entries to navigation first so links work
2. **Create `docs/cline-cli/authentication.mdx`** - Auth is foundational, other docs reference it
3. **Create `docs/cline-cli/tui-guide.mdx`** - Core new content for TUI users
4. **Create `docs/cline-cli/configuration.mdx`** - Config management guide
5. **Update `docs/cline-cli/overview.mdx`** - Reframe overview with new architecture
6. **Update `docs/cline-cli/installation.mdx`** - Expand installation guide
7. **Update `docs/cline-cli/three-core-flows.mdx`** - Rewrite as TUI/CLI/Automation flows
8. **Update `docs/cline-cli/cli-reference.mdx`** - Replace with current man page content
9. **Verify all cross-references and links work correctly**
---
## Detailed File Specifications
### 1. `docs/docs.json` (UPDATE)
Add new pages to the CLI navigation group:
```json
{
"group": "CLI",
"pages": [
"cline-cli/overview",
"cline-cli/installation",
"cline-cli/authentication",
"cline-cli/tui-guide",
"cline-cli/configuration",
"cline-cli/three-core-flows",
{
"group": "CLI Samples",
"pages": [
"cline-cli/samples/overview",
"cline-cli/samples/github-issue-rca",
"cline-cli/samples/github-integration"
]
},
"cline-cli/cli-reference"
]
}
```
### 2. `docs/cline-cli/authentication.mdx` (CREATE)
**Purpose:** Comprehensive guide to all authentication options
**Sections:**
- Quick start (sign in with Cline - recommended)
- Sign in with ChatGPT subscription (OpenAI Codex OAuth)
- Import from existing CLI tools:
- Import from Codex CLI (`~/.codex/auth.json`)
- Import from OpenCode (`~/.local/share/opencode/auth.json`)
- Bring your own API keys (manual provider configuration)
- Supported providers list with examples
- Switching providers (`cline auth`)
- Quick setup flags (`cline auth -p <provider> -k <key> -m <model>`)
**Key corrections from user input:**
- User said "import from Claude Code" - INCORRECT. Actual sources are:
- Codex CLI (OpenAI's CLI tool)
- OpenCode
- Document the actual import sources from `cli/src/utils/import-configs.ts`
### 3. `docs/cline-cli/tui-guide.mdx` (CREATE)
**Purpose:** Guide to the interactive terminal UI experience
**Sections:**
- Launching the TUI (`cline` without arguments)
- The welcome screen and robot animation
- Input field and message display
- Keyboard shortcuts:
- `Tab` - Toggle Plan/Act mode
- `Shift+Tab` - Toggle auto-approve all
- `Enter` - Submit message
- `Esc` - Exit/cancel
- `↑/↓` - Navigate history
- `Home/End` - Cursor movement
- `Ctrl+A/E/W/U` - Text editing
- File mentions with `@`:
- Type `@` to search workspace files
- Uses ripgrep for fast searching
- Slash commands with `/`:
- `/settings` - Open settings panel
- `/models` - Quick model switching
- `/history` - Browse task history
- `/clear` - Start fresh task
- `/help` - Show help
- `/exit` - Exit CLI
- Workflow commands
- Settings panel (`/settings`):
- API tab (provider, model, thinking)
- Auto-approve tab
- Features tab
- Account tab
- Other tab
- Session summary on exit
- Running multiple instances with `--config`:
- Default: settings shared across all instances
- Use `cline --config /path/to/config` for isolated configs
- Recommend tmux/terminal multiplexing for parallel work
### 4. `docs/cline-cli/configuration.mdx` (CREATE)
**Purpose:** Guide to `cline config` command and settings management
**Sections:**
- Running `cline config`
- Configuration tabs:
- Settings (global state, workspace state)
- Rules (`.clinerules` files, Cursor rules, Windsurf rules)
- Workflows
- Hooks (if enabled)
- Skills (if enabled)
- Keyboard navigation in config view
- Editing configuration values
- Configuration directory structure (`~/.cline/data/`)
- Environment variables (`CLINE_DIR`, `CLINE_COMMAND_PERMISSIONS`)
- Using `--config` flag for separate configurations
### 5. `docs/cline-cli/overview.mdx` (UPDATE)
**Changes:**
- Remove all references to instances (`cline instance new/list/kill`)
- Reframe around two modes: TUI (interactive) and CLI (automation)
- Update "What you can build" section to remove multi-instance examples
- Add section about new TUI features
- Link to new authentication and TUI guide pages
- Note deprecation of instance commands
**New structure:**
1. What is Cline CLI?
2. Two ways to use Cline CLI:
- TUI Mode (interactive development)
- CLI Mode (automation and scripting)
3. Supported Model Providers
4. What you can build
5. Learn more (links)
### 6. `docs/cline-cli/installation.mdx` (UPDATE)
**Changes:**
- Remove "Preview Release - macOS and Linux Only" warning (CLI is now GA and supports Windows)
- Add note that CLI supports macOS, Linux, and Windows
- Add Node.js version requirement (20+, recommend 22)
- Add version specification (`npm install -g cline@2.0.0`)
- Add more detail on post-install authentication
- Link to new authentication guide
- Add troubleshooting tips
- Add verification steps
**New structure:**
1. Prerequisites (Node.js version)
2. Installation: `npm install -g cline` (or `npm install -g cline@2.0.0`)
3. Authentication (`cline auth` - link to auth guide)
4. Quick Start (two paths: TUI and CLI)
5. Next Steps (links to guides)
### 7. `docs/cline-cli/three-core-flows.mdx` (UPDATE - Major Rewrite)
**Complete rewrite removing all instance references.**
**New title suggestion:** "CLI Workflows" or "Getting Started Workflows"
**New structure:**
1. **Interactive TUI Mode** (replaces old "Interactive mode")
- Launch with `cline`
- Plan/Act mode toggle (Tab key)
- Using slash commands and file mentions
- Auto-approve toggle (Shift+Tab)
- Session summary on exit (Ctrl+C)
2. **Direct Task Execution** (replaces old "Headless single-shot")
- `cline "prompt"` syntax
- Piping context (`cat file | cline "explain"`)
- Piping cline into cline: `git diff | cline -y "explain" | cline -y "write poem"`
- Image attachments
3. **Automation & CI/CD** (replaces old "Multi-instance")
- `--yolo` / `-y` flag for non-interactive mode (also called "yes mode")
- `--json` output for parsing (same format as `~/.cline/data/tasks/<id>/ui_messages.json`)
- `--timeout` for long-running tasks
- Environment variables:
- `CLINE_DIR` - custom config directory
- `CLINE_COMMAND_PERMISSIONS` - restrict allowed shell commands
- Example GitHub Actions workflow for PR review
**Creative use cases from engineer demo:**
- Chain cline commands: `git diff | cline -y "explain" | cline -y "write a poem about this"`
- GitHub PR review workflow with `gh` CLI integration
**Deprecation notice:**
Add a callout at the top noting that instance commands (`cline instance new/list/kill`) have been removed in favor of the simpler architecture.
### 8. `docs/cline-cli/cli-reference.mdx` (UPDATE)
**Changes:**
- Replace the outdated embedded man page with content from `cli/man/cline.1.md`
- The current man page in the docs references old instance commands
- The actual man page (`cli/man/cline.1.md`) has correct, updated content
- Convert man page markdown format to mdx documentation format
- Add JSON output schema section
- Add environment variables section
- Remove all instance command references
---
---
## Additional Features from Engineer Demo
### Man Page
- `man cline` - View in-depth documentation in terminal
### Dev Tools
- `cline dev log` - Opens log file for debugging
- `cline update` - Check for and install updates
### JSON Output Format
- Same format as saved task files: `~/.cline/data/tasks/<id>/ui_messages.json`
- Useful for programmatic use cases
- Pipe through `jq` for easier parsing
- Example: `cline --json "prompt" | jq '.text'`
---
## Verification Checklist
After implementation, verify these user requirements are documented:
- [x] New TUI experience explained
- [x] NPM installation covered
- [x] Authorization options:
- [x] Sign in with Cline
- [x] Sign in with ChatGPT Subscription (Codex OAuth)
- [x] Import from Codex CLI (CORRECTED from "Claude Code")
- [x] Import from OpenCode
- [x] Bring your own API keys
- [x] Bedrock support mentioned
- [x] `cline auth` for changing providers
- [x] Basic CLI usage:
- [x] `cline "task"` syntax
- [x] Piping context
- [x] `--yolo` / `-y` for CI/CD (also called "yes mode")
- [x] TUI features:
- [x] `cline` alone launches TUI
- [x] Tab to toggle Plan/Act mode
- [x] Shift+Tab for auto-approve all
- [x] Session summary on exit (Ctrl+C)
- [x] `--config` for separate configs
- [x] Instance deprecation noted
- [x] `cline config` for rules, workflows, hooks, skills
- [x] @ file mentions with autocomplete (fuzzy search)
- [x] / slash commands with autocomplete
- [x] `/settings` documented
- [x] `/models` documented
- [x] `/history` documented
- [x] Workflows generate slash commands
- [x] /settings panel sections documented (arrow keys to navigate tabs)
- [x] Environment variables:
- [x] `CLINE_DIR` documented
- [x] `CLINE_COMMAND_PERMISSIONS` documented (security measure)
- [x] Dev tools:
- [x] `cline dev log` documented
- [x] `cline update` documented
- [x] `man cline` documented
- [x] JSON output format documented
- [x] Piping cline into cline documented
- [x] GitHub Actions PR review example included
+6323 -4324
View File
File diff suppressed because it is too large Load Diff
+8 -7
View File
@@ -2,7 +2,7 @@
"name": "claude-dev",
"displayName": "Cline",
"description": "Autonomous coding agent right in your IDE, capable of creating/editing files, running commands, using the browser, and more with your permission every step of the way.",
"version": "3.56.2",
"version": "3.57.1",
"icon": "assets/icons/icon.png",
"workspaces": [
"cli"
@@ -393,7 +393,7 @@
"postcompile-standalone": "node scripts/package-standalone.mjs",
"postcompile-standalone-npm": "node scripts/package-npm.mjs",
"dev": "npm run protos && npm run watch",
"watch": "npm-run-all -p watch:*",
"watch": "npx npm-run-all -p watch:*",
"watch:esbuild": "node esbuild.mjs --watch",
"watch:tsc": "tsc --noEmit --watch --project tsconfig.json",
"package": "npm run check-types && npm run build:webview && npm run lint && node esbuild.mjs --production",
@@ -406,16 +406,16 @@
"clean:all": "npm run clean:build && npm run clean:deps",
"compile-tests": "node ./scripts/build-tests.js",
"watch-tests": "tsc -p . -w --outDir out",
"check-types": "npm run protos && npx tsc --noEmit && cd webview-ui && npx tsc --noEmit",
"check-types": "npm run protos && npx tsc --noEmit && cd webview-ui && npx tsc --noEmit && cd ../cli && npx tsc --noEmit",
"lint": "biome lint --no-errors-on-unmatched --files-ignore-unknown=true --diagnostic-level=error && npm run lint:proto",
"lint:proto": "bash ./scripts/proto-lint.sh",
"format": "biome format --changed --no-errors-on-unmatched --files-ignore-unknown=true --diagnostic-level=error",
"format:fix": "biome check --changed --no-errors-on-unmatched --files-ignore-unknown=true --write",
"fix:all": "biome check --no-errors-on-unmatched --files-ignore-unknown=true --write --diagnostic-level=error --unsafe",
"ci:check-all": "npm-run-all -p check-types lint format",
"ci:check-all": "npx npm-run-all -p check-types lint format",
"ci:build": "npm run protos && npm run build:webview && node esbuild.mjs && npm run compile-tests",
"pretest": "npm run compile && npm run compile-tests && npm run compile-standalone && npm run lint",
"test": "npm-run-all test:unit test:integration",
"test": "npx npm-run-all test:unit test:integration",
"test:integration": "vscode-test",
"test:unit": "cross-env TS_NODE_PROJECT=./tsconfig.unit-test.json mocha",
"test:coverage": "vscode-test --coverage",
@@ -426,7 +426,7 @@
"test:e2e": "playwright install && npm run test:e2e:build && node src/test/e2e/utils/build.mjs && playwright test",
"test:e2e:optimal": "npm run test:e2e:build && node src/test/e2e/utils/build.mjs && playwright test",
"test:e2e:ui": "npx tsx scripts/interactive-playwright.ts",
"install:all": "npm install && cd webview-ui && npm install && cd ../cli && npm install && cd ..",
"install:all": "npm install && cd webview-ui && npm install",
"dev:webview": "cd webview-ui && npm run dev",
"build:webview": "cd webview-ui && npm run build",
"test:webview": "cd webview-ui && npm run test",
@@ -453,7 +453,7 @@
]
},
"devDependencies": {
"@biomejs/biome": "^2.1.4",
"@biomejs/biome": "^2.3.14",
"@bufbuild/buf": "^1.54.0",
"@changesets/cli": "^2.27.12",
"@types/better-sqlite3": "^7.6.13",
@@ -465,6 +465,7 @@
"@types/mocha": "^10.0.7",
"@types/node": "20.x",
"@types/pdf-parse": "^1.1.4",
"@types/picomatch": "^4.0.2",
"@types/proxyquire": "^1.3.31",
"@types/shell-quote": "^1.7.5",
"@types/should": "^11.2.0",
+2
View File
@@ -41,6 +41,8 @@ service AccountService {
rpc openrouterAuthClicked(EmptyRequest) returns (Empty);
rpc requestyAuthClicked(StringRequest) returns (Empty);
rpc hicapAuthClicked(EmptyRequest) returns (Empty);
// Returns a link the webview can use to redirect back to the user's IDE.
rpc getRedirectUrl(EmptyRequest) returns (String);
+5
View File
@@ -36,6 +36,11 @@ service EnvService {
// Logs a debug message to the host environment's log/output console.
rpc debugLog(cline.StringRequest) returns (cline.Empty);
// Opens an external URL in the default browser.
// In remote environments (VS Code Server, SSH, etc.), this routes the URL
// to the user's local machine to open in their local browser.
rpc openExternal(cline.StringRequest) returns (cline.Empty);
}
message GetHostVersionResponse {
+1 -1
View File
@@ -130,7 +130,7 @@ async function generateVscodeProtobusServers(protobusServices) {
// Create output file
const output = `// GENERATED CODE -- DO NOT EDIT!
// Generated by ${SCRIPT_NAME}
import * as serviceTypes from "src/generated/hosts/vscode/protobus-service-types"
import * as serviceTypes from "@generated/hosts/vscode/protobus-service-types"
${imports.join("\n")}
${servers.join("\n")}
+8 -39
View File
@@ -27,7 +27,7 @@ async function main() {
await cleanBuildDir()
await buildTypeScriptCli()
await copyCliDist()
await createNpmPackageJson()
await copyPackageJson()
await copyReadme()
await createNpmIgnoreFile()
@@ -90,45 +90,14 @@ async function copyCliDist() {
}
/**
* Create package.json for NPM publication
* Reads from cli/package.json and modifies for publication
* Copy package.json from cli/ directory
*/
async function createNpmPackageJson() {
console.log("Creating NPM package.json...")
const sourcePackageJson = path.join(CLI_DIR, "package.json")
if (!fs.existsSync(sourcePackageJson)) {
console.error(`Error: package.json not found at ${sourcePackageJson}`)
process.exit(1)
}
const pkg = JSON.parse(fs.readFileSync(sourcePackageJson, "utf8"))
// Modify for NPM publication
const npmPkg = {
name: "cline", // Change from @cline/cli to cline for NPM
version: pkg.version,
description: pkg.description,
main: pkg.main,
bin: pkg.bin,
type: pkg.type,
engines: pkg.engines,
keywords: pkg.keywords,
author: pkg.author,
license: pkg.license,
repository: pkg.repository,
homepage: pkg.homepage,
bugs: pkg.bugs,
dependencies: pkg.dependencies,
os: ["darwin", "linux"],
cpu: ["x64", "arm64"],
}
const destPackageJson = path.join(BUILD_DIR, "package.json")
fs.writeFileSync(destPackageJson, JSON.stringify(npmPkg, null, "\t"))
console.log(`✓ package.json created (name: cline, version: ${pkg.version})`)
async function copyPackageJson() {
console.log("Copying package.json...")
const source = path.join(CLI_DIR, "package.json")
const dest = path.join(BUILD_DIR, "package.json")
await cpr(source, dest)
console.log(`✓ package.json copied`)
}
/**
+1
View File
@@ -259,6 +259,7 @@ function createHandlerForProvider(
return new ClineHandler({
onRetryAttempt: options.onRetryAttempt,
clineAccountId: options.clineAccountId,
clineApiKey: options.clineApiKey,
ulid: options.ulid,
reasoningEffort: mode === "plan" ? options.planModeReasoningEffort : options.actModeReasoningEffort,
thinkingBudgetTokens:
+7 -5
View File
@@ -8,15 +8,15 @@ import {
InvokeModelWithResponseStreamCommand,
} from "@aws-sdk/client-bedrock-runtime"
import { fromNodeProviderChain } from "@aws-sdk/credential-providers"
import { BedrockModelId, bedrockDefaultModelId, bedrockModels, CLAUDE_SONNET_1M_SUFFIX, ModelInfo } from "@shared/api"
import { type BedrockModelId, bedrockDefaultModelId, bedrockModels, CLAUDE_SONNET_1M_SUFFIX, type ModelInfo } from "@shared/api"
import { calculateApiCostOpenAI, calculateApiCostQwen } from "@utils/cost"
import { ExtensionRegistryInfo } from "@/registry"
import { ClineStorageMessage } from "@/shared/messages/content"
import type { ClineStorageMessage } from "@/shared/messages/content"
import { Logger } from "@/shared/services/Logger"
import { ApiHandler, CommonApiHandlerOptions } from "../"
import type { ApiHandler, CommonApiHandlerOptions } from "../"
import { withRetry } from "../retry"
import { convertToR1Format } from "../transform/r1-format"
import { ApiStream } from "../transform/stream"
import type { ApiStream } from "../transform/stream"
export interface AwsBedrockHandlerOptions extends CommonApiHandlerOptions {
apiModelId?: string
@@ -110,9 +110,11 @@ interface ProviderChainOptions {
profile?: string
}
// a special jp inference profile was created for sonnet 4.5 & haiku 4.5
// a special jp inference profile was created for opus 4.6, sonnet 4.5 & haiku 4.5
// https://docs.aws.amazon.com/bedrock/latest/userguide/inference-profiles-support.html
const JP_SUPPORTED_CRIS_MODELS = [
"anthropic.claude-opus-4-6-v1",
"anthropic.claude-opus-4-6-v1:1m",
"anthropic.claude-sonnet-4-5-20250929-v1:0",
"anthropic.claude-sonnet-4-5-20250929-v1:0:1m",
"anthropic.claude-haiku-4-5-20251001-v1:0",
+14 -14
View File
@@ -1,4 +1,4 @@
import { ModelInfo, openRouterDefaultModelId, openRouterDefaultModelInfo } from "@shared/api"
import { type ModelInfo, openRouterDefaultModelId, openRouterDefaultModelInfo } from "@shared/api"
import { shouldSkipReasoningForModel } from "@utils/model-utils"
import axios from "axios"
import OpenAI from "openai"
@@ -8,15 +8,15 @@ import { ClineAccountService } from "@/services/account/ClineAccountService"
import { AuthService } from "@/services/auth/AuthService"
import { buildClineExtraHeaders } from "@/services/EnvUtils"
import { CLINE_ACCOUNT_AUTH_ERROR_MESSAGE } from "@/shared/ClineAccount"
import { ClineStorageMessage } from "@/shared/messages/content"
import type { ClineStorageMessage } from "@/shared/messages/content"
import { fetch, getAxiosSettings } from "@/shared/net"
import { Logger } from "@/shared/services/Logger"
import { ApiHandler, CommonApiHandlerOptions } from "../"
import type { ApiHandler, CommonApiHandlerOptions } from "../"
import { withRetry } from "../retry"
import { createOpenRouterStream } from "../transform/openrouter-stream"
import { ApiStream, ApiStreamUsageChunk } from "../transform/stream"
import type { ApiStream, ApiStreamUsageChunk } from "../transform/stream"
import { ToolCallProcessor } from "../transform/tool-call-processor"
import { OpenRouterErrorResponse } from "./types"
import type { OpenRouterErrorResponse } from "./types"
interface ClineHandlerOptions extends CommonApiHandlerOptions {
ulid?: string
@@ -27,6 +27,7 @@ interface ClineHandlerOptions extends CommonApiHandlerOptions {
openRouterModelId?: string
openRouterModelInfo?: ModelInfo
clineAccountId?: string
clineApiKey?: string
geminiThinkingLevel?: string
}
@@ -48,7 +49,7 @@ export class ClineHandler implements ApiHandler {
}
private async ensureClient(): Promise<OpenAI> {
const clineAccountAuthToken = await this._authService.getAuthToken()
const clineAccountAuthToken = this.options.clineApiKey || (await this._authService.getAuthToken())
if (!clineAccountAuthToken) {
throw new Error(CLINE_ACCOUNT_AUTH_ERROR_MESSAGE)
}
@@ -108,7 +109,7 @@ export class ClineHandler implements ApiHandler {
this.lastGenerationId = undefined
this.lastRequestId = undefined
let didOutputUsage: boolean = false
let didOutputUsage = false
const stream = await createOpenRouterStream(
client,
@@ -148,11 +149,8 @@ export class ClineHandler implements ApiHandler {
const error = choiceWithError.error
Logger.error(`Cline Mid-Stream Error: ${error.code || error.type || "Unknown"} - ${error.message}`)
throw new Error(`Cline Mid-Stream Error: ${error.code || error.type || "Unknown"} - ${error.message}`)
} else {
throw new Error(
"Cline Mid-Stream Error: Stream terminated with error status but no error details provided",
)
}
throw new Error("Cline Mid-Stream Error: Stream terminated with error status but no error details provided")
}
const delta = choice?.delta
@@ -187,7 +185,7 @@ export class ClineHandler implements ApiHandler {
if (
"reasoning_details" in delta &&
delta.reasoning_details &&
// @ts-ignore-next-line
// @ts-expect-error-next-line
delta?.reasoning_details?.length && // exists and non-0
!shouldSkipReasoningForModel(this.options.openRouterModelId)
) {
@@ -199,10 +197,12 @@ export class ClineHandler implements ApiHandler {
}
if (!didOutputUsage && chunk.usage) {
// @ts-ignore-next-line
// @ts-expect-error-next-line
let totalCost = (chunk.usage.cost || 0) + (chunk.usage.cost_details?.upstream_inference_cost || 0)
const modelId = this.getModel().id
const isFreeModel = ["kwaipilot/kat-coder-pro", "moonshotai/kimi-k2.5"].includes(modelId)
const isFreeModel = ["kwaipilot/kat-coder-pro", "moonshotai/kimi-k2.5", "minimax/minimax-m2.1"].includes(
modelId,
)
if (isFreeModel) {
totalCost = 0
+1
View File
@@ -77,6 +77,7 @@ export class RequestyHandler implements ApiHandler {
? { thinking: { type: "enabled", budget_tokens: thinkingBudget } }
: { thinking: { type: "disabled" } }
const thinkingArgs =
model.id.includes("claude-opus-4-6") ||
model.id.includes("claude-3-7-sonnet") ||
model.id.includes("claude-sonnet-4") ||
model.id.includes("claude-opus-4") ||
+10 -3
View File
@@ -5,6 +5,7 @@ import {
OPENROUTER_PROVIDER_PREFERENCES,
openRouterClaudeSonnet41mModelId,
openRouterClaudeSonnet451mModelId,
openRouterClaudeOpus461mModelId,
} from "@shared/api"
import { shouldSkipReasoningForModel } from "@utils/model-utils"
import OpenAI from "openai"
@@ -30,8 +31,11 @@ export async function createOpenRouterStream(
...convertToOpenAiMessages(messages),
]
const isClaudeSonnet1m = model.id === openRouterClaudeSonnet41mModelId || model.id === openRouterClaudeSonnet451mModelId
if (isClaudeSonnet1m) {
const isClaude1m =
model.id === openRouterClaudeSonnet41mModelId ||
model.id === openRouterClaudeSonnet451mModelId ||
model.id === openRouterClaudeOpus461mModelId
if (isClaude1m) {
// remove the custom :1m suffix, to create the model id openrouter API expects
model.id = model.id.slice(0, -CLAUDE_SONNET_1M_SUFFIX.length)
}
@@ -43,6 +47,7 @@ export async function createOpenRouterStream(
// this was initially specifically for claude models (some models may 'support prompt caching' automatically without this)
// handles direct model.id match logic
switch (model.id) {
case "anthropic/claude-opus-4.6":
case "anthropic/claude-haiku-4.5":
case "anthropic/claude-4.5-haiku":
case "anthropic/claude-sonnet-4.5":
@@ -110,6 +115,7 @@ export async function createOpenRouterStream(
// (models usually default to max tokens allowed)
let maxTokens: number | undefined
switch (model.id) {
case "anthropic/claude-opus-4.6":
case "anthropic/claude-haiku-4.5":
case "anthropic/claude-4.5-haiku":
case "anthropic/claude-sonnet-4.5":
@@ -155,6 +161,7 @@ export async function createOpenRouterStream(
let reasoning: { max_tokens: number } | undefined
switch (model.id) {
case "anthropic/claude-opus-4.6":
case "anthropic/claude-haiku-4.5":
case "anthropic/claude-4.5-haiku":
case "anthropic/claude-sonnet-4.5":
@@ -210,7 +217,7 @@ export async function createOpenRouterStream(
...(reasoning ? { reasoning } : {}),
...(openRouterProviderSorting && !providerPreferences ? { provider: { sort: openRouterProviderSorting } } : {}),
...(providerPreferences ? { provider: providerPreferences } : {}),
...(isClaudeSonnet1m ? { provider: { order: ["anthropic", "google-vertex/global"], allow_fallbacks: false } } : {}),
...(isClaude1m ? { provider: { order: ["anthropic", "google-vertex/global"], allow_fallbacks: false } } : {}),
...getOpenAIToolParams(tools),
...(model.id.includes("gemini-3") && geminiThinkingLevel
? { thinking_config: { thinking_level: geminiThinkingLevel, include_thoughts: true } }
@@ -4,6 +4,7 @@ import {
ModelInfo,
openRouterClaudeSonnet41mModelId,
openRouterClaudeSonnet451mModelId,
openRouterClaudeOpus461mModelId,
} from "@shared/api"
import { shouldSkipReasoningForModel } from "@utils/model-utils"
import OpenAI from "openai"
@@ -28,8 +29,11 @@ export async function createVercelAIGatewayStream(
...convertToOpenAiMessages(messages),
]
const isClaudeSonnet1m = model.id === openRouterClaudeSonnet41mModelId || model.id === openRouterClaudeSonnet451mModelId
if (isClaudeSonnet1m) {
const isClaude1m =
model.id === openRouterClaudeSonnet41mModelId ||
model.id === openRouterClaudeSonnet451mModelId ||
model.id === openRouterClaudeOpus461mModelId
if (isClaude1m) {
// remove the custom :1m suffix, to create the model id the API expects
model.id = model.id.slice(0, -CLAUDE_SONNET_1M_SUFFIX.length)
}
@@ -0,0 +1,16 @@
import { Empty, EmptyRequest } from "@shared/proto/cline/common"
import { HostProvider } from "@/hosts/host-provider"
import { openExternal } from "@/utils/env"
import { Controller } from ".."
/**
* Initiates Hicap auth
*/
export async function hicapAuthClicked(_: Controller, __: EmptyRequest): Promise<Empty> {
const callbackUri = await HostProvider.get().getCallbackUrl()
const authUri = `https://dashboard.hicap.ai/setup?application=cline&callback_url=${callbackUri}/hicap`
await openExternal(authUri)
return {}
}
+25 -1
View File
@@ -23,9 +23,9 @@ import fs from "fs/promises"
import open from "open"
import pWaitFor from "p-wait-for"
import * as path from "path"
import type { FolderLockWithRetryResult } from "src/core/locks/types"
import type * as vscode from "vscode"
import { ClineEnv } from "@/config"
import type { FolderLockWithRetryResult } from "@/core/locks/types"
import { HostProvider } from "@/hosts/host-provider"
import { ExtensionRegistryInfo } from "@/registry"
import { AuthService } from "@/services/auth/AuthService"
@@ -759,6 +759,30 @@ export class Controller {
return undefined
}
// Hicap
async handleHicapCallback(code: string) {
const apiKey: string = code
const hicap: ApiProvider = "hicap"
const currentMode = this.stateManager.getGlobalSettingsKey("mode")
// Update API configuration through cache service
const currentApiConfiguration = this.stateManager.getApiConfiguration()
const updatedConfig = {
...currentApiConfiguration,
planModeApiProvider: hicap,
actModeApiProvider: hicap,
hicapApiKey: apiKey,
}
this.stateManager.setApiConfiguration(updatedConfig)
await this.postStateToWebview()
this.accountService
if (this.task) {
this.task.api = buildApiHandler({ ...updatedConfig, ulid: this.task.ulid }, currentMode)
}
}
// Task history
async getTaskWithId(id: string): Promise<{
@@ -5,17 +5,46 @@ import { ANTHROPIC_MAX_THINKING_BUDGET, ModelInfo } from "@shared/api"
import { fileExistsAtPath } from "@utils/fs"
import { parsePrice } from "@utils/model-utils"
import axios from "axios"
import { StateManager } from "@/core/storage/StateManager"
import { getAxiosSettings } from "@/shared/net"
import { Logger } from "@/shared/services/Logger"
import { basetenModels } from "../../../shared/api"
import { Controller } from ".."
// Track pending refresh promise to prevent duplicate concurrent fetches
let pendingRefresh: Promise<Record<string, ModelInfo>> | null = null
/**
* Core function: Refreshes the Baseten models and returns application types
* @param controller The controller instance
* @returns Record of model ID to ModelInfo (application types)
*/
export async function refreshBasetenModels(controller: Controller): Promise<Record<string, ModelInfo>> {
// Check in-memory cache first
const cache = StateManager.get().getModelsCache("baseten")
if (cache) {
return cache
}
// If a fetch is already in progress, return the same promise
if (pendingRefresh) {
return pendingRefresh
}
// Start new fetch and track the promise
pendingRefresh = (async () => {
try {
return await fetchAndCacheModels(controller)
} finally {
// Clear pending promise when done (success or error)
pendingRefresh = null
}
})()
return pendingRefresh
}
async function fetchAndCacheModels(controller: Controller): Promise<Record<string, ModelInfo>> {
const basetenModelsFilePath = path.join(await ensureCacheDirectoryExists(), GlobalFileNames.basetenModels)
// Get the Baseten API key from the controller's state
@@ -153,6 +182,9 @@ export async function refreshBasetenModels(controller: Controller): Promise<Reco
}
}
// Store in StateManager's in-memory cache
StateManager.get().setModelsCache("baseten", typedModels)
return typedModels
}
@@ -4,18 +4,47 @@ import { fileExistsAtPath } from "@utils/fs"
import axios from "axios"
import fs from "fs/promises"
import path from "path"
import { StateManager } from "@/core/storage/StateManager"
import { telemetryService } from "@/services/telemetry"
import { getAxiosSettings } from "@/shared/net"
import { Logger } from "@/shared/services/Logger"
import { groqModels } from "../../../shared/api"
import { Controller } from ".."
// Track pending refresh promise to prevent duplicate concurrent fetches
let pendingRefresh: Promise<Record<string, ModelInfo>> | null = null
/**
* Core function: Refreshes the Groq models and returns application types
* @param controller The controller instance
* @returns Record of model ID to ModelInfo (application types)
*/
export async function refreshGroqModels(controller: Controller): Promise<Record<string, ModelInfo>> {
// Check in-memory cache first
const cache = StateManager.get().getModelsCache("groq")
if (cache) {
return cache
}
// If a fetch is already in progress, return the same promise
if (pendingRefresh) {
return pendingRefresh
}
// Start new fetch and track the promise
pendingRefresh = (async () => {
try {
return await fetchAndCacheModels(controller)
} finally {
// Clear pending promise when done (success or error)
pendingRefresh = null
}
})()
return pendingRefresh
}
async function fetchAndCacheModels(controller: Controller): Promise<Record<string, ModelInfo>> {
const groqModelsFilePath = path.join(await ensureCacheDirectoryExists(), GlobalFileNames.groqModels)
const groqApiKey = controller.stateManager.getSecretKey("groqApiKey")
@@ -83,11 +112,12 @@ export async function refreshGroqModels(controller: Controller): Promise<Record<
models[rawModel.id] = modelInfo
}
await fs.writeFile(groqModelsFilePath, JSON.stringify(models))
Logger.log("Groq models fetched and saved", models)
} else {
Logger.error("Invalid response from Groq API")
}
await fs.writeFile(groqModelsFilePath, JSON.stringify(models))
Logger.log("Groq models fetched and saved", models)
}
} catch (error) {
Logger.error("Error fetching Groq models:", error)
@@ -159,6 +189,9 @@ export async function refreshGroqModels(controller: Controller): Promise<Record<
}
}
// Store in StateManager's in-memory cache
StateManager.get().setModelsCache("groq", typedModels)
return typedModels
}
@@ -55,8 +55,10 @@ export async function refreshLiteLlmModels(): Promise<Record<string, ModelInfo>>
// Use litellm_params.model as the key since that's the actual model ID users select
// model_name may not include the region prefix (e.g., "us." for Bedrock models)
const modelId = rawModel.litellm_params?.model || rawModel.model_name
models[modelId] = modelInfo
if (rawModel.litellm_params?.model) {
models[rawModel.litellm_params?.model] = modelInfo
}
models[rawModel.model_name] = modelInfo
}
}
} catch (error) {
@@ -7,7 +7,9 @@ import path from "path"
import { StateManager } from "@/core/storage/StateManager"
import {
ANTHROPIC_MAX_THINKING_BUDGET,
CLAUDE_OPUS_1M_TIERS,
CLAUDE_SONNET_1M_TIERS,
openRouterClaudeOpus461mModelId,
openRouterClaudeSonnet41mModelId,
openRouterClaudeSonnet451mModelId,
} from "@/shared/api"
@@ -72,12 +74,40 @@ interface OpenRouterRawModelInfo {
supported_parameters?: OpenRouterSupportedParams[] | null
}
// Track pending refresh promise to prevent duplicate concurrent fetches
let pendingRefresh: Promise<Record<string, ModelInfo>> | null = null
/**
* Core function: Refreshes the OpenRouter models and returns application types
* @param controller The controller instance
* @returns Record of model ID to ModelInfo (application types)
*/
export async function refreshOpenRouterModels(controller: Controller): Promise<Record<string, ModelInfo>> {
// Check in-memory cache first
const cache = StateManager.get().getModelsCache("openRouter")
if (cache) {
return cache
}
// If a fetch is already in progress, return the same promise
if (pendingRefresh) {
return pendingRefresh
}
// Start new fetch and track the promise
pendingRefresh = (async () => {
try {
return await fetchAndCacheModels(controller)
} finally {
// Clear pending promise when done (success or error)
pendingRefresh = null
}
})()
return pendingRefresh
}
async function fetchAndCacheModels(controller: Controller): Promise<Record<string, ModelInfo>> {
const openRouterModelsFilePath = path.join(await ensureCacheDirectoryExists(), GlobalFileNames.openRouterModels)
let models: Record<string, ModelInfo> = {}
@@ -135,6 +165,12 @@ export async function refreshOpenRouterModels(controller: Controller): Promise<R
modelInfo.cacheWritesPrice = 3.75
modelInfo.cacheReadsPrice = 0.3
break
case "anthropic/claude-opus-4.6":
modelInfo.contextWindow = 200_000 // restrict to 200k, 1m variant created below
modelInfo.supportsPromptCache = true
modelInfo.cacheWritesPrice = 6.25
modelInfo.cacheReadsPrice = 0.5
break
case "anthropic/claude-opus-4.5":
modelInfo.supportsPromptCache = true
modelInfo.cacheWritesPrice = 6.25
@@ -227,7 +263,7 @@ export async function refreshOpenRouterModels(controller: Controller): Promise<R
models[rawModel.id] = modelInfo
// add custom :1m model variant
// add custom :1m model variant for sonnet
if (rawModel.id === "anthropic/claude-sonnet-4" || rawModel.id === "anthropic/claude-sonnet-4.5") {
const claudeSonnet1mModelInfo = cloneDeep(modelInfo)
claudeSonnet1mModelInfo.contextWindow = 1_000_000 // limiting providers to those that support 1m context window
@@ -237,12 +273,21 @@ export async function refreshOpenRouterModels(controller: Controller): Promise<R
// sonnet 4.5
models[openRouterClaudeSonnet451mModelId] = claudeSonnet1mModelInfo
}
// add custom :1m model variant for opus 4.6
if (rawModel.id === "anthropic/claude-opus-4.6") {
const claudeOpus1mModelInfo = cloneDeep(modelInfo)
claudeOpus1mModelInfo.contextWindow = 1_000_000
claudeOpus1mModelInfo.tiers = CLAUDE_OPUS_1M_TIERS
models[openRouterClaudeOpus461mModelId] = claudeOpus1mModelInfo
}
}
// Save models and cache them in memory
await fs.writeFile(openRouterModelsFilePath, JSON.stringify(models))
Logger.log("OpenRouter models fetched and saved")
} else {
Logger.error("Invalid response from OpenRouter API")
throw new Error("Invalid response data when fetching OpenRouter models")
}
await fs.writeFile(openRouterModelsFilePath, JSON.stringify(models))
Logger.log("OpenRouter models fetched and saved")
} catch (error) {
Logger.error("Error fetching OpenRouter models:", error)
@@ -4,6 +4,7 @@ import { fileExistsAtPath } from "@utils/fs"
import axios from "axios"
import fs from "fs/promises"
import path from "path"
import { StateManager } from "@/core/storage/StateManager"
import { getAxiosSettings } from "@/shared/net"
import { Logger } from "@/shared/services/Logger"
import { Controller } from ".."
@@ -75,12 +76,40 @@ function deriveTemperature(modelId: string): number | undefined {
return undefined
}
// Track pending refresh promise to prevent duplicate concurrent fetches
let pendingRefresh: Promise<Record<string, ModelInfo>> | null = null
/**
* Core function: Refreshes Vercel AI Gateway models and returns application types
* @param _controller The controller instance (unused)
* @returns Record of model ID to ModelInfo (application types)
*/
export async function refreshVercelAiGatewayModels(_controller: Controller): Promise<Record<string, ModelInfo>> {
// Check in-memory cache first
const cache = StateManager.get().getModelsCache("vercel")
if (cache) {
return cache
}
// If a fetch is already in progress, return the same promise
if (pendingRefresh) {
return pendingRefresh
}
// Start new fetch and track the promise
pendingRefresh = (async () => {
try {
return await fetchAndCacheModels()
} finally {
// Clear pending promise when done (success or error)
pendingRefresh = null
}
})()
return pendingRefresh
}
async function fetchAndCacheModels(): Promise<Record<string, ModelInfo>> {
const vercelAiGatewayModelsFilePath = path.join(await ensureCacheDirectoryExists(), GlobalFileNames.vercelAiGatewayModels)
let models: Record<string, ModelInfo> = {}
@@ -122,7 +151,7 @@ export async function refreshVercelAiGatewayModels(_controller: Controller): Pro
await fs.writeFile(vercelAiGatewayModelsFilePath, JSON.stringify(models))
Logger.log("Vercel AI Gateway models fetched and saved")
} else {
Logger.error("Invalid response from Vercel AI Gateway API")
throw new Error("Invalid response from Vercel AI Gateway API")
}
} catch (error) {
Logger.error("Error fetching Vercel AI Gateway models:", error)
@@ -134,6 +163,9 @@ export async function refreshVercelAiGatewayModels(_controller: Controller): Pro
}
}
// Store in StateManager's in-memory cache
StateManager.get().setModelsCache("vercel", models)
return models
}
+59 -12
View File
@@ -62,18 +62,22 @@ export class StateManager {
private context: ExtensionContext
private isInitialized = false
// Cache TTL: 1 hour - long enough to prevent duplicate fetches, short enough to see new models
private readonly MODEL_CACHE_TTL_MS = 60 * 60 * 1000
// In-memory model info cache (not persisted to disk)
// These are for dynamic providers that fetch models from APIs
private modelInfoCache: {
openRouterModels: Record<string, ModelInfo> | null
groqModels: Record<string, ModelInfo> | null
basetenModels: Record<string, ModelInfo> | null
huggingFaceModels: Record<string, ModelInfo> | null
requestyModels: Record<string, ModelInfo> | null
huaweiCloudMaasModels: Record<string, ModelInfo> | null
hicapModels: Record<string, ModelInfo> | null
aihubmixModels: Record<string, ModelInfo> | null
liteLlmModels: Record<string, ModelInfo> | null
openRouterModels: { data: Record<string, ModelInfo>; timestamp: number } | null
groqModels: { data: Record<string, ModelInfo>; timestamp: number } | null
basetenModels: { data: Record<string, ModelInfo>; timestamp: number } | null
huggingFaceModels: { data: Record<string, ModelInfo>; timestamp: number } | null
requestyModels: { data: Record<string, ModelInfo>; timestamp: number } | null
huaweiCloudMaasModels: { data: Record<string, ModelInfo>; timestamp: number } | null
hicapModels: { data: Record<string, ModelInfo>; timestamp: number } | null
aihubmixModels: { data: Record<string, ModelInfo>; timestamp: number } | null
liteLlmModels: { data: Record<string, ModelInfo>; timestamp: number } | null
vercelModels: { data: Record<string, ModelInfo>; timestamp: number } | null
} = {
openRouterModels: null,
groqModels: null,
@@ -84,6 +88,7 @@ export class StateManager {
hicapModels: null,
aihubmixModels: null,
liteLlmModels: null,
vercelModels: null,
}
// Debounced persistence state
@@ -417,11 +422,41 @@ export class StateManager {
| "huaweiCloudMaas"
| "hicap"
| "aihubmix"
| "liteLlm",
| "liteLlm"
| "vercel",
models: Record<string, ModelInfo>,
): void {
const cacheKey = `${provider}Models` as keyof typeof this.modelInfoCache
this.modelInfoCache[cacheKey] = models
this.modelInfoCache[cacheKey] = { data: models, timestamp: Date.now() }
}
getModelsCache(
provider:
| "openRouter"
| "groq"
| "baseten"
| "huggingFace"
| "requesty"
| "huaweiCloudMaas"
| "hicap"
| "aihubmix"
| "liteLlm"
| "vercel",
): Record<string, ModelInfo> | null {
const cacheKey = `${provider}Models` as keyof typeof this.modelInfoCache
const cached = this.modelInfoCache[cacheKey]
if (!cached) {
return null
}
// Check if cache has expired
if (Date.now() - cached.timestamp > this.MODEL_CACHE_TTL_MS) {
this.modelInfoCache[cacheKey] = null
return null
}
return cached.data
}
/**
@@ -441,7 +476,19 @@ export class StateManager {
modelId: string,
): ModelInfo | undefined {
const cacheKey = `${provider}Models` as keyof typeof this.modelInfoCache
return this.modelInfoCache[cacheKey]?.[modelId]
const cached = this.modelInfoCache[cacheKey]
if (!cached) {
return undefined
}
// Check if cache has expired
if (Date.now() - cached.timestamp > this.MODEL_CACHE_TTL_MS) {
this.modelInfoCache[cacheKey] = null
return undefined
}
return cached.data[modelId]
}
/**
+34 -16
View File
@@ -1,5 +1,5 @@
import { synchronizeRemoteRuleToggles } from "@core/context/instructions/user-instructions/rule-helpers"
import { RemoteConfig } from "@shared/remote-config/schema"
import type { RemoteConfig, S3AccessKeySettings } from "@shared/remote-config/schema"
import { ConfiguredAPIKeys, GlobalStateAndSettings, RemoteConfigFields } from "@shared/storage/state-keys"
import { AuthService } from "@/services/auth/AuthService"
import { getDistinctId } from "@/services/logging/distinctId"
@@ -12,10 +12,29 @@ import { ApiProvider } from "@/shared/api"
import { isOpenTelemetryConfigValid, remoteConfigToOtelConfig } from "@/shared/services/config/otel-config"
import { Logger } from "@/shared/services/Logger"
import { syncWorker } from "@/shared/services/worker/sync"
import { BlobStoreSettings } from "@/shared/storage"
import { ensureSettingsDirectoryExists } from "../disk"
import { StateManager } from "../StateManager"
import { syncRemoteMcpServersToSettings } from "./syncRemoteMcpServers"
function accessSettingsToBlobStorage(type: BlobStoreSettings["adapterType"], settings: S3AccessKeySettings): BlobStoreSettings {
return {
adapterType: type,
accessKeyId: settings.accessKeyId,
secretAccessKey: settings.secretAccessKey,
region: settings.region,
bucket: settings.bucket,
endpoint: settings.endpoint,
accountId: settings.accountId,
intervalMs: settings.intervalMs,
maxRetries: settings.maxRetries,
batchSize: settings.batchSize,
maxQueueSize: settings.maxQueueSize,
maxFailedAgeMs: settings.maxFailedAgeMs,
backfillEnabled: settings.backfillEnabled,
}
}
/**
* Transforms RemoteConfig schema to RemoteConfigFields shape
* @param remoteConfig The remote configuration object
@@ -182,6 +201,17 @@ export function transformRemoteConfigToStateShape(remoteConfig: RemoteConfig): P
}
}
const anthropicSettings = remoteConfig.providerSettings?.Anthropic
if (anthropicSettings) {
transformed.planModeApiProvider = "anthropic"
transformed.actModeApiProvider = "anthropic"
providers.push("anthropic")
if (anthropicSettings.baseUrl) {
transformed.anthropicBaseUrl = anthropicSettings.baseUrl
}
}
// This line needs to stay here, it is order dependent on the above code checking the configured providers
if (providers.length > 0) {
transformed.remoteConfiguredProviders = providers
@@ -198,21 +228,9 @@ export function transformRemoteConfigToStateShape(remoteConfig: RemoteConfig): P
if (remoteConfig.enterpriseTelemetry?.promptUploading) {
const promptUplaoding = remoteConfig.enterpriseTelemetry.promptUploading
if (promptUplaoding.type === "s3_access_keys" && promptUplaoding.s3AccessSettings) {
transformed.blobStoreConfig = {
adapterType: "s3",
accessKeyId: promptUplaoding.s3AccessSettings.accessKeyId,
secretAccessKey: promptUplaoding.s3AccessSettings.secretAccessKey,
region: promptUplaoding.s3AccessSettings.region,
bucket: promptUplaoding.s3AccessSettings.bucket,
endpoint: promptUplaoding.s3AccessSettings.endpoint,
accountId: promptUplaoding.s3AccessSettings.accountId,
intervalMs: promptUplaoding.s3AccessSettings.intervalMs,
maxRetries: promptUplaoding.s3AccessSettings.maxRetries,
batchSize: promptUplaoding.s3AccessSettings.batchSize,
maxQueueSize: promptUplaoding.s3AccessSettings.maxQueueSize,
maxFailedAgeMs: promptUplaoding.s3AccessSettings.maxFailedAgeMs,
backfillEnabled: promptUplaoding.s3AccessSettings.backfillEnabled,
}
transformed.blobStoreConfig = accessSettingsToBlobStorage("s3", promptUplaoding.s3AccessSettings)
} else if (promptUplaoding.type === "r2_access_keys" && promptUplaoding.r2AccessSettings) {
transformed.blobStoreConfig = accessSettingsToBlobStorage("r2", promptUplaoding.r2AccessSettings)
}
}
+12
View File
@@ -4,6 +4,7 @@ import * as vscode from "vscode"
import { HistoryItem } from "@/shared/HistoryItem"
import { Logger } from "@/shared/services/Logger"
import { ensureRulesDirectoryExists, readTaskHistoryFromState, writeTaskHistoryToState } from "./disk"
import { StateManager } from "./StateManager"
export async function migrateWorkspaceToGlobalStorage(context: vscode.ExtensionContext) {
// Keys to migrate from workspace storage back to global storage
@@ -663,3 +664,14 @@ export async function cleanupMcpMarketplaceCatalogFromGlobalState(context: vscod
// Continue execution - cleanup failure shouldn't break extension startup
}
}
export function cleanupOldApiKey() {
try {
// Old API Keys were introduced in March 2025 and later replaced with tokens
// Now that we have new API keys that are prefixed with `sk_`,
// we need to clean up the old ones to free the secret storage
StateManager.get().setSecret("clineApiKey", undefined)
} catch (error) {
Logger.error("Failed to cleanup old clineApiKey", error)
}
}
@@ -2,6 +2,7 @@ import type { ToolUse } from "@core/assistant-message"
import { formatResponse } from "@core/prompts/responses"
import { ClineAsk, ClineAskUseMcpServer } from "@shared/ExtensionMessage"
import { telemetryService } from "@/services/telemetry"
import { truncateContent } from "@/shared/content-limits"
import { ClineDefaultTool } from "@/shared/tools"
import type { ToolResponse } from "../../index"
import { showNotificationForApproval } from "../../utils"
@@ -158,7 +159,10 @@ export class AccessMcpResourceHandler implements IFullyManagedTool {
// Display result to user
await config.callbacks.say("mcp_server_response", resourceResultPretty)
// Truncate response if it exceeds 400KB to prevent context overflow
const truncatedResult = truncateContent(resourceResultPretty)
// Return formatted result
return formatResponse.toolResult(resourceResultPretty)
return formatResponse.toolResult(truncatedResult)
}
}
@@ -25,22 +25,13 @@ export class AttemptCompletionHandler implements IToolHandler, IPartialBlockHand
/**
* Handle partial block streaming for attempt_completion
* Matches the original conditional logic structure for command vs no-command cases
*/
async handlePartialBlock(block: ToolUse, uiHelpers: StronglyTypedUIHelpers): Promise<void> {
const result = block.params.result
const command = block.params.command
if (!command) {
// no command, still outputting partial result
await uiHelpers.say(
"completion_result",
uiHelpers.removeClosingTag(block, "result", result),
undefined,
undefined,
block.partial,
)
const result = uiHelpers.removeClosingTag(block, "result", block.params.result)
if (result) {
await uiHelpers.say("completion_result", result, undefined, undefined, block.partial)
}
// We will handle command in the final execution step
}
async execute(config: TaskConfig, block: ToolUse): Promise<ToolResponse> {
@@ -2,6 +2,7 @@ import type { ToolUse } from "@core/assistant-message"
import { formatResponse } from "@core/prompts/responses"
import { ClineAsk, ClineAskUseMcpServer } from "@shared/ExtensionMessage"
import { telemetryService } from "@/services/telemetry"
import { truncateContent } from "@/shared/content-limits"
import { ClineDefaultTool } from "@/shared/tools"
import type { ToolResponse } from "../../index"
import { showNotificationForApproval } from "../../utils"
@@ -204,6 +205,9 @@ export class UseMcpToolHandler implements IFullyManagedTool {
toolResultText += `\n\n[${toolResultImages.length} images were provided in the response, and while they are displayed to the user, you do not have the ability to view them.]`
}
// Truncate response if it exceeds 400KB to prevent context overflow
toolResultText = truncateContent(toolResultText)
// Return formatted result (only pass images if model supports them)
return formatResponse.toolResult(toolResultText, supportsImages ? toolResultImages : undefined)
} catch (error) {
+3 -1
View File
@@ -31,6 +31,7 @@ import { HookDiscoveryCache } from "./core/hooks/HookDiscoveryCache"
import { StateManager } from "./core/storage/StateManager"
import {
cleanupMcpMarketplaceCatalogFromGlobalState,
cleanupOldApiKey,
migrateCustomInstructionsToGlobalRules,
migrateTaskHistoryToFile,
migrateWelcomeViewCompleted,
@@ -493,7 +494,7 @@ ${ctx.cellJson || "{}"}
// Register the generateGitCommitMessage command handler
context.subscriptions.push(
vscode.commands.registerCommand(commands.GenerateCommit, async (scm) => {
generateCommitMsg(webview.controller.stateManager, scm)
generateCommitMsg(webview.controller, scm)
}),
vscode.commands.registerCommand(commands.AbortCommit, () => {
abortCommitGeneration()
@@ -655,6 +656,7 @@ if (IS_DEV) {
// VSCode-specific storage migrations
async function performStorageMigrations(context: ExtensionContext): Promise<void> {
try {
cleanupOldApiKey()
// Migrate is not done if the new storage does not have the lastShownAnnouncementId flag
const hasMigrated = StateManager.get().getGlobalStateKey("lastShownAnnouncementId")
if (hasMigrated !== undefined) {

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