深入 Cursor 的上下文机制、vision prefill 物理代价、Agent 模式的 token 消耗路径,以及一个极简 skill 如何用文件系统绕过 LLM attachment 模式。

TL;DR

  • Cursor 聊天里拖 10 张截图卡 60 秒通常不是网络慢,而是模型 vision prefill + 宿主 attachment 机制叠加后的结果
  • 图片一旦作为 attachment 进入对话,后续轮次通常会继续携带这批视觉输入,token 和首字延迟持续累加
  • 我写了一个极简 skill 叫 img2md,把图片从 “attachment + 上下文驻留” 切换到 “本地文件 + 按需 Read”
  • 双模式触发:「上传图片」走快速暂存就停;「上传图片生成 markdown」才走完整 OCR
  • 当前实现只有 5 个文件SKILL.md、Cursor 触发规则和 3 个脚本;脚本约 500 行,规则文档另约 300 行
  • 真正能优化的是工作流,不是技术:不要拖图,输入路径

口径说明

本文的耗时和 token 数是我在自己机器、自己的 Cursor 配置、同一批截图上的观测值,不是通用 benchmark。Cursor、模型供应商、账号套餐、图片尺寸、网络、缓存命中率都会改变绝对数字。本文真正想证明的是一个结构性判断:把图片作为可丢弃的文件路径处理,通常比把图片作为聊天 attachment 常驻上下文更稳定、更便宜、更可控

文中把图片 attachment 简化描述为「进入 prompt / 粘性层」。不同宿主的内部实现未必真的把图片以同一种 base64 字符串拼进文本 prompt;但只要它在后续请求里继续作为模型可见的视觉输入存在,工程代价就是类似的:视觉 token 要被计费、prefill 要等待、上下文要变重。


一、痛点是怎么发现的

一个真实的早晨

我习惯把屏幕上看到的内容(书的章节)截下来,喂给 Cursor 让它转成我自己的笔记 markdown。两三张图的时候没什么感觉,一切都很顺。

直到某天我截了一篇文章的 12 张连续滚动截图,全部拖进 Cursor 输入框,敲下「帮我转成 markdown」回车。

然后我等了 47 秒 才看到第一个字。

接下来更糟。我跟它对话 5 轮(让它修订标题、调整层级、补段落),每一轮我都得等差不多 30 秒才看到回复开始。任务做完打开 Cursor 的 usage 面板一看——这一次对话烧了 6 万 token,而我感觉自己只问了 5 个简单问题。

这不对劲。从我做的事情看:12 张图 + 一段 markdown 输出 + 5 轮文字修订,模型工作量按理说不该这么贵、这么慢。


二、Cursor 内部:每次按下回车,到底发生了什么

要理解为什么卡、为什么贵,必须先看清 Cursor 的请求机制。很多人有个朴素的想象:模型像人一样「记住」了聊天内容,下次直接调用记忆。完全不是这样。

LLM 本质是 stateless 的

每一次你按回车,Cursor 实际上做的事情是:

  1. 从这次会话开头到现在的所有内容打包成一个巨大的 prompt
  2. 把这个完整 prompt 整个发给 Anthropic / OpenAI 的 API
  3. 模型从头读一遍整个 prompt,然后开始输出
  4. 输出完毕,追加进历史,作为下一轮 prompt 的一部分

所以「记忆」其实是每一轮重新喂一遍历史。模型没有 RAM,只有 prompt。

Cursor 每次请求的 prompt 构成

一次 API 请求的 prompt 并不只是"你说的那句话",而是多层叠加的结果:

在这里插入图片描述

固定层(每轮相对稳定,约 5,000–10,000 token):

  • 系统提示:Cursor 内置的角色定义和行为规则,约 2,000 token
  • Cursor Rules:你在 .cursorrules.mdc 文件里写的项目规则,每条规则 200–500 token
  • 工具 schema:Read、Write、Bash、Search 等工具的 JSON schema 定义,约 3,000–5,000 token。每增加一个工具就多一段 schema

动态层(随对话时间线性增长):

  • 文件 context:Cursor 自动识别的相关文件(当前打开的文件、import 链、@mention 的文件)。一个中型项目打开 3 个文件,这一层就可能达到 5,000–20,000 token
  • 对话历史:所有历史轮次的消息 + 每次工具调用的结果。随着对话轮次增加线性膨胀。当总长超过 context window 时,Cursor 会从最早的历史开始截断

粘性层(图片 attachment——核心痛点):

  • 你拖进来的图片会以宿主可转发给模型的多模态 attachment 形式保留在会话里,直到你新开会话或手动删除。后续轮次通常仍会把它作为模型可见输入携带过去

本轮输入(你这次说的话,约 100 token)

Agent 模式的内部循环

Cursor 的 Agent 模式(Composer + Agent)比普通 Chat 贵得多,因为它有一个内部循环:

在这里插入图片描述

用户发一句话,模型可能需要调用多次工具才能完成任务。每一次工具调用都是一次完整的 API 请求,prompt 比上一次更大(因为工具结果被追加进了历史)。

一句「帮我把这个组件重构一下」,背后可能是:

  1. Read 当前文件(API 请求 1)
  2. 发现需要看 import,Read 另一个文件(API 请求 2)
  3. Write 修改(API 请求 3)
  4. 检查有没有 TypeScript 报错,Bash 跑 tsc(API 请求 4)
  5. 修复报错(API 请求 5)

5 次 API 请求,每次 prompt 都比上次更大,你感受到的是"等了一会",但实际上烧了 5 次完整的 prefill 成本。

Attachment 的粘性代价

现在你理解了 prompt 的组成,"拖图很慢"的根因就很清楚了:

一旦你拖进去一张图,这张图就进了粘性层,会在当前会话里持续影响后续请求,直到新开会话或移除 attachment:

  • 第 1 轮:prompt 大小 = 固定层 + 历史(0) + 图片×10 + 用户消息
  • 第 2 轮:prompt 大小 = 固定层 + 历史(第1轮) + 图片×10 + 用户消息
  • 第 3 轮:prompt 大小 = 固定层 + 历史(前2轮) + 图片×10 + 用户消息
  • ……

每一轮,那 10 张图都可能继续成为模型输入的一部分——即使你的新问题跟图毫无关系(比如「再短一点」「换个标题」)。这就是聊天框 attachment 模式的核心 cost:把一次性输入变成了持续性输入

在这里插入图片描述

Vision prefill 的物理代价

以 Anthropic 的图片 token 估算口径为例,常见截图(约 1500–2000px 长边)经常落在 千级 token/张。10 张大截图很容易变成上万视觉 token,相当于把一大段长文本塞进每次请求的输入里。

模型在生成第一个输出 token 之前,必须完成 prefill——把整个 prompt 跑一遍前向传播,建立所有 token 的 attention 状态。Prefill 时间正比于 prompt 长度,对视觉 token 尤其敏感。

我的观测数字(Claude Sonnet 级别,12 张滚动截图那批数据的同类图片):

图片数量 大致 prefill 时间 你感受到的"首字延迟"
0 张(纯文本几百 token) < 0.5 秒 瞬时
2 张 3–5 秒 5–8 秒
5 张 8–15 秒 12–20 秒
10 张 25–45 秒 30–50 秒
30 张 经常超时 N/A

如果 attachment 在后续轮次仍被携带,这个延迟就会反复出现。5 轮对话可能变成 5 次多图 prefill。

Prompt caching 救不了多少

Anthropic 提供 prompt caching:连续两次请求的 prompt 前缀完全相同,就可以复用缓存,省掉重复的 input token 费用。听起来很美,但实际场景里经常 miss:

  • 你加一条新消息,历史部分发生变化,前缀就不再完全匹配
  • Cursor 内部 prompt 组装会插入动态内容(时间戳、文件修改时间等),导致前缀漂移
  • Cache TTL 是 5 分钟,稍微停顿一下就过期了
  • Prompt cache 主要优化重复前缀的计费和部分服务端处理成本;它不是「把这轮视觉输入变成免费、零延迟」的开关。你感受到的首字延迟仍然会受当前请求体量影响

我观察自己的几次多图对话,cache hit rate 经常在 60% 以下。即便命中,也不能把多图请求变成纯文本请求的交互手感。

Cursor 容易烧 token 的 6 个加速器

汇总下来,和直接调 API 相比,Cursor 用户面对的是 6 个叠加的 token 加速器:

机制 原因 节省手段
Attachment 持久性 拖入的图片每轮都在 改用文件路径 + skill
文件 context 自动注入 打开的文件、import 自动进 prompt 精准 @,关闭 Auto-context
Agent 工具调用循环 每次工具调用 = 一次新 API 请求 一次只做一件事,减少工具调用次数
工具 schema 本身 所有工具的定义都在 prompt 里 减少不必要的 Cursor 插件
Thinking 模式 思考输出按 output 价格计费 非推理任务关闭
失败重试 工具调用失败自动重试,prompt 再次膨胀 写更精确的 prompt 减少失败率

单看每一条都合理,叠加起来,一次「看起来很简单」的对话可能悄无声息地烧掉数万 token。


三、设计目标:skill 能解决什么,不能解决什么

理解了上面的原理,我才能清醒地想:哪些问题 skill 能解决,哪些是 skill 改不了的?

Skill 的边界

卡顿 / 烧钱环节 谁负责 Skill 能管吗
拖图 → Cursor UI 生成缩略图 Cursor 前端
上传至 Cursor 中继服务器 Cursor 客户端
中继转发至模型 API Cursor 后端
Vision prefill 的计算时间 模型层物理限制
每轮 attachment 重复 prefill 模型协议本身
图片已在本地硬盘后的暂存、重命名 Skill 工具层
引导用户改用文件路径而非 attachment Skill 设计 + trigger

Skill 跑在 agent 循环里,在 prefill 完成之后才被调用。你看到 skill 启动的那一刻,慢的部分早就发生过了。Skill 没有任何办法加速 Cursor 前端的上传或模型的 prefill——这些是宿主层和物理层的事。

但 skill 能做一件结构性的事:把工作流从"拖图 + attachment"切换到"路径 + 按需 Read"

关键 insight:图片到达模型的两条路径

在这里插入图片描述

路径 A(attachment,拖入对话框):

  • 图片以宿主的多模态 attachment / payload 形式进入模型输入
  • 进入粘性 context,跟随每一轮 prefill
  • 每轮 token 成本 = 图片完整 token 数(无论这轮你是否用它)

路径 B(文件路径 + skill 的 Read 工具):

  • 图片以文件路径字符串形式在 context 里(几十 byte)
  • 模型只在真正需要看图的那一轮调用 Read 工具
  • 后续轮次不再 Read = 图片不进 context,token 成本归零
  • 多张图可以一次 batch 并行 Read,且每张只 Read 一次

路径 B 的优势是结构性的:图片变成了一次性消耗品,而不是持续性负担img2md skill 的所有设计都围绕这一个 insight 展开。


四、设计思想:少量文件能做什么

最终的 skill 只有 5 个文件:

~/.cursor/skills/img2md/
├── SKILL.md                 # 给 agent 的指令(一切的入口)
├── cursor/img2md.mdc        # Cursor IDE 的触发规则
└── scripts/
    ├── intake.sh            # 把图片 cp 到 inbox 并命名为 NN.ext
    ├── embed.py             # 把 <!-- EMBED/CROP --> 标记物化为真实图片
    └── log.py               # JSONL 日志(可选)

截至这版,3 个脚本约 500 行,SKILL.md 约 300 多行,Cursor 触发规则约 50 行。真正执行逻辑仍然很薄:bash + Python 标准库,只有 CROP 模式需要 Pillow。这个口径比「400 行代码」更诚实,也更能说明重点:复杂度主要在 agent 行为约束,而不是在脚本流水线。

设计原则 1:极简,少即是多

第一个版本(v6)我做得很复杂:子 agent 分发、多引擎 OCR 融合(macOS Vision + PaddleOCR + 智谱 GLM-4V)、空间 stitching、内容寻址的 hints 缓存、Chunk 规划器……600 多行 Python,复杂的并发控制。

跑了几次之后发现:这些复杂性全是 self-imposed 的

  • 子 agent 分发并不快——子 agent 也要 prefill,整体反而更慢
  • 多引擎融合的"准确率提升"在 LLM vision 足够强的今天,价值接近零
  • Stitching + 去重逻辑写了 300 行,但模型直接看相邻两张图自己就会处理重叠
  • 缓存层经常 invalidate,反而增加了调试成本

最让我清醒的一个观察:这一切的总耗时比直接让 Cursor 读图慢得多,因为我手工拼装了一个比 LLM 内部更低效的流水线。

v9 我把这些全砍了。一个主 agent 直接看所有图、直接写 markdown,没有任何中间产物。脚本流水线从复杂多阶段退回到 3 个小脚本,速度反而快 3–5 倍。

经验:在 LLM agent 这个世界里,你能托付给模型的事比你想象的多,能用脚本"优化"的事比你想象的少。

设计原则 2:双模式,解耦"暂存"和"OCR"

最近一次迭代(v9.4)加了双模式触发,这是被真实使用场景逼出来的。

最初我只考虑了"图片转 markdown"这一个场景。但后来发现,我有时只是想把一批截图快速暂存好,让 Cursor 去写代码(根据 UI 截图复刻界面),而不是生成 markdown。Cursor 拖图同样慢,但 skill 之前非得写 markdown 才肯退出,显得很愚蠢。

intake.sh 本质上是个独立工具——它是一个本地 cp 循环 + 命名规范化器。OCR 只是它的可选下游。把两者绑死,等于限制了 skill 的复用。

所以 v9.4 拆成两个模式:

用户说的话 触发模式 动作
“上传图片” / “上传图片生成代码” / 只输入路径 Mode A(快速暂存) intake.sh → 报 $INBOX 路径 → ,等下一句指令
“上传图片,生成 markdown” / “OCR 这些图片” / “img2md” Mode B(暂存 + OCR) intake.sh → Read 所有图 → Write .mdembed.py → 报告

判断逻辑一条:消息里有没有 markdown / md / OCR / 转文字。有 → Mode B;没有 → Mode A。

Mode A 是安全默认值——只是本地 cp,不会产生错误的 .md 文件,后续 Cursor 可以从 $INBOX 直接读图做任何事。

设计原则 3:用文件路径绕过 attachment

整个 skill 的物理价值都在 intake.sh 这段逻辑上:

# 核心逻辑(简化版)
TOTAL_IN=${#INPUTS[@]}
if   (( TOTAL_IN >= 1000 )); then PAD_W=4
elif (( TOTAL_IN >= 100  )); then PAD_W=3
else                              PAD_W=2
fi

for src in "${INPUTS[@]}"; do
  pad=$(printf "%0${PAD_W}d" "$i")
  cp "$src" "$CURRENT/$pad.$ext"
  i=$((i + 1))
done

就是个 cp 循环,加几个工程细节(自动 zero-padding、归档轮转、project 隔离)。没有任何 OCR、没有任何 LLM 调用

它做的事是 Cursor 拖拽上传很难做到的:让图片先停在本地文件系统里,而不是一开始就进入 prompt 的粘性层。用户说「上传图片,目录 ~/Downloads/」,Cursor 收到的 prompt 只是一段几百字符的文字。首轮 prefill 接近纯文本请求,intake.sh 只做本地复制,随后回报 INBOX 路径;后续是否 Read 图片,由下一步任务决定。

设计原则 4:Fidelity rule —— 禁止想当然

这一条跟性能无关,但跟产品质量有关。

OCR 任务对模型来说是个特别危险的情境:它看得见原文,于是会自然产生"我能写得更好"的冲动。模型常见的违规行为(每一条都是真实发生过的):

  • 把引号里的字改掉("感觉省了不少""感觉省时间了"
  • 给原本无标签的 bullet 加分类标签(ROI 量化:度量缺失:
  • 把无序 bullet 改成有序的 1. 2. 3.
  • 漏一句、合并两段、在节末凭空加"总结/承接"句
  • 同义词替换、调换词序(走了 6 个 Phase6 阶段的
  • 增减标点(中文 ——没有数据——没有数据。

这些都是「改得更通顺」的好意,但在「OCR 转录」这个语境下全是 bug——用户要的是与原图字符一致的 markdown,不是模型的二次创作。

v9.3 加了三层防御:

  1. 强禁止表:SKILL.md 顶部 13 类禁止行为,每条配一个 source→bad 的具体例子
  2. Pre-Write checklist:写文件前自检 11 项(bullet 数量、引号内容、段落终止句字符等)
  3. Post-Write self-verify:写完后重新 Read 原图,随机挑 2 段做人工 diff,brief 报告里必须出现 SELF_VERIFY: checked S, found V, fixed V,缺这行视为协议违规

效果:paraphrase 违规率从 v9.2 的「一节文字里 12 处违规」降到 v9.3 的接近零。

设计原则 5:把失败显式化,而不是假装没有失败

专业的工具设计不能只描述 happy path。img2md 现在保留了几个很朴素但关键的失败出口:

  • intake.sh 找不到目录、目录里没有图片、所有输入都缺失时直接退出,不创建空结果
  • 上一批 current/ 会先归档到 archive/,避免新批次覆盖旧批次;默认只保留最近 30 批,磁盘紧张时可用 IMG2MD_MAX_ARCHIVE 调小
  • 文件名使用自动 zero-padding,100 张以上会变成 001.png 这类宽度,保证 ls 排序和阅读顺序一致
  • embed.py 是幂等的:没有 marker 时是 no-op;marker 出错时保留原 HTML comment,不破坏 markdown 主体
  • CROP 依赖 Pillow,但只有实际出现 crop marker 才需要导入;缺依赖时降级为保留 marker 和错误报告
  • log.py 只做 best-effort JSONL 日志,失败不会阻断主流程

这类设计看起来不酷,但它决定了工具能不能长期使用:失败时要留下可修复的现场,而不是半写文件、吞掉错误、或者让 agent 凭感觉继续往下编。

设计原则 6:隐私和数据边界要说清楚

这个 skill 不是“本地 OCR”。Mode A 只是本地暂存,不把图片交给模型;但 Mode B 一旦进入 OCR,agent 会 Read 图片,图片内容仍然会作为视觉输入发送给所选模型供应商。它绕开的只是 Cursor 聊天 attachment 的常驻路径,不是模型推理本身。

因此它适合处理“我本来就准备发给模型看的截图”,不适合处理不允许进入第三方模型的敏感图片。真正需要离线 OCR 的场景,应该换成本地 OCR 引擎,或者把 Mode B 禁用,只使用 Mode A 做本地文件整理。


五、架构演进史(教训汇编)

倒过来看 9 个版本,最有价值的反而是那些被我删掉的东西

v1–v5:探索期

最早是命令行工具,用 Vision/Paddle 做本地 OCR,识别率一般,需要人工校对。核心问题是:本地 OCR 在中文/混排上准确率不够,让 LLM 直接看图反而更准

v6:复杂巅峰

引入 LLM vision 作为最终决策者,本地 OCR 退化成"hints"。同时引入子 agent fan-out、多引擎融合、空间 stitching、chunk 规划器。最复杂的一版,也是最慢的一版。

v7–v8:开始砍

v7 的草稿就叫"lightweight redesign"——直接砍 60% 的代码。v8 实现了大部分简化。

v9:极简回归

砍掉所有中间表示(hints、sections、manifest),主 agent 直接看图直接写 md。v9.2 几乎是这个 skill 的"正确形态"。

v9.3:补 fidelity 漏洞

发现 v9.2 在 paraphrase 上失守,加了三层防御机制。

v9.4:双模式

把"暂存"和"OCR"解耦成正交工具。这是产品视角的一次升级,技术上没有大改动,但对用户体验是质变。

教训清单

  1. 工程复杂性是负债:你写的每一行代码都要在未来的某次需求变更里付出维护成本
  2. LLM 能做的事远比你想象的多:图片去重、版式识别、内容理解,让模型直接看图反而更好
  3. 可用性 > 准确率:v6 的"99% 准确率"没人用,v9 的"95% 准确率 + 10 倍速度"才有人用
  4. 触发匹配决定一切:SKILL.md 的 description 字段是 skill 唯一的"营销文案",写不好就不会被调用
  5. 少加抽象,多删代码:每次想加新功能时,先问"有没有可以一起删的"

六、效果对比

同一个场景:12 张滚动截图转 markdown。

为了避免把体感当结论,我用的是同一批图片、同一个输出目标、同一个模型档位做对比。记录口径很简单:

  1. 新开会话,清空历史干扰
  2. 记录从回车到第一个输出 token 出现的时间,也就是用户真实感受到的首字延迟
  3. 记录任务完成后的 usage 面板 token 数
  4. 每组至少跑两次,剔除明显的网络异常或服务端排队异常
  5. 只比较同一批图片在「拖入聊天框」和「路径 + skill」两条路径下的差异

这不是严格实验室 benchmark,但足够回答本文的问题:慢点到底来自 OCR 本身,还是来自图片进入上下文的方式。

阶段 拖图模式(路径 A) 文件路径模式(路径 B + skill)
上传 / 暂存 30–50 秒(上传 + 中继 + 转发) < 100ms(本地 cp)
首轮 prefill 30–45 秒 1–2 秒(prompt 只有文字)
Skill 内部 Read 处理 25–35 秒 25–35 秒
总耗时(首轮) 85–130 秒 27–37 秒
后续每轮(修订标题等) 可能再次携带图片 prefill(30–45 秒) 接近纯文本轮次(图不在 context,除非再次 Read)
5 轮对话总 token 消耗 ~60,000 token ~12,000 token

在这批样本上,速度提升约 3–4 倍,token 消耗降低约 5 倍

但这些数字的根本来源不是 skill 的"优化"——而是 skill 把用户引导到了一条结构性更优的工作流。同样的模型、同样的图片、同样的任务,只是图片到达模型的路径变了。


七、如何最省 token 使用 Cursor

基于上面对 Cursor 内部机制的理解,这里整理出按影响力排序的节省策略:

在这里插入图片描述

高优先级(影响 50–80%)

1. 不拖图,用文件路径 + skill

这是影响最大的单点优化。从路径 A 切换到路径 B,token 消耗立刻下降 5 倍(以 12 张图为例)。操作很简单:

  • macOS:Cmd + Shift + 5 把截图存到固定目录(如 ~/Screenshots/
  • 对 Cursor 说「上传图片,目录 ~/Screenshots/」,skill 秒级完成暂存

2. 任务完成即开新会话

这是比任何优化都管用的手段——直接清空所有粘性 context 和对话历史。Cursor 的 context 是单向增长的,不存在"聊着聊着会变短"的情况,唯一的办法是开新会话。

经验法则:一个任务 = 一个会话,任务做完就关。下一个任务重新开,不要把多个不相关的任务拼在同一个会话里。

3. 精准 @文件,不用 @codebase

@codebase 会让 Cursor 扫描整个仓库并注入大量相关文件,每次请求额外带来 10,000–50,000 token 的文件 context。除非真的需要全局搜索,否则精确 @ComponentA.tsx @utils.ts@codebase 省很多。

中优先级(影响 20–40%)

4. 截图前先压缩

如果有时必须拖图(比如临时看一张图),先把图压缩再拖:

  • macOS 预览 → 导出 → JPEG 80% 品质,分辨率缩到 1200px
  • 一张 4MB 的 PNG 压缩后变成 400KB,对应的 token 数同比减少 ~80%

5. 关闭 Cursor 的 Auto-context

Cursor 默认会自动检测相关文件并注入 context。不同版本的设置入口会变化,但思路是一样的:降低自动注入的激进程度,改为尽量手动 @ 文件。对 token 消耗通常有稳定影响。

6. 一次 prompt 只做一件事

每增加一个需求,就多一轮工具调用,prompt 就再膨胀一次。「帮我重构这个组件、顺便加单测、再把 README 更新一下」听起来是一句话,实际是 3 个独立任务,触发很长的工具调用链。拆开做,每个任务一个新会话,整体消耗反而更少。

低优先级(影响 5–15%)

7. 精简 .cursorrules / .mdc

每条 Cursor Rule 都会进入固定层,持续消耗 token。检查一下你的 .cursorrulesimg2md.mdc 等文件,把从未被用到的规则删掉。一般项目里 30–50% 的规则是"当时加了但后来没用上"的。

8. 简单任务选小模型

Cursor 支持切换模型。Haiku 的价格约为 Sonnet 的 1/10,速度更快。重命名变量、格式化代码、写简单的 utility 函数等任务,Haiku 完全胜任,不需要用 Sonnet 或 Opus。

9. 非推理任务关闭 Thinking 模式

如果开启了 Thinking 模式(Extended thinking),模型先输出一段思考过程,这段思考按 output token 价格计费(比 input 贵 3–5 倍)。大部分日常编程任务不需要 Thinking,关掉能节省 20–40% 的 output 成本。


八、另一条解:Claude Code CLI 为什么更准、更省

写完这个 skill 之后我有个反向感受:与其在 Cursor 里堆 skill 和 rule 绕开它的开销,不如直接换工具。我现在做不依赖 IDE 的任务(写脚本、批量改文件、写文档、做 OCR)几乎全部移到了 Claude Code CLI。

Claude Code 是 Anthropic 推出的命令行 agent。安装方式以官方文档为准;安装后在任意目录敲 claude 就进入。表面上看跟 Cursor 一样都是"和模型对话写代码",但底层 prompt 组装方式差很多。

在这里插入图片描述

工作原理:少了一整个 IDE chrome 层

Cursor 是 VSCode fork + 模型层,每一次 prompt 里都包含 IDE 状态:当前打开的文件、光标位置、recent files、import 链分析、Codebase indexing 结果。这些是好用的,但它们默认就在 prompt 里,无论你这次问题是否需要它们。

Claude Code 是纯 CLI agent,它的认知模型只有 3 件事:

  1. 当前工作目录pwd 是它的边界,不会越界扫别的目录
  2. CLAUDE.md:当前目录(以及上级目录)的 CLAUDE.md 文件是项目级 system prompt,由你显式写,没有 IDE 自动塞进来的隐式内容
  3. 本轮对话:你说的话 + 它调用过的工具结果

没有 IDE 自动注入文件,没有 attachment 机制(图片必须以路径方式传入,比如 分析 ~/Desktop/a.png 这张图),没有 tab 补全产生的隐式消息。

为什么更准确

1. Context 边界由你显式控制,不会被 IDE 偷偷扩张

Cursor 里你打开了 5 个文件,每次请求就可能带上这 5 个文件。中型项目 import 链一展开就是 1–2 万 token 的"背景噪音"——模型要花注意力区分哪些是相关的、哪些只是恰好打开了。Claude Code 不会自动 Read 任何文件,你说一句话就只有这句话。它需要看文件时主动调用 Read 工具,这一刻你是知道它在读什么的

2. 工具调用更直接

Cursor 的 Read/Write 工具是包了一层 IDE 的(要触发 diff 视图、文件 watcher 之类)。Claude Code 直接调系统的文件系统,工具结果回来就是文件内容,没有中间层。失败率明显低,重试次数少,整体决策链更短。

3. 没有粘性 attachment,每轮都是新鲜的

这是和本文主题最相关的一点。Cursor 拖图后图片容易在当前会话里持续存在;Claude Code 的常规用法是"用文件路径传入 → 模型按需 Read → 用完即丢"。结构上就是本文路径 B 的工作流,不需要 skill 绕一圈。

4. CLAUDE.md 让"重复说明"只说一次

我在 ~/.claude/CLAUDE.md 里写了一段「作图规范」(颜色、d2 语法、命名规则)。每次进 Claude Code 它都自动加载这段,我再也不用每次重申"画图用 d2、配色用这套调色板"。在 Cursor 里同样的事情要靠 .cursorrules,但效果不如直接读 CLAUDE.md 来得稳定。

为什么更省 token

在我的一次对比里,同一个任务(让 agent 改一个 200 行的 Python 脚本,跑测试,修 bug),Cursor Agent 模式吃掉 ~45,000 token,Claude Code 大概 ~12,000 token,差 3–4 倍。具体来源:

开销项 Cursor Claude Code
系统提示 ~2,000 token(含 IDE 集成相关) ~1,200 token(精简)
Cursor Rules / CLAUDE.md 每条规则 200–500 token,且经常加冗余规则 你写多少就是多少,普遍更紧凑
工具 schema 内置工具多(搜索、编辑、终端、补全等) 工具更少更聚焦(Read/Edit/Bash/Grep/Glob/Task)
自动注入文件 打开的文件 + import 链 5k–20k 默认接近 0(通常需要显式 Read)
图片 attachment 拖入后容易持续携带,10 张图可带来上万视觉 token 常规路径是文件路径,天然接近路径模式
历史累积 IDE 会话长,倾向于"一直聊下去" CLI 天然一任务一会话,结束就 exit

最后一条最被低估。Cursor 的"会话"是 IDE 里的一个标签页,关掉的代价很高(会丢失 composer 状态),所以大家倾向于在同一个会话里做完一整天的工作。结果就是粘性 context 越攒越大。Claude Code 用完一退出,下次重新进就是干净的——这个 ergonomics 上的差别带来的 token 节省比任何技术优化都大。

几个最常用的 Claude Code 命令

# 进入当前目录的 agent 会话
claude

# 一次性 prompt(不进入交互模式)
claude -p "解释这个仓库的目录结构"

# 接着上一次会话继续
claude -c

# 用某个特定模型(具体 model id 以官方文档为准)
claude --model <model-id>

# 跳过权限确认(用于自动化脚本,谨慎使用)
claude --dangerously-skip-permissions

# 调用某个 skill(会话内)
/img2md 上传图片,目录 ~/Downloads/

/init 会让 agent 帮你扫描当前项目并生成一份 CLAUDE.md 草稿——非常推荐每个项目跑一次,相当于一次性建立"项目级 system prompt"。

Claude Code 不是 Cursor 的替代品

公平地说:

  • 需要可视化 diff、tab 补全、悬浮文档的工作(前端联调、复杂重构),Cursor 仍然更顺手
  • 需要 agent 跑较多步骤、自动决策、批处理任务的工作(数据处理、文档生成、脚本编写、OCR、批量改名),Claude Code 又快又省

我现在的分工是:写交互式前端代码用 Cursor;写脚本、文档、blog、做数据处理用 Claude Code。本文这个 img2md skill 同时塞在 ~/.cursor/skills/~/.claude/skills/ 两边,也是因为两个工具我都在用。


九、什么时候适合写 skill

写完 img2md 之后,我对"哪些问题适合 skill"有了更清楚的认知:

适合 skill 的问题

  1. 反复发生、可流程化:每次都要做一样的事,写成 skill 就是一个命令
  2. 能用本地工具绕开 LLM context 开销:文件操作、目录管理、本地脚本能搞定的事
  3. 工作流可被规则清晰描述:模型能按 SKILL.md 一步步执行,不需要动态决策
  4. 需要在 agent 行为里加硬约束(如 fidelity rule):把"禁止 X"写进 skill 比每次对话都强调更有效

不适合 skill 的问题

  1. 需要改宿主 UI:skill 跑在 agent 工具层,碰不到前端
  2. 一次性问题:写 skill 的成本可能比手动做更高
  3. 复杂跨 session 状态:skill 是无状态的,复杂状态用 memory 或数据库更好
  4. 需要持续运行的后台服务:skill 是 invoke-and-return,不适合做 daemon

一个判断公式:

如果你每周至少做 3 次这件事,且这件事可以用50 行脚本 + 一段清晰的指令文档完成 → 值得写成 skill


十、给 LLM agent 工具设计者的几点思考

1. Stateless 才是常态,attachment 是反模式

只要可能,就让信息以可丢弃的方式进入 context。文件路径 + Read 工具几乎总是优于把内容直接塞进 prompt。思考信息是否"需要在每轮都存在",而不是"能不能加进去"。

2. 文件系统是 LLM 最便宜的 memory

inbox/<project>/current/ 这一个目录就承担了 skill 的所有中间状态。不需要数据库,不需要缓存层——ls 就能查,cp 就能改,rm 就能清。LLM 应用里有大量的"中间产物"可以用本地文件系统替代内存或 KV store。

3. Skill description 是触发匹配的全部

我改过 6 次 SKILL.md 的 description 字段,每次都是因为"某种说法下不被触发"或"不该触发时被错误触发"。这个字段是 skill 唯一的入口——写不好就形同虚设。策略上:正例和反例都要写。仅写"什么时候触发"不够,必须写"什么时候不要触发"。

4. 删代码比加代码更难也更有价值

v6 → v9 砍掉了 200+ 行 Python。被砍掉的每一段当时都是"为了某个边界情况设计的"。事后看:那些边界情况要么从没发生,要么模型自己处理得比脚本更好。每次想加一个新模块时,先问:能不能让模型直接做?

5. 用户真实痛点 vs 工程师想象的需求

我以前以为用户要的是「OCR 准确率」。后来发现用户要的是「不要让我等」和「不要让我重复操作」。前者是工程问题,后者是产品问题。多数时候后者更重要,而解决产品问题往往需要更少的代码,而不是更多。


结语

这个 skill 看起来很小——5 个文件,脚本约 500 行,做的事一句话说得清。但它背后是对「LLM 上下文物理代价」「attachment 与文件路径的结构差异」「极简主义 vs 工程癖好」的一系列认知迭代。

如果用一句话总结这次工程经验:真正的优化不在算法层,在工作流层。Cursor 拖图慢,没有任何算法能解决;但绕开它的方式很简单——告诉模型"图片在那个目录里",而不是把图片放进对话框。

Skill 不过是把这个朴素的工作流变化固化成一个一句话能调用的工具而已。

完。


更多推荐