所有文章 > 日积月累 > Cursor 的开源平替产品 Cline介绍
Cursor 的开源平替产品 Cline介绍

Cursor 的开源平替产品 Cline介绍

Cursor 确实很强。只需要简单闲聊几句,它就能根据你的需求,开发一个完整的软件应用。

但是 Cursor 对于国内用户来说有很多局限性,比如网络受限,比如价格昂贵,很不接地气。

于是便出现了一些开源平替,其中最著名的当属 VS Code 插件 Cline。

Cline 完全免费,支持接入各种主流大模型 API,包括 DeepSeek,使用体验非常接近 Cursor。

当然 Cline 不会免费提供模型 API,需要自己接入。

Cline 将 AI 能力与开发工具深度集成,提供了一个统一的智能化开发环境。通过智能理解开发者意图,自动化执行开发任务,并保持对项目上下文的持续理解,显著提升了开发效率和体验。

而且它还是开源的,这么一个优秀的开源项目,必须得好好研究学习一下。

本文将从架构设计、核心模块实现等角度,深入解析 Cline 背后的技术原理和实现细节。

概述

Cline 插件采用模块化设计,主要由以下核心组件构成:

  • ClineProvider:负责状态管理和 UI 交互
  • Cline:核心业务逻辑处理
  • 外部服务集成:包括 AI 服务、认证服务等
  • 工具系统:提供文件操作、命令执行等能力

通过这些组件的协同工作,Cline 实现了四个核心目标:

  1. 工具统一
  • 在 VS Code 中集成文件操作、命令执行、浏览器控制等能力
  • 提供统一的界面来执行各种开发任务
  • 减少工具切换带来的认知负担
  1. 智能自动化
  • 通过 AI 理解开发者意图
  • 自动完成重复性工作
  • 智能化的代码理解和生成
  1. 上下文保持
  • 维护对项目结构的持续理解
  • 在任务执行过程中保持状态连贯
  • 支持长时间运行的复杂任务
  1. 安全可控
  • 提供操作确认机制
  • 支持任务的暂停和恢复
  • 实现关键操作的回滚能力

整体架构概览

Cline 的整体架构可以分为四个主要部分:

  • VS Code Extension Layer
  • Webview Panel
  • Sidebar
  • Commands
  • extension.ts:插件入口点
  • 用户界面组件:
  • Core System
  • assistant-message:消息处理
  • ignore:忽略规则
  • prompts:提示管理
  • sliding-window:对话管理
  • webview:UI 交互
  • ClineProvider:状态管理和视图控制
  • Cline:核心业务逻辑
  • 核心模块:
  • State Management
  • Global State:全局状态
  • Workspace State:工作区状态
  • Secrets Storage:密钥存储
  • External Services
  • Anthropic API
  • OpenAI API
  • OpenRouter
  • AI Services:
  • Authentication:认证服务
  • Browser Control:浏览器控制

核心架构图

插件入口

export function activate(context: vscode.ExtensionContext) {
// 1. 创建输出通道
outputChannel = vscode.window.createOutputChannel("Cline");

// 2. 初始化日志系统
Logger.initialize(outputChannel);

// 3. 创建侧边栏提供者
const sidebarProvider = new ClineProvider(context, outputChannel);

// 4. 注册侧边栏视图
context.subscriptions.push(
vscode.window.registerWebviewViewProvider(
ClineProvider.sideBarId,
sidebarProvider,
{
webviewOptions: {
retainContextWhenHidden: true
}
}
)
);
}

// 注册了多个VS Code命令:
- "cline.plusButtonClicked" // 新建对话
- "cline.mcpButtonClicked" // MCP按钮
- "cline.popoutButtonClicked" // 弹出到新标签页
- "cline.settingsButtonClicked" // 设置
- "cline.historyButtonClicked" // 历史记录
- "cline.accountLoginClicked" // 账号登录

ClineProvider.ts

cline.ts

提示词

提示词分为几个部分:

  1. 角色定义:定义角色信息
  2. 工具调用:
  • 文件操作:读文件,写文件 (覆盖),替换文件内容 (diff 形式),查文件,list 文件,list 代码定义
  • 浏览器操作:浏览器操作 (使用 puppeteer 访问和点击操作,执行操作并且返回截图和控制台日志)
  • 终端操作:执行命令
  • MCP 操作:MCP 工具调用和资源请求
  • 基础操作:询问用户问题,输出命令结果 (用于展示任务完成后的结果,标志结束)
  • 规划模式:规划模式定义 (帮助用户规划和脑暴解决方案)
  • 工具定义
  • 内置工具列表:
  • 调用例子和调用指南
  1. MCP 服务:MCP 定义,动态注入 MCP 服务器列表,MCP 创建示例和指南
  2. 替换文件和创建文件的区别:两者的定义和使用场景以及优势,考虑改写之后用户编辑器的自动格式化 (直接输出最终状态),工作流程的一些建议
  3. ACT 模式和 PLAN 模式:让 AI 理解 act 模式和 plan 模式,act 模式可以调用除 plan_mode_response 工具之外的所有工具,而 plan 模式则使用 plan_mode_response
  4. 能力:指定 AI 具有什么能力,比如能调用哪些工具以及这些工具是干什么的
  5. 规则:指定 AI 需要遵守的规则,能做什么,不能做什么
  6. 系统信息:操作系统、默认 shell,home 目录,当前工作目录
  7. 目标:宏观指定 AI 完成任务的步骤
  8. 用户自定义指南

完整提示词:https://github.com/cline/cline/blob/main/src/core/prompts/system.ts

核心执行流程

任务执行流程

  1. 任务初始化
  • 用户通过 Webview 界面输入任务
  • Webview 将消息传递给 ClineProvider
  • ClineProvider 创建和初始化 Cline 实例
  1. 任务执行循环
  • Cline 向 AI 服务发送请求
  • AI 服务返回响应和可能的工具调用
  • Cline 解析响应并执行相应的工具
  • 需要用户确认的操作会请求用户确认
  • 工具执行结果返回给 Cline
  • Cline 将结果返回给 AI 服务继续处理
  1. 任务完成
  • 当所有必要的步骤都完成后
  • Cline 向用户返回最终结果
  • 任务执行完成

工具执行流程

核心组件实现

ClineProvider Cline 核心类

class ClineProvider implements vscode.WebviewViewProvider {
private cline?: Cline
private view?: vscode.WebviewView
private context: vscode.ExtensionContext

// 核心管理功能
async initClineWithTask(task?: string)
async handleMessage(message: Message)
async updateState(state: State)

// 定义全局文件名
export const GlobalFileNames = {
apiConversationHistory: "api_conversation_history.json", // API对话历史
uiMessages: "ui_messages.json", // UI消息
openRouterModels: "openrouter_models.json", // OpenRouter模型
mcpSettings: "cline_mcp_settings.json", // MCP设置
clineRules: ".clinerules", // Cline规则
}
}

3

class ClineProvider {
constructor(context: vscode.ExtensionContext) {
this.context = context
this.cline = undefined // 延迟初始化
}

async initClineWithTask(task?: string) {
await this.clearTask() // 清理现有任务

// 获取配置
const config = await this.getState()

// 创建新实例
this.cline = new Cline(
this,
config.apiConfiguration,
config.autoApprovalSettings,
config.browserSettings,
config.chatSettings,
config.customInstructions
)
}
}

1

export class Cline {
// 核心属性
readonly taskId: string;
api: ApiHandler;
private terminalManager: TerminalManager;
private urlContentFetcher: UrlContentFetcher;
browserSession: BrowserSession;

// 状态控制
private didEditFile: boolean = false;
private abort: boolean = false;
private consecutiveMistakeCount: number = 0;

// 历史记录
apiConversationHistory: Anthropic.MessageParam[] = [];
clineMessages: ClineMessage[] = [];

// 配置系统
autoApprovalSettings: AutoApprovalSettings;
private browserSettings: BrowserSettings;
private chatSettings: ChatSettings;

// 任务执行核心方法
async startTask(task?: string, images?: string[]): Promise<void> {
// 初始化状态
this.clineMessages = [];
this.apiConversationHistory = [];

// 更新界面
await this.providerRef.deref()?.postStateToWebview();

// 显示任务
await this.say("text", task, images);

// 标记初始化完成
this.isInitialized = true;

// 构建初始消息
let imageBlocks = formatResponse.imageBlocks(images);

// 启动任务循环
await this.initiateTaskLoop(
[{
type: "text",
text: `<task>\\n${task}\\n</task>`,
},
...imageBlocks],
true // isNewTask 标记
);
}
}

任务循环实现

private async initiateTaskLoop(
userContent: UserContent,
isNewTask: boolean
): Promise<void> {
let nextUserContent = userContent;
let includeFileDetails = true;

while (!this.abort) {
// 核心请求处理
const didEndLoop = await this.recursivelyMakeClineRequests(
nextUserContent,
includeFileDetails,
isNewTask
);

includeFileDetails = false;

if (didEndLoop) {
break;
} else {
// 处理未使用工具的情况
nextUserContent = [{
type: "text",
text: formatResponse.noToolsUsed(),
}];
this.consecutiveMistakeCount++;
}
}
}

任务结束的条件

// 1. this.abort 为 true 的情况:
- 用户手动中止
- 发生严重错误
- Cline 实例被销毁

// 2. didEndLoop 为 true 的情况:
- 任务成功完成(使用 attempt_completion 工具)
- 达到最大错误次数
- 任务被标记为完成

// 3. 其他终止条件:
- 连续错误次数过多
- API 调用失败
- 工具执行失败

请求处理系统

递归请求处理

private async recursivelyMakeClineRequests(
userContent: UserContent,
includeFileDetails: boolean,
isNewTask: boolean
): Promise<boolean> {
// 1. 错误检查
if (this.consecutiveMistakeCount >= MAX_CONSECUTIVE_MISTAKES) {
throw new Error("Too many consecutive mistakes")
}

try {
// 2. 准备消息
const messages = this.prepareMessages(userContent, includeFileDetails)

// 3. 发送 API 请求
const response = await this.api.sendRequest(messages)

// 4. 处理响应
if (response.type === "completion") {
return true // 任务完成
} else if (response.type === "tool_use") {
await this.handleToolUse(response.tool)
return false // 继续执行
}
} catch (error) {
this.handleError(error)
return true
}
}

消息准备

private prepareMessages(
userContent: UserContent,
includeFileDetails: boolean
): MessageParam[] {
// 1. 构建基础消息
const messages = [...this.apiConversationHistory]

// 2. 添加环境详情
if (includeFileDetails) {
messages.push({
type: "environment_details",
content: this.getEnvironmentDetails()
})
}

// 3. 添加用户内容
messages.push({
type: "user_content",
content: userContent
})

return messages
}

API 通信系统

class ApiHandler {
async *attemptApiRequest(previousApiReqIndex: number) {
// 1. 系统提示准备
let systemPrompt = await SYSTEM_PROMPT(
cwd,
this.api.getModel().info.supportsComputerUse,
mcpHub,
this.browserSettings
);

// 2. 上下文预处理
const { truncatedHistory, deletedRange } = this.truncateHistoryToFitContext(
previousApiReqIndex
);

// 3. 流式通信
const stream = this.api.createMessage(systemPrompt, truncatedHistory);
yield* this.handleStreamResponse(stream);
}

private truncateHistoryToFitContext(previousIndex: number) {
const totalTokens = this.calculateTokensUsage();

switch (this.contextWindow) {
case 64_000: // deepseek models
this.maxAllowedSize = this.contextWindow - 27_000;
break;
case 128_000: // most models
this.maxAllowedSize = this.contextWindow - 30_000;
break;
}

if (totalTokens >= this.maxAllowedSize) {
return this.performTruncation();
}

return { truncatedHistory: this.history };
}
}

工具

export const toolUseNames = [
"execute_command",
"read_file",
"write_to_file",
"replace_in_file",
"search_files",
"list_files",
"list_code_definition_names",
"browser_action",
"use_mcp_tool",
"access_mcp_resource",
"ask_followup_question",
"plan_mode_response",
"attempt_completion",
] as const

终端命令执行工具

文件操作工具

class FileOperations implements Tool {
// 文件读取
async readFile(path: string): Promise<string> {
return fs.readFile(path, 'utf8');
}

// 文件写入
async writeFile(path: string, content: string): Promise<void> {
await this.ensureDirectoryExists(path);
await fs.writeFile(path, content);
}

// 文件替换
async replaceInFile(path: string, diff: string): Promise<void> {
const content = await this.readFile(path);
const newContent = await constructNewFileContent(diff, content);
await this.writeFile(path, newContent);
}

// 文件列表
async listFiles(path: string, recursive: boolean): Promise<string[]> {
if (recursive) {
return this.recursiveListFiles(path);
}
return fs.readdir(path);
}
}

安全机制实现

文件操作安全

class FileOperationApproval {
async checkFileAccess(operation: FileOperation): Promise<boolean> {
// 1. 路径安全检查
if (!this.isPathSafe(operation.path)) {
return false;
}

// 2. 操作权限检查
if (!this.hasPermission(operation.type)) {
return false;
}

// 3. 内容安全检查
if (operation.type === 'write' && !this.isContentSafe(operation.content)) {
return false;
}

return true;
}

private isPathSafe(path: string): boolean {
// 检查路径是否在工作目录内
// 防止目录遍历攻击
const normalizedPath = normalizePath(path);
return isWithinWorkspace(normalizedPath);
}
}

命令执行安全

class CommandSecurity {
private readonly restrictedCommands: Set<string> = new Set([
'rm -rf',
'format',
'shutdown',
// ... 其他危险命令
]);

async validateCommand(command: string): Promise<boolean> {
// 1. 检查是否是受限命令
if (this.isRestrictedCommand(command)) {
return false;
}

// 2. 检查命令权限
if (!await this.checkCommandPermissions(command)) {
return false;
}

// 3. 检查资源限制
if (!this.checkResourceLimits(command)) {
return false;
}

return true;
}

private isRestrictedCommand(command: string): boolean {
return Array.from(this.restrictedCommands).some(
restricted => command.includes(restricted)
);
}
}

检查点系统

三种情况:task (恢复任务消息)、workspace (恢复工作区文件)、taskAndWorkspace (同时恢复任务消息和工作区文件)

核心机制:ShadowGit (主仓库的变更不会影响到 Shadow 仓库,shadow 仓库的变更会形成主仓库的未提交的更改)

核心流程:

  1. 如果恢复 workspace 或 taskAndWorkspace,则会首先初始化 checkpointTracker,并通过 resetHead 直接将工作区恢复到指定 git commit。
  2. 对于 task 或 taskAndWorkspace,我们还需要恢复 task 历史消息记录,同时记录回滚的 APi 资源消耗
class CheckpointSystem {
private checkpoints: Map<string, Checkpoint> = new Map();

// 创建检查点
async createCheckpoint(): Promise<string> {
const checkpoint = {
id: generateId(),
timestamp: Date.now(),
state: await this.captureCurrentState(),
files: await this.captureFileState()
};

this.checkpoints.set(checkpoint.id, checkpoint);
return checkpoint.id;
}

// 恢复检查点
async restoreCheckpoint(id: string): Promise<void> {
const checkpoint = this.checkpoints.get(id);
if (!checkpoint) {
throw new Error('Checkpoint not found');
}

await this.restoreState(checkpoint.state);
await this.restoreFiles(checkpoint.files);
}
}

文件存储

文件存储主要分为以下几类:

Task 对话流,配置文件 (主要是 mcp 服务器信息),mcp 服务端代码,自定义 prompt (。clinerules)

Task 存储

存储位置:~/Library/Application\ Support/Code/User/globalStorage/扩展名/tasks/任务 ID

  • api_conversation_history.json:存储消息和上下文
[
{
"role": "user",
"content": [
{ "type": "text", "text": "<task>\n你给我优化一下代码\n</task>" },
{
"type": "text",
"text": "<environment_details>\n# VSCode Visible Files\nlib/auth.ts\n\n# VSCode Open Tabs\nlib/auth.ts\n\n# Current Time\n2025/1/22 下午5:10:08 (Asia/Shanghai, UTC+8:00)\n\n# Current Working Directory (/Users/mlhiter/personal-projects/cherrix) Files\n.env.template\n.gitignore\nauth.config.ts\nauth.ts\ncomponents.json\neslint.config.mjs\nmiddleware.ts\nnext-auth.d.ts\nnext.config.ts\npackage.json\npnpm-lock.yaml\npostcss.config.mjs\nprettier.config.js\nREADME.md\nroutes.ts\ntailwind.config.ts\ntsconfig.json\nactions/\nactions/login.ts\nactions/logout.ts\nactions/new-password.ts\nactions/new-verification.tsx\nactions/register.ts\nactions/reset.ts\nactions/settings.ts\napp/\napp/favicon.ico\napp/globals.css\napp/layout.tsx\napp/page.tsx\napp/(protected)/\napp/api/\napp/api/auth/\napp/api/auth/[...nextauth]/\napp/auth/\napp/auth/layout.tsx\napp/auth/error/\napp/auth/error/page.tsx\napp/auth/login/\napp/auth/login/page.tsx\napp/auth/new-password/\napp/auth/new-password/page.tsx\napp/auth/new-verification/\napp/auth/new-verification/page.tsx\napp/auth/register/\napp/auth/register/page.tsx\napp/auth/reset/\napp/auth/reset/page.tsx\ncomponents/\ncomponents/form-error.tsx\ncomponents/form-success.tsx\ncomponents/user-info.tsx\ncomponents/auth/\ncomponents/auth/back-button.tsx\ncomponents/auth/card-wrapper.tsx\ncomponents/auth/error-card.tsx\ncomponents/auth/header.tsx\ncomponents/auth/login-button.tsx\ncomponents/auth/login-form.tsx\ncomponents/auth/logout-button.tsx\ncomponents/auth/new-password-form.tsx\ncomponents/auth/new-verification-form.tsx\ncomponents/auth/register-form.tsx\ncomponents/auth/reset-form.tsx\ncomponents/auth/role-gate.tsx\ncomponents/auth/social.tsx\ncomponents/auth/user-button.tsx\ncomponents/ui/\ncomponents/ui/avatar.tsx\ncomponents/ui/badge.tsx\ncomponents/ui/button.tsx\ncomponents/ui/card.tsx\ncomponents/ui/dialog.tsx\ncomponents/ui/dropdown-menu.tsx\ncomponents/ui/form.tsx\ncomponents/ui/input.tsx\ncomponents/ui/label.tsx\ncomponents/ui/select.tsx\ncomponents/ui/sonner.tsx\ncomponents/ui/switch.tsx\ndata/\ndata/account.ts\ndata/password-reset-token.ts\ndata/two-factor-confirmation.ts\ndata/two-factor-token.ts\ndata/user.ts\ndata/verification-token.ts\nhooks/\nhooks/use-current-role.ts\nhooks/use-current.user.ts\nlib/\nlib/auth.ts\nlib/db.ts\nlib/mail.ts\nlib/tokens.ts\nlib/utils.ts\nprisma/\nprisma/schema.prisma\nschemas/\nschemas/index.ts\n\n# Current Mode\nACT MODE\n</environment_details>"
}
]
},
{
"role": "assistant",
"content": [
{
"type": "text",
"text": "<thinking>\n1. 需要优化代码,但用户没有指定具体文件。从环境信息中看到当前打开的文件是 lib/auth.ts\n2. 首先需要查看 lib/auth.ts 的内容,了解当前代码结构,才能进行优化\n3. 使用 read_file 工具读取 lib/auth.ts 文件内容\n</thinking>\n\n<read_file>\n<path>lib/auth.ts</path>\n</read_file>"
}
]
},
{
"role": "user",
"content": [
{ "type": "text", "text": "[read_file for 'lib/auth.ts'] Result:" },
{
"type": "text",
"text": "import { auth } from '@/auth'\n\nexport const currentUser = async () => {\n const session = await auth()\n return session?.user\n}\n\nexport const currentRole = async () => {\n const session = await auth()\n return session?.user.role\n}\n"
},
{
"type": "text",
"text": "<environment_details>\n# VSCode Visible Files\nlib/auth.ts\n\n# VSCode Open Tabs\nlib/auth.ts\n\n# Current Time\n2025/1/22 下午5:10:15 (Asia/Shanghai, UTC+8:00)\n\n# Current Mode\nACT MODE\n</environment_details>"
}
]
},
{
"role": "assistant",
"content": [
{
"type": "text",
"text": "<thinking>\n1. 当前代码存在以下可以优化的地方:\n - 两个函数都调用了 auth(),存在重复代码\n - 没有错误处理机制\n - 返回类型不明确\n - 可以添加缓存机制提高性能\n\n2. 优化方案:\n - 提取公共的 auth() 调用\n - 添加类型定义\n - 添加错误处理\n - 添加简单的内存缓存\n</thinking>\n\n<replace_in_file>\n<path>lib/auth.ts</path>\n<diff>\n<<<<<<< SEARCH\nimport { auth } from '@/auth'\n\nexport const currentUser = async () => {\n const session = await auth()\n return session?.user\n}\n\nexport const currentRole = async () => {\n const session = await auth()\n return session?.user.role\n}\n=======\nimport { auth } from '@/auth'\nimport { User } from 'next-auth'\n\n// Simple in-memory cache\nlet cachedSession: { user: User } | null = null\nlet lastFetchTime = 0\nconst CACHE_DURATION = 1000 * 60 // 1 minute\n\nasync function getSession() {\n const now = Date.now()\n if (!cachedSession || now - lastFetchTime > CACHE_DURATION) {\n try {\n cachedSession = await auth()\n lastFetchTime = now\n } catch (error) {\n console.error('Failed to fetch session:', error)\n return null\n }\n }\n return cachedSession\n}\n\nexport const currentUser = async (): Promise<User | null> => {\n const session = await getSession()\n return session?.user ?? null\n}\n\nexport const currentRole = async (): Promise<string | null> => {\n const session = await getSession()\n return session?.user?.role ?? null\n}\n>>>>>>> REPLACE\n</diff>\n</replace_in_file>"
}
]
},
...
]
  • ui_messages.json:存储 UI 上显示的信息
[
{
"ts": 1737537007785,
"type": "say",
"say": "text",
"text": "你给我优化一下代码",
"images": [],
"conversationHistoryIndex": -1
},
{
"ts": 1737537007797,
"type": "say",
"say": "api_req_started",
"text": "{\"request\":\"<task>\\n你给我优化一下代码\\n</task>\\n\\n<environment_details>\\n# VSCode Visible Files\\nlib/auth.ts\\n\\n# VSCode Open Tabs\\nlib/auth.ts\\n\\n# Current Time\\n2025/1/22 下午5:10:08 (Asia/Shanghai, UTC+8:00)\\n\\n# Current Working Directory (/Users/mlhiter/personal-projects/cherrix) Files\\n.env.template\\n.gitignore\\nauth.config.ts\\nauth.ts\\ncomponents.json\\neslint.config.mjs\\nmiddleware.ts\\nnext-auth.d.ts\\nnext.config.ts\\npackage.json\\npnpm-lock.yaml\\npostcss.config.mjs\\nprettier.config.js\\nREADME.md\\nroutes.ts\\ntailwind.config.ts\\ntsconfig.json\\nactions/\\nactions/login.ts\\nactions/logout.ts\\nactions/new-password.ts\\nactions/new-verification.tsx\\nactions/register.ts\\nactions/reset.ts\\nactions/settings.ts\\napp/\\napp/favicon.ico\\napp/globals.css\\napp/layout.tsx\\napp/page.tsx\\napp/(protected)/\\napp/api/\\napp/api/auth/\\napp/api/auth/[...nextauth]/\\napp/auth/\\napp/auth/layout.tsx\\napp/auth/error/\\napp/auth/error/page.tsx\\napp/auth/login/\\napp/auth/login/page.tsx\\napp/auth/new-password/\\napp/auth/new-password/page.tsx\\napp/auth/new-verification/\\napp/auth/new-verification/page.tsx\\napp/auth/register/\\napp/auth/register/page.tsx\\napp/auth/reset/\\napp/auth/reset/page.tsx\\ncomponents/\\ncomponents/form-error.tsx\\ncomponents/form-success.tsx\\ncomponents/user-info.tsx\\ncomponents/auth/\\ncomponents/auth/back-button.tsx\\ncomponents/auth/card-wrapper.tsx\\ncomponents/auth/error-card.tsx\\ncomponents/auth/header.tsx\\ncomponents/auth/login-button.tsx\\ncomponents/auth/login-form.tsx\\ncomponents/auth/logout-button.tsx\\ncomponents/auth/new-password-form.tsx\\ncomponents/auth/new-verification-form.tsx\\ncomponents/auth/register-form.tsx\\ncomponents/auth/reset-form.tsx\\ncomponents/auth/role-gate.tsx\\ncomponents/auth/social.tsx\\ncomponents/auth/user-button.tsx\\ncomponents/ui/\\ncomponents/ui/avatar.tsx\\ncomponents/ui/badge.tsx\\ncomponents/ui/button.tsx\\ncomponents/ui/card.tsx\\ncomponents/ui/dialog.tsx\\ncomponents/ui/dropdown-menu.tsx\\ncomponents/ui/form.tsx\\ncomponents/ui/input.tsx\\ncomponents/ui/label.tsx\\ncomponents/ui/select.tsx\\ncomponents/ui/sonner.tsx\\ncomponents/ui/switch.tsx\\ndata/\\ndata/account.ts\\ndata/password-reset-token.ts\\ndata/two-factor-confirmation.ts\\ndata/two-factor-token.ts\\ndata/user.ts\\ndata/verification-token.ts\\nhooks/\\nhooks/use-current-role.ts\\nhooks/use-current.user.ts\\nlib/\\nlib/auth.ts\\nlib/db.ts\\nlib/mail.ts\\nlib/tokens.ts\\nlib/utils.ts\\nprisma/\\nprisma/schema.prisma\\nschemas/\\nschemas/index.ts\\n\\n# Current Mode\\nACT MODE\\n</environment_details>\",\"tokensIn\":11870,\"tokensOut\":87,\"cacheWrites\":11614,\"cacheReads\":256,\"cost\":0.001653904}",
"conversationHistoryIndex": -1
},
{
"ts": 1737537012356,
"type": "say",
"say": "text",
"text": "1. 需要优化代码,但用户没有指定具体文件。从环境信息中看到当前打开的文件是 lib/auth.ts\n2. 首先需要查看 lib/auth.ts 的内容,了解当前代码结构,才能进行优化\n3. 使用 read_file 工具读取 lib/auth.ts 文件内容",
"partial": false,
"conversationHistoryIndex": 0
},
{
"ts": 1737537014438,
"type": "say",
"say": "tool",
"text": "{\"tool\":\"readFile\",\"path\":\"lib/auth.ts\",\"content\":\"/Users/mlhiter/personal-projects/cherrix/lib/auth.ts\"}",
"partial": false,
"conversationHistoryIndex": 0,
"lastCheckpointHash": "a6d8812056f5cf1220148390772dba1a528b95ac"
},
{
"ts": 1737537015217,
"type": "say",
"say": "api_req_started",
"text": "{\"request\":\"[read_file for 'lib/auth.ts'] Result:\\n\\nimport { auth } from '@/auth'\\n\\nexport const currentUser = async () => {\\n const session = await auth()\\n return session?.user\\n}\\n\\nexport const currentRole = async () => {\\n const session = await auth()\\n return session?.user.role\\n}\\n\\n\\n<environment_details>\\n# VSCode Visible Files\\nlib/auth.ts\\n\\n# VSCode Open Tabs\\nlib/auth.ts\\n\\n# Current Time\\n2025/1/22 下午5:10:15 (Asia/Shanghai, UTC+8:00)\\n\\n# Current Mode\\nACT MODE\\n</environment_details>\",\"tokensIn\":12095,\"tokensOut\":405,\"cacheWrites\":255,\"cacheReads\":11840,\"cost\":0.00031486}",
"conversationHistoryIndex": 1
},
{
"ts": 1737537017423,
"type": "say",
"say": "text",
"text": "1. 当前代码存在以下可以优化的地方:\n - 两个函数都调用了 auth(),存在重复代码\n - 没有错误处理机制\n - 返回类型不明确\n - 可以添加缓存机制提高性能\n\n2. 优化方案:\n - 提取公共的 auth() 调用\n - 添加类型定义\n - 添加错误处理\n - 添加简单的内存缓存",
"partial": false,
"conversationHistoryIndex": 2
},
{
"ts": 1737537020284,
"type": "say",
"say": "tool",
"text": "{\"tool\":\"editedExistingFile\",\"path\":\"lib/auth.ts\",\"content\":\"<<<<<<< SEARCH\\nimport { auth } from '@/auth'\\n\\nexport const currentUser = async () => {\\n const session = await auth()\\n return session?.user\\n}\\n\\nexport const currentRole = async () => {\\n const session = await auth()\\n return session?.user.role\\n}\\n=======\\nimport { auth } from '@/auth'\\nimport { User } from 'next-auth'\\n\\n// Simple in-memory cache\\nlet cachedSession: { user: User } | null = null\\nlet lastFetchTime = 0\\nconst CACHE_DURATION = 1000 * 60 // 1 minute\\n\\nasync function getSession() {\\n const now = Date.now()\\n if (!cachedSession || now - lastFetchTime > CACHE_DURATION) {\\n try {\\n cachedSession = await auth()\\n lastFetchTime = now\\n } catch (error) {\\n console.error('Failed to fetch session:', error)\\n return null\\n }\\n }\\n return cachedSession\\n}\\n\\nexport const currentUser = async (): Promise<User | null> => {\\n const session = await getSession()\\n return session?.user ?? null\\n}\\n\\nexport const currentRole = async (): Promise<string | null> => {\\n const session = await getSession()\\n return session?.user?.role ?? null\\n}\\n>>>>>>> REPLACE\"}",
"partial": false,
"conversationHistoryIndex": 2,
"lastCheckpointHash": "212e268f2f768fce47f65215b40725a967e6c2b4"
},
{
"ts": 1737537031730,
"type": "say",
"say": "api_req_started",
"text": "{\"request\":\"[replace_in_file for 'lib/auth.ts'] Result:\\n\\nThe content was successfully saved to lib/auth.ts.\\n\\nHere is the full, updated content of the file that was saved:\\n\\n<final_file_content path=\\\"lib/auth.ts\\\">\\nimport { auth } from '@/auth'\\nimport { User } from 'next-auth'\\n\\n// Simple in-memory cache\\nlet cachedSession: { user: User } | null = null\\nlet lastFetchTime = 0\\nconst CACHE_DURATION = 1000 * 60 // 1 minute\\n\\nasync function getSession() {\\n const now = Date.now()\\n if (!cachedSession || now - lastFetchTime > CACHE_DURATION) {\\n try {\\n cachedSession = await auth()\\n lastFetchTime = now\\n } catch (error) {\\n console.error('Failed to fetch session:', error)\\n return null\\n }\\n }\\n return cachedSession\\n}\\n\\nexport const currentUser = async (): Promise<User | null> => {\\n const session = await getSession()\\n return session?.user ?? null\\n}\\n\\nexport const currentRole = async (): Promise<string | null> => {\\n const session = await getSession()\\n return session?.user?.role ?? null\\n}\\n\\n</final_file_content>\\n\\nIMPORTANT: For any future changes to this file, use the final_file_content shown above as your reference. This content reflects the current state of the file, including any auto-formatting (e.g., if you used single quotes but the formatter converted them to double quotes). Always base your SEARCH/REPLACE operations on this final version to ensure accuracy.\\n\\n\\n\\nNew problems detected after saving the file:\\nlib/auth.ts\\n- [ts Error] Line 30: 类型“User”上不存在属性“role”。\\n\\n<environment_details>\\n# VSCode Visible Files\\nlib/auth.ts\\n\\n# VSCode Open Tabs\\nlib/auth.ts\\n\\n# Current Time\\n2025/1/22 下午5:10:31 (Asia/Shanghai, UTC+8:00)\\n\\n# Current Mode\\nACT MODE\\n</environment_details>\",\"tokensIn\":0,\"tokensOut\":0,\"cacheWrites\":0,\"cacheReads\":0,\"cost\":0}",
"conversationHistoryIndex": 3
},
{
"ts": 1737537034848,
"type": "say",
"say": "text",
"text": "1. 出现类型错误,因为 User 类型上缺少 role 属性\n2. 需要扩展 next-auth 的 User 类型定义\n3. 在 next-auth.d.ts 文件中添加类型扩展",
"partial": false,
"conversationHistoryIndex": 4
},
{
"ts": 1737537036421,
"type": "say",
"say": "tool",
"text": "{\"tool\":\"editedExistingFile\",\"path\":\"next-auth.d.ts\",\"content\":\"<<<<<<< SEARCH\\nimport { DefaultSession } from \\\"next-auth\\\";\\n\\ndeclare module \\\"next-auth\\\" {\\n interface Session {\\n user: {\\n id: string;\\n } & DefaultSession[\\\"user\\\"];\\n }\\n}\\n======\"}",
"partial": true,
"conversationHistoryIndex": 4
},
{
"ts": 1737537037472,
"type": "say",
"say": "diff_error",
"text": "next-auth.d.ts",
"conversationHistoryIndex": 4
},
{
"ts": 1737537037561,
"type": "say",
"say": "api_req_started",
"text": "{\"request\":\"[replace_in_file for 'next-auth.d.ts'] Result:\\n\\nThe tool execution failed with the following error:\\n<error>\\nThe SEARCH block:\\nimport { DefaultSession } from \\\"next-auth\\\";\\n\\ndeclare module \\\"next-auth\\\" {\\n interface Session {\\n user: {\\n id: string;\\n } & DefaultSession[\\\"user\\\"];\\n }\\n}\\n...does not match anything in the file.\\n\\nThis is likely because the SEARCH block content doesn't match exactly with what's in the file, or if you used multiple SEARCH/REPLACE blocks they may not have been in the order they appear in the file.\\n\\nThe file was reverted to its original state:\\n\\n<file_content path=\\\"next-auth.d.ts\\\">\\nimport { type DefaultSession } from 'next-auth'\\nimport { UserRole } from '@prisma/client'\\nexport type ExtendedUser = DefaultSession['user'] & {\\n role: UserRole\\n isTwoFactorEnabled: boolean\\n isOAuth: boolean\\n}\\n\\ndeclare module 'next-auth' {\\n interface Session {\\n user: ExtendedUser\\n }\\n}\\n\\n</file_content>\\n\\nTry again with a more precise SEARCH block.\\n(If you keep running into this error, you may use the write_to_file tool as a workaround.)\\n</error>\\n\\n<environment_details>\\n# VSCode Visible Files\\nlib/auth.ts\\n\\n# VSCode Open Tabs\\nlib/auth.ts\\n\\n# Current Time\\n2025/1/22 下午5:10:37 (Asia/Shanghai, UTC+8:00)\\n\\n# Current Mode\\nACT MODE\\n</environment_details>\",\"tokensIn\":13424,\"tokensOut\":534,\"cacheWrites\":496,\"cacheReads\":12928,\"cost\":0.00039995199999999997}",
"conversationHistoryIndex": 5
},
{
"ts": 1737537039528,
"type": "say",
"say": "text",
"text": "1. 从错误信息中看到 next-auth.d.ts 文件已经定义了 ExtendedUser 类型,包含 role 属性\n2. 需要更新 lib/auth.ts 中的类型定义以使用 ExtendedUser\n3. 使用 replace_in_file 工具更新 lib/auth.ts 的类型定义",
"partial": false,
"conversationHistoryIndex": 6
},
{
"ts": 1737537041539,
"type": "say",
"say": "tool",
"text": "{\"tool\":\"editedExistingFile\",\"path\":\"lib/auth.ts\",\"content\":\"<<<<<<< SEARCH\\nimport { auth } from '@/auth'\\nimport { User } from 'next-auth'\\n\\n// Simple in-memory cache\\nlet cachedSession: { user: User } | null = null\\nlet lastFetchTime = 0\\nconst CACHE_DURATION = 1000 * 60 // 1 minute\\n\\nasync function getSession() {\\n const now = Date.now()\\n if (!cachedSession || now - lastFetchTime > CACHE_DURATION) {\\n try {\\n cachedSession = await auth()\\n lastFetchTime = now\\n } catch (error) {\\n console.error('Failed to fetch session:', error)\\n return null\\n }\\n }\\n return cachedSession\\n}\\n\\nexport const currentUser = async (): Promise<User | null> => {\\n const session = await getSession()\\n return session?.user ?? null\\n}\\n\\nexport const currentRole = async (): Promise<string | null> => {\\n const session = await getSession()\\n return session?.user?.role ?? null\\n}\\n=======\\nimport { auth } from '@/auth'\\nimport { ExtendedUser } from 'next-auth'\\n\\n// Simple in-memory cache\\nlet cachedSession: { user: ExtendedUser } | null = null\\nlet lastFetchTime = 0\\nconst CACHE_DURATION = 1000 * 60 // 1 minute\\n\\nasync function getSession() {\\n const now = Date.now()\\n if (!cachedSession || now - lastFetchTime > CACHE_DURATION) {\\n try {\\n cachedSession = await auth()\\n lastFetchTime = now\\n } catch (error) {\\n console.error('Failed to fetch session:', error)\\n return null\\n }\\n }\\n return cachedSession\\n}\\n\\nexport const currentUser = async (): Promise<ExtendedUser | null> => {\\n const session = await getSession()\\n return session?.user ?? null\\n}\\n\\nexport const currentRole = async (): Promise<string | null> => {\\n const session = await getSession()\\n return session?.user?.role ?? null\\n}\\n>>>>>>> REPLACE\"}",
"partial": false,
"conversationHistoryIndex": 6,
"lastCheckpointHash": "c70f2140b610333799ec281a4004e462e2434b89"
},
{
"ts": 1737537056632,
"type": "say",
"say": "api_req_started",
"text": "{\"request\":\"[replace_in_file for 'lib/auth.ts'] Result:\\n\\nThe content was successfully saved to lib/auth.ts.\\n\\nHere is the full, updated content of the file that was saved:\\n\\n<final_file_content path=\\\"lib/auth.ts\\\">\\nimport { auth } from '@/auth'\\nimport { ExtendedUser } from 'next-auth'\\n\\n// Simple in-memory cache\\nlet cachedSession: { user: ExtendedUser } | null = null\\nlet lastFetchTime = 0\\nconst CACHE_DURATION = 1000 * 60 // 1 minute\\n\\nasync function getSession() {\\n const now = Date.now()\\n if (!cachedSession || now - lastFetchTime > CACHE_DURATION) {\\n try {\\n cachedSession = await auth()\\n lastFetchTime = now\\n } catch (error) {\\n console.error('Failed to fetch session:', error)\\n return null\\n }\\n }\\n return cachedSession\\n}\\n\\nexport const currentUser = async (): Promise<ExtendedUser | null> => {\\n const session = await getSession()\\n return session?.user ?? null\\n}\\n\\nexport const currentRole = async (): Promise<string | null> => {\\n const session = await getSession()\\n return session?.user?.role ?? null\\n}\\n\\n</final_file_content>\\n\\nIMPORTANT: For any future changes to this file, use the final_file_content shown above as your reference. This content reflects the current state of the file, including any auto-formatting (e.g., if you used single quotes but the formatter converted them to double quotes). Always base your SEARCH/REPLACE operations on this final version to ensure accuracy.\\n\\n\\n\\nNew problems detected after saving the file:\\nlib/auth.ts\\n- [ts Error] Line 2: 模块 \\\"\\\"next-auth\\\"\\\" 没有导出的成员 \\\"ExtendedUser\\\"。你是想改用 \\\"import ExtendedUser from \\\"next-auth\\\"\\\" 吗?\\n\\n<environment_details>\\n# VSCode Visible Files\\nlib/auth.ts\\n\\n# VSCode Open Tabs\\nlib/auth.ts\\n\\n# Current Time\\n2025/1/22 下午5:10:56 (Asia/Shanghai, UTC+8:00)\\n\\n# Current Mode\\nACT MODE\\n</environment_details>\",\"tokensIn\":14423,\"tokensOut\":141,\"cacheWrites\":1047,\"cacheReads\":13376,\"cost\":0.000373324}",
"conversationHistoryIndex": 7
},
{
"ts": 1737537058830,
"type": "say",
"say": "text",
"text": "1. 出现新的类型错误,因为 ExtendedUser 没有从 next-auth 模块正确导出\n2. 需要从 next-auth.d.ts 中导入 ExtendedUser 类型\n3. 使用 replace_in_file 工具更新 lib/auth.ts 的导入语句",
"partial": false,
"conversationHistoryIndex": 8
},
{
"ts": 1737537060971,
"type": "say",
"say": "tool",
"text": "{\"tool\":\"editedExistingFile\",\"path\":\"lib/auth.ts\",\"content\":\"<<<<<<< SEARCH\\nimport { auth } from '@/auth'\\nimport { ExtendedUser } from 'next-auth'\\n=======\\nimport { auth } from '@/auth'\\nimport type { ExtendedUser } from '@/next-auth.d.ts'\\n>>>>>>> REPLACE\"}",
"partial": false,
"conversationHistoryIndex": 8,
"lastCheckpointHash": "1a7d627dcc7386d2185abf42a35c26ca161ab4bb"
},
{
"ts": 1737537067107,
"type": "say",
"say": "api_req_started",
"text": "{\"request\":\"[replace_in_file for 'lib/auth.ts'] Result:\\n\\nThe content was successfully saved to lib/auth.ts.\\n\\nHere is the full, updated content of the file that was saved:\\n\\n<final_file_content path=\\\"lib/auth.ts\\\">\\nimport { auth } from '@/auth'\\nimport type { ExtendedUser } from '@/next-auth.d.ts'\\n\\n// Simple in-memory cache\\nlet cachedSession: { user: ExtendedUser } | null = null\\nlet lastFetchTime = 0\\nconst CACHE_DURATION = 1000 * 60 // 1 minute\\n\\nasync function getSession() {\\n const now = Date.now()\\n if (!cachedSession || now - lastFetchTime > CACHE_DURATION) {\\n try {\\n cachedSession = await auth()\\n lastFetchTime = now\\n } catch (error) {\\n console.error('Failed to fetch session:', error)\\n return null\\n }\\n }\\n return cachedSession\\n}\\n\\nexport const currentUser = async (): Promise<ExtendedUser | null> => {\\n const session = await getSession()\\n return session?.user ?? null\\n}\\n\\nexport const currentRole = async (): Promise<string | null> => {\\n const session = await getSession()\\n return session?.user?.role ?? null\\n}\\n\\n</final_file_content>\\n\\nIMPORTANT: For any future changes to this file, use the final_file_content shown above as your reference. This content reflects the current state of the file, including any auto-formatting (e.g., if you used single quotes but the formatter converted them to double quotes). Always base your SEARCH/REPLACE operations on this final version to ensure accuracy.\\n\\n\\n\\n<environment_details>\\n# VSCode Visible Files\\nlib/auth.ts\\n\\n# VSCode Open Tabs\\nlib/auth.ts\\n\\n# Current Time\\n2025/1/22 下午5:11:07 (Asia/Shanghai, UTC+8:00)\\n\\n# Current Mode\\nACT MODE\\n</environment_details>\",\"tokensIn\":14981,\"tokensOut\":73,\"cacheWrites\":581,\"cacheReads\":14400,\"cost\":0.00030337999999999996}",
"conversationHistoryIndex": 9
},
{
"ts": 1737537068631,
"type": "say",
"say": "text",
"text": "",
"partial": false,
"conversationHistoryIndex": 10
},
{
"ts": 1737537068761,
"type": "say",
"say": "completion_result",
"text": "已完成代码优化:\n1. 添加了内存缓存机制,减少重复调用 auth()\n2. 添加了错误处理\n3. 完善了类型定义\n4. 解决了类型错误问题\n\n优化后的代码具有更好的性能、更强的健壮性和更好的类型安全性。HAS_CHANGES",
"partial": false,
"conversationHistoryIndex": 10,
"lastCheckpointHash": "ba03330c546aabe2673b238725cd5584813c9101"
},
{
"ts": 1737537071615,
"type": "ask",
"ask": "completion_result",
"text": "",
"conversationHistoryIndex": 11
},
{
"ts": 1737537174467,
"type": "say",
"say": "user_feedback",
"text": "帮我运行起来这个服务器",
"images": [],
"conversationHistoryIndex": 11
},
{
"ts": 1737537174500,
"type": "say",
"say": "api_req_started",
"text": "{\"request\":\"[attempt_completion] Result:\\n\\nThe user has provided feedback on the results. Consider their input to continue the task, and then attempt completion again.\\n<feedback>\\n帮我运行起来这个服务器\\n</feedback>\\n\\n<environment_details>\\n# VSCode Visible Files\\nlib/auth.ts\\n\\n# VSCode Open Tabs\\nlib/auth.ts\\n\\n# Current Time\\n2025/1/22 下午5:12:54 (Asia/Shanghai, UTC+8:00)\\n\\n# Current Mode\\nACT MODE\\n</environment_details>\",\"tokensIn\":15167,\"tokensOut\":73,\"cacheWrites\":191,\"cacheReads\":14976,\"cost\":0.000256844}",
"conversationHistoryIndex": 11
},
{
"ts": 1737537176056,
"type": "say",
"say": "text",
"text": "1. 这是一个 Next.js 项目,从文件结构可以看出\n2. 需要运行开发服务器来启动项目\n3. 使用 execute_command 工具运行开发服务器",
"partial": false,
"conversationHistoryIndex": 12
},
{
"ts": 1737537177951,
"type": "say",
"say": "command",
"text": "pnpm dev",
"conversationHistoryIndex": 12
},
{
"ts": 1737537179473,
"type": "ask",
"ask": "command_output",
"text": "",
"conversationHistoryIndex": 13
},
{
"ts": 1737537179473,
"type": "ask",
"ask": "command_output",
"text": "pnpm> pnpm dev",
"conversationHistoryIndex": 13
},
{
"ts": 1737537179679,
"type": "ask",
"ask": "command_output",
"text": "pnpm dev\u0007",
"conversationHistoryIndex": 13
},
{
"ts": 1737537179679,
"type": "ask",
"ask": "command_output",
"text": "> cherrix@0.1.0 dev /Users/mlhiter/personal-projects/cherrix",
"conversationHistoryIndex": 13
},
{
"ts": 1737537179679,
"type": "ask",
"ask": "command_output",
"text": "> next dev --turbopack",
"conversationHistoryIndex": 13
},
{
"ts": 1737537179679,
"type": "ask",
"ask": "command_output",
"text": "",
"conversationHistoryIndex": 13
},
{
"ts": 1737537180524,
"type": "ask",
"ask": "command_output",
"text": " ▲ Next.js 15.1.3 (Turbopack)",
"conversationHistoryIndex": 13
},
{
"ts": 1737537180524,
"type": "ask",
"ask": "command_output",
"text": " - Local: http://localhost:3000",
"conversationHistoryIndex": 13
},
{
"ts": 1737537180524,
"type": "ask",
"ask": "command_output",
"text": " - Network: http://192.168.0.123:3000",
"conversationHistoryIndex": 13
},
{
"ts": 1737537180524,
"type": "ask",
"ask": "command_output",
"text": " - Environments: .env",
"conversationHistoryIndex": 13
},
{
"ts": 1737537180524,
"type": "ask",
"ask": "command_output",
"text": "",
"conversationHistoryIndex": 13
},
{
"ts": 1737537180524,
"type": "ask",
"ask": "command_output",
"text": " ✓ Starting...",
"conversationHistoryIndex": 13
},
{
"ts": 1737537181139,
"type": "ask",
"ask": "command_output",
"text": " ✓ Compiled in 338ms",
"conversationHistoryIndex": 13
},
{
"ts": 1737537181414,
"type": "ask",
"ask": "command_output",
"text": " ✓ Ready in 1017ms",
"conversationHistoryIndex": 13
}
]
  • checkpoints:检查点信息
  • git:存储记录检查点的影子仓库数据

配置信息存储

存储位置:~/Library/Application\ Support/Code/User/globalStorage/扩展名/settings

  • cline_mcp_settings.json:存储 mcp 的信息,包括 ID、参数,环境变量等信息
{
"mcpServers": {
"npm-docs": {
"command": "node",
"args": [
"/Users/mlhiter/Documents/Cline/MCP/npm-docs-server/build/index.js"
],
"env": {},
"disabled": false,
"autoApprove": [
"get_docs"
]
}
}
}

mcp 服务端代码存储位置

  • 存储位置:~/Documents/Cline/MCP

总结

通过对 Cline 开源项目的深入分析,我们可以看到其优秀的架构设计和工程实现为 AI 辅助开发工具树立了新的标杆。Cline 的模块化架构设计、基于 ShadowGit 的检查点系统、多层级文件存储方案以及安全可控的任务执行机制,为构建智能开发环境提供了重要参考。

文章转载自: Cursor 开源平替 Cline 到底是如何实现的?本文带你刨根问底

#你可能也喜欢这些API文章!