Cursor 的开源平替产品 Cline介绍

作者:youqing · 2025-03-13 · 阅读时间:34分钟

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

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

}

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

// 历史记录
apiConversationHistory: [Anthropic](https://www.explinks.com/api/ai_anthropic_brand).MessageParam[] = [];
clineMessages: ClineMessage[] = [];

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

// 任务执行核心方法
async startTask(task?: string, images?: string[]): Promise {
// 初始化状态
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 {
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 {
// 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 {
return fs.readFile(path, 'utf8');
}

// 文件写入
async writeFile(path: string, content: string): Promise {
await this.ensureDirectoryExists(path);
await fs.writeFile(path, content);
} // 文件替换
async replaceInFile(path: string, diff: string): Promise {
const content = await this.readFile(path);
const newContent = await constructNewFileContent(diff, content);
await this.writeFile(path, newContent);
} // 文件列表
async listFiles(path: string, recursive: boolean): Promise {
if (recursive) {
return this.recursiveListFiles(path);
}
return fs.readdir(path);
}
}

安全机制实现

文件操作安全

class FileOperationApproval {
async checkFileAccess(operation: FileOperation): Promise {
// 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 = new Set([
'rm -rf',
'format',
'shutdown',
// ... 其他危险命令
]);

async validateCommand(command: string): Promise {
// 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 = new Map();

// 创建检查点
async createCheckpoint(): Promise {
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 {
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": "n你给我优化一下代码n" },
{
"type": "text",
"text": "n

# VSCode Visible Filesnlib/auth.tsnn# VSCode Open Tabsnlib/auth.tsnn# Current Timen2025/1/22 下午5:10:08 (Asia/Shanghai, UTC+8:00)nn# Current Working Directory (/Users/mlhiter/personal-projects/cherrix) Filesn.env.templaten.gitignorenauth.config.tsnauth.tsncomponents.jsonneslint.config.mjsnmiddleware.tsnnext-auth.d.tsnnext.config.tsnpackage.jsonnpnpm-lock.yamlnpostcss.config.mjsnprettier.config.jsnREADME.mdnroutes.tsntailwind.config.tsntsconfig.jsonnactions/nactions/login.tsnactions/logout.tsnactions/new-password.tsnactions/new-verification.tsxnactions/register.tsnactions/reset.tsnactions/settings.tsnapp/napp/favicon.iconapp/globals.cssnapp/layout.tsxnapp/page.tsxnapp/(protected)/napp/api/napp/api/auth/napp/api/auth/[...nextauth]/napp/auth/napp/auth/layout.tsxnapp/auth/error/napp/auth/error/page.tsxnapp/auth/login/napp/auth/login/page.tsxnapp/auth/new-password/napp/auth/new-password/page.tsxnapp/auth/new-verification/napp/auth/new-verification/page.tsxnapp/auth/register/napp/auth/register/page.tsxnapp/auth/reset/napp/auth/reset/page.tsxncomponents/ncomponents/form-error.tsxncomponents/form-success.tsxncomponents/user-info.tsxncomponents/auth/ncomponents/auth/back-button.tsxncomponents/auth/card-wrapper.tsxncomponents/auth/error-card.tsxncomponents/auth/header.tsxncomponents/auth/login-button.tsxncomponents/auth/login-form.tsxncomponents/auth/logout-button.tsxncomponents/auth/new-password-form.tsxncomponents/auth/new-verification-form.tsxncomponents/auth/register-form.tsxncomponents/auth/reset-form.tsxncomponents/auth/role-gate.tsxncomponents/auth/social.tsxncomponents/auth/user-button.tsxncomponents/ui/ncomponents/ui/avatar.tsxncomponents/ui/badge.tsxncomponents/ui/button.tsxncomponents/ui/card.tsxncomponents/ui/dialog.tsxncomponents/ui/dropdown-menu.tsxncomponents/ui/form.tsxncomponents/ui/input.tsxncomponents/ui/label.tsxncomponents/ui/select.tsxncomponents/ui/sonner.tsxncomponents/ui/switch.tsxndata/ndata/account.tsndata/password-reset-token.tsndata/two-factor-confirmation.tsndata/two-factor-token.tsndata/user.tsndata/verification-token.tsnhooks/nhooks/use-current-role.tsnhooks/use-current.user.tsnlib/nlib/auth.tsnlib/db.tsnlib/mail.tsnlib/tokens.tsnlib/utils.tsnprisma/nprisma/schema.prismanschemas/nschemas/index.tsnn# Current ModenACT MODEn"
}
]
},
{
"role": "assistant",
"content": [
{
"type": "text",
"text": "n1. 需要优化代码,但用户没有指定具体文件。从环境信息中看到当前打开的文件是 lib/auth.tsn2. 首先需要查看 lib/auth.ts 的内容,了解当前代码结构,才能进行优化n3. 使用 read_file 工具读取 lib/auth.ts 文件内容nnnnlib/auth.tsn"
}
]
},
{
"role": "user",
"content": [
{ "type": "text", "text": "[read_file for 'lib/auth.ts'] Result:" },
{
"type": "text",
"text": "import { auth } from '@/auth'nnexport const currentUser = async () => {n const session = await auth()n return session?.usern}nnexport const currentRole = async () => {n const session = await auth()n return session?.user.rolen}n"
},
{
"type": "text",
"text": "n# VSCode Visible Filesnlib/auth.tsnn# VSCode Open Tabsnlib/auth.tsnn# Current Timen2025/1/22 下午5:10:15 (Asia/Shanghai, UTC+8:00)nn# Current ModenACT MODEn"
}
]
},
{
"role": "assistant",
"content": [
{
"type": "text", "text": "n1. 当前代码存在以下可以优化的地方:n - 两个函数都调用了 auth(),存在重复代码n - 没有错误处理机制n - 返回类型不明确n - 可以添加缓存机制提高性能nn2. 优化方案:n - 提取公共的 auth() 调用n - 添加类型定义n - 添加错误处理n - 添加简单的内存缓存nnnnlib/auth.tsnn<<<<<< {n const session = await auth()n return session?.usern}nnexport const currentRole = async () => {n const session = await auth()n return session?.user.rolen}n=======nimport { auth } from '@/auth'nimport { User } from 'next-auth'nn// Simple in-memory cachenlet cachedSession: { user: User } | null = nullnlet lastFetchTime = 0nconst CACHE_DURATION = 1000 * 60 // 1 minutennasync function getSession() {n const now = Date.now()n if (!cachedSession || now - lastFetchTime > CACHE_DURATION) {n try {n cachedSession = await auth()n lastFetchTime = nown } catch (error) {n console.error('Failed to fetch session:', error)n return nulln }n }n return cachedSessionn}nnexport const currentUser = async (): Promise => {n const session = await getSession()n return session?.user ?? nulln}nnexport const currentRole = async (): Promise => {n const session = await getSession()n return session?.user?.role ?? nulln}n>>>>>>> REPLACEnn" }
]
},
...
]
  • ui_messages.json:存储 UI 上显示的信息

VSCode Visible Filesnlib/auth.tsnn# VSCode Open Tabsnlib/auth.tsnn# Current Timen2025/1/22 下午5:12:54 (Asia/Shanghai, UTC+8:00)nn# Current ModenACT MODEn,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 devu0007",
"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
}
]

# VSCode Visible Filesnlib/auth.tsnn# VSCode Open Tabsnlib/auth.tsnn# Current Timen2025/1/22 下午5:10:08 (Asia/Shanghai, UTC+8:00)nn# Current Working Directory (/Users/mlhiter/personal-projects/cherrix) Filesn.env.templaten.gitignorenauth.config.tsnauth.tsncomponents.jsonneslint.config.mjsnmiddleware.tsnnext-auth.d.tsnnext.config.tsnpackage.jsonnpnpm-lock.yamlnpostcss.config.mjsnprettier.config.jsnREADME.mdnroutes.tsntailwind.config.tsntsconfig.jsonnactions/nactions/login.tsnactions/logout.tsnactions/new-password.tsnactions/new-verification.tsxnactions/register.tsnactions/reset.tsnactions/settings.tsnapp/napp/favicon.iconapp/globals.cssnapp/layout.tsxnapp/page.tsxnapp/(protected)/napp/api/napp/api/auth/napp/api/auth/[...nextauth]/napp/auth/napp/auth/layout.tsxnapp/auth/error/napp/auth/error/page.tsxnapp/auth/login/napp/auth/login/page.tsxnapp/auth/new-password/napp/auth/new-password/page.tsxnapp/auth/new-verification/napp/auth/new-verification/page.tsxnapp/auth/register/napp/auth/register/page.tsxnapp/auth/reset/napp/auth/reset/page.tsxncomponents/ncomponents/form-error.tsxncomponents/form-success.tsxncomponents/user-info.tsxncomponents/auth/ncomponents/auth/back-button.tsxncomponents/auth/card-wrapper.tsxncomponents/auth/error-card.tsxncomponents/auth/header.tsxncomponents/auth/login-button.tsxncomponents/auth/login-form.tsxncomponents/auth/logout-button.tsxncomponents/auth/new-password-form.tsxncomponents/auth/new-verification-form.tsxncomponents/auth/register-form.tsxncomponents/auth/reset-form.tsxncomponents/auth/role-gate.tsxncomponents/auth/social.tsxncomponents/auth/user-button.tsxncomponents/ui/ncomponents/ui/avatar.tsxncomponents/ui/badge.tsxncomponents/ui/button.tsxncomponents/ui/card.tsxncomponents/ui/dialog.tsxncomponents/ui/dropdown-menu.tsxncomponents/ui/form.tsxncomponents/ui/input.tsxncomponents/ui/label.tsxncomponents/ui/select.tsxncomponents/ui/sonner.tsxncomponents/ui/switch.tsxndata/ndata/account.tsndata/password-reset-token.tsndata/two-factor-confirmation.tsndata/two-factor-token.tsndata/user.tsndata/verification-token.tsnhooks/nhooks/use-current-role.tsnhooks/use-current.user.tsnlib/nlib/auth.tsnlib/db.tsnlib/mail.tsnlib/tokens.tsnlib/utils.tsnprisma/nprisma/schema.prismanschemas/nschemas/index.tsnn# Current ModenACT MODEn,tokensIn:11870,tokensOut:87,cacheWrites:11614,cacheReads:256,cost:0.001653904}",
"conversationHistoryIndex": -1
},
{
"ts": 1737537012356,
"type": "say",
"say": "text",
"text": "1. 需要优化代码,但用户没有指定具体文件。从环境信息中看到当前打开的文件是 lib/auth.tsn2. 首先需要查看 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:nnimport { auth } from '@/auth'nnexport const currentUser = async () => {n const session = await auth()n return session?.usern}nnexport const currentRole = async () => {n const session = await auth()n return session?.user.rolen}nnnn

# VSCode Visible Filesnlib/auth.tsnn# VSCode Open Tabsnlib/auth.tsnn# Current Timen2025/1/22 下午5:10:15 (Asia/Shanghai, UTC+8:00)nn# Current ModenACT MODEn,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 - 可以添加缓存机制提高性能nn2. 优化方案:n - 提取公共的 auth() 调用n - 添加类型定义n - 添加错误处理n - 添加简单的内存缓存",
"partial": false,
"conversationHistoryIndex": 2
},
{
"ts": 1737537020284,
"type": "say",
"say": "tool",

"text": "{tool:editedExistingFile,path:lib/auth.ts,content:<<<<<< {n const session = await auth()n return session?.usern}nnexport const currentRole = async () => {n const session = await auth()n return session?.user.rolen}n=======nimport { auth } from '@/auth'nimport { User } from 'next-auth'nn// Simple in-memory cachenlet cachedSession: { user: User } | null = nullnlet lastFetchTime = 0nconst CACHE_DURATION = 1000 * 60 // 1 minutennasync function getSession() {n const now = Date.now()n if (!cachedSession || now - lastFetchTime > CACHE_DURATION) {n try {n cachedSession = await auth()n lastFetchTime = nown } catch (error) {n console.error('Failed to fetch session:', error)n return nulln }n }n return cachedSessionn}nnexport const currentUser = async (): Promise => {n const session = await getSession()n return session?.user ?? nulln}nnexport const currentRole = async (): Promise => {n const session = await getSession()n return session?.user?.role ?? nulln}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:nnThe content was successfully saved to lib/auth.ts.nnHere is the full, updated content of the file that was saved:nnnimport { auth } from '@/auth'nimport { User } from 'next-auth'nn// Simple in-memory cachenlet cachedSession: { user: User } | null = nullnlet lastFetchTime = 0nconst CACHE_DURATION = 1000 * 60 // 1 minutennasync function getSession() {n const now = Date.now()n if (!cachedSession || now - lastFetchTime > CACHE_DURATION) {n try {n cachedSession = await auth()n lastFetchTime = nown } catch (error) {n console.error('Failed to fetch session:', error)n return nulln }n }n return cachedSessionn}nnexport const currentUser = async (): Promise => {n const session = await getSession()n return session?.user ?? nulln}nnexport const currentRole = async (): Promise => {n const session = await getSession()n return session?.user?.role ?? nulln}nnnnIMPORTANT: 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.nnnnNew problems detected after saving the file:nlib/auth.tsn- [ts Error] Line 30: 类型“User”上不存在属性“role”。nnn

# VSCode Visible Filesnlib/auth.tsnn# VSCode Open Tabsnlib/auth.tsnn# Current Timen2025/1/22 下午5:10:31 (Asia/Shanghai, UTC+8:00)nn# Current ModenACT MODEn,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:<<<<<<< SEARCHnimport { DefaultSession } from next-auth;nndeclare 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:nnThe tool execution failed with the following error:nnThe SEARCH block:nimport { DefaultSession } from next-auth;nndeclare module next-auth {n interface Session {n user: {n id: string;n } & DefaultSession[user];n }n}n...does not match anything in the file.nnThis 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.nnThe file was reverted to its original state:nnnimport { type DefaultSession } from 'next-auth'nimport { UserRole } from '@prisma/client'nexport type ExtendedUser = DefaultSession['user'] & {n role: UserRolen isTwoFactorEnabled: booleann isOAuth: booleann}nndeclare module 'next-auth' {n interface Session {n user: ExtendedUsern }n}nnnnTry 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.)nnnn

# VSCode Visible Filesnlib/auth.tsnn# VSCode Open Tabsnlib/auth.tsnn# Current Timen2025/1/22 下午5:10:37 (Asia/Shanghai, UTC+8:00)nn# Current ModenACT MODEn,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 中的类型定义以使用 ExtendedUsern3. 使用 replace_in_file 工具更新 lib/auth.ts 的类型定义",
"partial": false,
"conversationHistoryIndex": 6
},
{
"ts": 1737537041539,
"type": "say",
"say": "tool",

"text": "{tool:editedExistingFile,path:lib/auth.ts,content:<<<<<< CACHE_DURATION) {n try {n cachedSession = await auth()n lastFetchTime = nown } catch (error) {n console.error('Failed to fetch session:', error)n return nulln }n }n return cachedSessionn}nnexport const currentUser = async (): Promise => {n const session = await getSession()n return session?.user ?? nulln}nnexport const currentRole = async (): Promise => {n const session = await getSession()n return session?.user?.role ?? nulln}n=======nimport { auth } from '@/auth'nimport { ExtendedUser } from 'next-auth'nn// Simple in-memory cachenlet cachedSession: { user: ExtendedUser } | null = nullnlet lastFetchTime = 0nconst CACHE_DURATION = 1000 * 60 // 1 minutennasync function getSession() {n const now = Date.now()n if (!cachedSession || now - lastFetchTime > CACHE_DURATION) {n try {n cachedSession = await auth()n lastFetchTime = nown } catch (error) {n console.error('Failed to fetch session:', error)n return nulln }n }n return cachedSessionn}nnexport const currentUser = async (): Promise => {n const session = await getSession()n return session?.user ?? nulln}nnexport const currentRole = async (): Promise => {n const session = await getSession()n return session?.user?.role ?? nulln}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:nnThe content was successfully saved to lib/auth.ts.nnHere is the full, updated content of the file that was saved:nnnimport { auth } from '@/auth'nimport { ExtendedUser } from 'next-auth'nn// Simple in-memory cachenlet cachedSession: { user: ExtendedUser } | null = nullnlet lastFetchTime = 0nconst CACHE_DURATION = 1000 * 60 // 1 minutennasync function getSession() {n const now = Date.now()n if (!cachedSession || now - lastFetchTime > CACHE_DURATION) {n try {n cachedSession = await auth()n lastFetchTime = nown } catch (error) {n console.error('Failed to fetch session:', error)n return nulln }n }n return cachedSessionn}nnexport const currentUser = async (): Promise => {n const session = await getSession()n return session?.user ?? nulln}nnexport const currentRole = async (): Promise => {n const session = await getSession()n return session?.user?.role ?? nulln}nnnnIMPORTANT: 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.nnnnNew problems detected after saving the file:nlib/auth.tsn- [ts Error] Line 2: 模块 \next-auth\ 没有导出的成员 ExtendedUser。你是想改用 import ExtendedUser from next-auth\ 吗?nnn

# VSCode Visible Filesnlib/auth.tsnn# VSCode Open Tabsnlib/auth.tsnn# Current Timen2025/1/22 下午5:10:56 (Asia/Shanghai, UTC+8:00)nn# Current ModenACT MODEn,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:<<<<<<>>>>>> 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:nnThe content was successfully saved to lib/auth.ts.nnHere is the full, updated content of the file that was saved:nnnimport { auth } from '@/auth'nimport type { ExtendedUser } from '@/next-auth.d.ts'nn// Simple in-memory cachenlet cachedSession: { user: ExtendedUser } | null = nullnlet lastFetchTime = 0nconst CACHE_DURATION = 1000 * 60 // 1 minutennasync function getSession() {n const now = Date.now()n if (!cachedSession || now - lastFetchTime > CACHE_DURATION) {n try {n cachedSession = await auth()n lastFetchTime = nown } catch (error) {n console.error('Failed to fetch session:', error)n return nulln }n }n return cachedSessionn}nnexport const currentUser = async (): Promise => {n const session = await getSession()n return session?.user ?? nulln}nnexport const currentRole = async (): Promise => {n const session = await getSession()n return session?.user?.role ?? nulln}nnnnIMPORTANT: 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.nnnnn

# VSCode Visible Filesnlib/auth.tsnn# VSCode Open Tabsnlib/auth.tsnn# Current Timen2025/1/22 下午5:11:07 (Asia/Shanghai, UTC+8:00)nn# Current ModenACT MODEn,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. 解决了类型错误问题nn优化后的代码具有更好的性能、更强的健壮性和更好的类型安全性。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:nnThe user has provided feedback on the results. Consider their input to continue the task, and then attempt completion again.nn帮我运行起来这个服务器nnnn

# VSCode Visible Filesnlib/auth.tsnn# VSCode Open Tabsnlib/auth.tsnn# Current Timen2025/1/22 下午5:12:54 (Asia/Shanghai, UTC+8:00)nn# Current ModenACT MODEn,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 devu0007",
"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 到底是如何实现的?本文带你刨根问底