☰
用命令行工具解决上下文切换难题:从零实现 context-mode
2026/10/7 6:53:55 网站建设 项目流程

你有没有遇到过这种场景:正在写一篇技术文章,脑子里塞满了思路,顺手切过去修了个 bug,又有人让你帮忙看看一份文档。等再切回来的时候,之前整理好的思路已经乱成一团。我试过很多办法,开多个文档、用笔记软件、把内容粘来粘去,最后都败给了“上下文切换”这件事。

所以我自己写了一个小工具,名字就叫context-mode。它的定位很直接:在终端里用一条命令,把当前正在进行的任务上下文保存下来,随时切换,随时恢复。这篇博文把我从设计到落地整个过程完整拆开,包括数据格式、命令设计、关键代码、常见坑和排查方法。适合每天要同时处理多个任务、经常和 AI 对话、写长文档或者长期跟维护一堆工程分支的人读,看完可以直接抄去自己用。

1. context-mode 到底要解决什么问题

1.1 上下文混乱的典型场景

我先把你可能遇到的场景摆出来,看看是不是似曾相识:

  • 早上在写一份季度总结,写到一半被叫去处理线上告警,等你回来,刚才想好的一段表达怎么也想不起来。
  • 正在基于一个开源项目做二次开发,刚理清了调用链,结果又去回复了几个用户问题,再回到代码里,之前的上下文已经丢了。
  • 长期维护一个项目,经常要在“调试接口”“写文档”“梳理架构”几个状态之间反复横跳,每个状态下你关注的代码区域、依赖项、关键命令都不一样。

这些场景的共同点,不是“信息不存在”,而是“信息堆在一起没法快速分离”。文本编辑器里的多个 Tab 能帮一点忙,但真正有价值的上下文不只是文件内容,还包括你当时整理出的结论、待验证的想法、临时收集的参考资料、甚至一段正在调试用的命令。

1.2 设计目标:像抽屉一样管理你的工作状态

我在做 context-mode 的时候,给自己定了几个硬性目标:

  1. 足够轻,一条命令完成保存/切换,不能在工具使用本身上花精力。
  2. 存储格式可读,所有数据就是纯文本和 JSON,随时可以用编辑器打开改。
  3. 完全离线可用,不依赖任何外部服务,敏感信息留在自己电脑里。
  4. 可脚本化,能放进 Git hook、cron 任务、编辑器快捷键里配合使用。

你可以把它理解成一个“给大脑用的抽屉柜”。每个抽屉是一个命名好的上下文,里面有这段工作需要的全部背景信息。你工作时只拉开一个抽屉,做完一个任务就把抽屉推回去,拉开另一个。抽屉里的内容不会因为你中途去干别的事而自动消失。

1.3 为什么不做成 GUI 或者编辑器插件

这不是技术能力问题,而是刻意选择。GUI 工具在“记录”这个动作上天然比命令行重:你得打开窗口、找到输入框、填写表单、点保存。而终端里的命令可以在你思考的间隙直接敲完,甚至可以通过别名做到两个键触发。

编辑器插件也是个方案,但它只能解决“写入”的问题,不能解决“把上下文带到任何地方”的问题。context-mode 是独立的命令行工具,我可以在终端里用,可以在脚本里调用,也可以让 AI 编程助手读取同一份上下文文件。这种可组合性,是绑定在单一编辑器里做不到的。

下图是整个工具的简化工作流,我用文字描述一下:

  • 你通过cm save 名称把当前工作上下文存入本地存储;
  • 后续通过cm load 名称把它读出来,注入终端、剪贴板或指定文件;
  • cm list让你随时看到自己有哪些抽屉;
  • 当你不再需要某个上下文时,用cm rm清理。

2. 整体架构与核心功能设计

2.1 命令体系与数据模型

context-mode 项目名中带着 “mode” 这个词,就是希望它像一个“模式开关”一样工作。命令集合设计如下:

命令作用示例
cm save <name> -s "内容"保存一个新的上下文cm save weekly-report -s "Q3目标:..."; cm save debug-login -f ./tmp/notes.md
cm append <name> -s "补充内容"往已有上下文追加内容cm append weekly-report -s "补充数据:新增用户3000"
cm load <name>加载上下文到终端/剪贴板cm load debug-login
cm list列出所有上下文条目cm list
cm rm <name>删除一个上下文cm rm old-notes
cm clip <name>将上下文内容复制到剪贴板cm clip weekly-report
cm export/cm import导出/导入全部数据cm export ./backup.json

存储结构放在了~/.context-mode/目录下,主文件是store.json。单个条目的结构这样设计:

{ "id": "20250918-214530", "name": "weekly-report", "tags": ["report", "q3"], "content": "季度目标:... 当前进展:... 待确认:...", "created_at": "2025-09-18T21:45:30+08:00", "last_used": "2025-09-19T09:12:00+08:00" }

字段不多,但都是有用的设计:

  • name是给人看的,也是命令里要用的关键参数,我限定为小写字母、数字和连字符,避免空格带来的转义问题。
  • content是真正承载上下文的地方,纯文本,长度不限。
  • tags可选项,方便按标签过滤。
  • last_used用来支持“最近使用的上下文”这个功能,我经常用cm list --recent快速找回上午的工作状态。

2.2 上下文注入与模板机制

只保存一段文本文档还不够,实际使用时我更希望“加载上下文”这个动作能同时完成几件具体的事。context-mode 里做了一个“注入模板”机制,每个上下文保存时除了content,还可以附带模式类型,比如coding、writing、review、mail。

举个例子,我保存一个code-fix类型的上下文,然后加载时,工具会把内容包装成这样的结构:

【当前任务】 修复登录模块在 session 过期后跳转异常的问题 【已知信息】 - 异常发生在 LoginService.checkSession() - 前端接口在 401 时未统一处理 - 复现步骤:登录后等待 30 分钟,点击任意菜单触发 【待验证】 - 服务端是否返回新的 refreshToken - 前端路由守卫是否需要补充白名单 【建议动作】 1. 先在后端接口加日志,确认过期时间点 2. 再检查前端 axios 拦截器对 401 的处理

这个结构其实就是给 LLM 或给未来的自己看的“提示词”。实际里我会把这段包装后的内容直接复制到 AI 对话框或者文档开头,省去每次重新整理背景信息的功夫。

2.3 安全性与隐私设计

一个和上下文管理相关的工具,最容易被人担心的是隐私。context-mode 默认所有数据都存在本地,没有网络请求。我使用的场景里有时会涉及一些内部系统的日志、密钥片段、客户信息,这些内容不适合放到第三方平台,这也是我坚持本地优先的原因。

为了让这个原则更容易落地,我还做了两个小功能:

  • 每个条目支持设置sensitive: true,标注为敏感的内容在cm list里只显示名称,不显示内容预览。
  • 支持cm export --redact导出脱敏版本。脱敏规则很简单,就是把疑似邮箱、手机号、密钥片段用***替换。这个功能在你想把上下文分享给同事参考时特别好用。

3. 从零搭建 context-mode 的实操过程

3.1 环境准备与安装

context-mode 我尽量做到零依赖,但为了让 JSON 解析更可靠,我选择依赖jq。如果你经常在命令行操作,大概率已经装了;没装的话用系统自带包管理器装一下就行。

完整依赖清单:

  • Bash 4.0+ 或 Zsh
  • jq 1.6+(解析 store.json 用)
  • fzf(可选,推荐,用于交互式选择上下文)

安装脚本我写在项目里了,逻辑不复杂:检测系统中的依赖,创建数据目录,然后把主脚本下载到/usr/local/bin/cm并加上可执行权限。完成之后在 shell 配置里加一行别名:

alias cm='context-mode'

装完先验证一下:

$ cm --version context-mode 1.0.0

如果命令提示找不到,大概率是/usr/local/bin不在 PATH 里。用echo $PATH看一下,加进去就行。

3.2 核心代码实现

整个工具的核心是四个函数:保存、加载、列表、删除。我现在把关键实现贴出来,并逐段解释为什么这么写。

# 保存上下文 cm_save() { local name="$1" local content="$2" local store="$HOME/.context-mode/store.json" local id id=$(date +%Y%m%d-%H%M%S) local entry entry=$(jq -n \ --arg id "$id" \ --arg name "$name" \ --arg content "$content" \ --arg created "$(date -Iseconds)" \ '{id: $id, name: $name, content: $content, created_at: $created}') if [[ -f "$store" ]]; then jq --argjson entry "$entry" '. + [$entry]' "$store" > "$store.tmp" mv "$store.tmp" "$store" else mkdir -p "$HOME/.context-mode" echo "[$entry]" > "$store" fi echo "saved: $name" }

这里用jq而不是直接用echo拼 JSON,是因为用户输入的内容里可能包含引号、换行、反斜杠。手动拼字符串很容易破坏 JSON 结构,而jq -n --arg会自动处理转义。实测下来,用jq后从来没有因为特殊字符出过问题。

加载函数要更细心一点,因为要考虑到“加载到什么位置”的问题:

cm_load() { local name="$1" local store="$HOME/.context-mode/store.json" local target="${2:-screen}" local content content=$(jq -r --arg name "$name" '.[] | select(.name == $name) | .content' "$store") if [[ -z "$content" ]]; then echo "no context found: $name" return 1 fi case "$target" in screen) echo "$content" ;; clip) printf "%s" "$content" | pbcopy || printf "%s" "$content" | xclip -selection clipboard ;; file) printf "%s\n" "$content" > "$2." echo "written to $2." ;; esac # 更新时间戳 jq --arg name "$name" \ '(.[] | select(.name == $name) | .last_used) |= now' \ "$store" > "$store.tmp" mv "$store.tmp" "$store" }

target参数让同一个命令具备了三种输出方式。screen模式适合直接在终端里查看;clip模式适合准备粘贴到编辑器或 AI 对话框;file模式适合把上下文输出到一个临时文件,配合其他工具进一步处理。

列表函数我用表格输出,方便人眼扫:

cm_list() { local store="$HOME/.context-mode/store.json" jq -r '.[] | "\(.name)\t\(.last_used // "never")\t\(.content[0:40])"' "$store" \ | column -t -s $'\t' }

输出效果差不多是这样:

name last_used content_preview weekly-report 2025-09-19T09:12:00 季度目标:... debug-login 2025-09-18T21:45:30 LoginService...

3.3 在日常工作流里的实际演示

光看代码不够直观,我拿三个真实工作场景演示一下 context-mode 能怎么改变节奏。

场景一:写一篇技术文章

  1. 我把所有参考资料、核心论点、示例代码片段先收集起来,保存为一个上下文。
  2. 写文章时用cm load article --target clip把背景说明复制到 AI 对话框,让 AI 按我的信息生成初稿。
  3. 中途被打断去改 bug,改完回来再cm load article,思路完整恢复。
  4. 晚上文章写完了,cm rm article清理这个临时上下文。

场景二:处理客户反馈

  1. 收到客户反馈后,我把问题描述、相关日志、排查思路存进cm save support-customerA。
  2. 第二天客户又追加了一条反馈,用cm append support-customerA -s "客户补充:重启后问题仍然存在"。
  3. 排查到解决办法后,把结论追加进去,形成完整处理记录。
  4. 事后cm export --redact ./result.json导出脱敏版,分享给团队。

场景三:跨天恢复“昨天的研究”

  1. 每天下班前,我用cm save research-vector-db把当天看过的资料、当前结论、下一步计划存下来。
  2. 第二天上班直接cm load research-vector-db,30 秒内进入昨天的工作节奏。
  3. 周五汇总时用cm list --tags research把所有研究类上下文列出来,写周报时引用的都是真实过程记录。

我用下来最大的感受是,context-mode 让我从“记忆负担”里解放了出来。以前我总得靠脑力维护一个“现在进行到哪了”的指针,现在这个指针变成了公开的、可随时查看的状态。

3.4 配置与自定义扩展

每个工具只有用到一定深度才会顺手,context-mode 提供了两个自定义维度:

第一,模板自定义。默认模板有coding、writing、review等,但每个人的工作习惯不同。模板文件放在~/.context-mode/templates/下,你可以创建自己的模板,比如--type support定义客服场景的结构。加载时指定--type support就能按这个模板渲染。

第二,指令钩子。context-mode 支持在加载和保存时触发自定义命令。比如我想在加载一个coding类型上下文时,自动把项目目录切换到 context 里记录的工作目录,我可以写一段钩子脚本:

# ~/.context-mode/hooks/after_load.sh case "$CM_NAME" in project-*) cd "$(jq -r --arg n "$CM_NAME" '.[] | select(.name == $n) | .content' \ | grep -oP '工作目录[::]\s*\K[^\n]+')" ;; esac

这个例子稍微有点硬编码,但思路是值得借鉴的:工具本身保持简单,把奇技淫巧留给 shell 环境去增强。

4. 高频问题与排查技巧实录

4.1 常见问题速查表

我在实际使用和给同事推荐的过程中,收集了不少问题。挑典型的整理成表格:

问题现象可能原因解决办法
cm: command not foundPATH 中没有包含安装目录export PATH="/usr/local/bin:$PATH"
中文内容显示成\uXXXX终端或 jq 输出默认转义非 ASCII 字符JSON 存储本身没问题,查看时加-r参数即可
保存时被 shell 展开变量在双引号里写了$HOME等变量cm save name -s "价格是 $5"要在内容包含$时加引号,用\$转义
加载的内容尾部被截断有些实现用echo输出多行文本统一改成printf "%s",不要用echo
两个终端同时保存导致数据丢失并发写入 store.json用flock给写入操作加锁
误删了重要上下文没有自动备份在 rm 时自动把内容压缩到~/.context-mode/trash/

第四条的“截断问题”尤其隐蔽。echo在有些环境下会对以-开头的内容处理异常,而且echo默认在尾部追加换行,多行内容时行为不一致。所有涉及内容输出的地方,我最终都换成了printf,这个问题再也没有出现过。

4.2 我踩过的三个坑

第一个坑:在管道里 alias 失效。早期我给cm设置了 alias,但在脚本里写cm list | grep project时发现 alias 不会被展开,命令直接报错。后来我把脚本里的调用全部改成完整函数名context-mode,只在交互式 shell 里保留 alias。

第二个坑:JSON 里保存带$符号的内容。有一次保存 shell 片段时,没有注意转义,导致变量被提前展开成一串空值。比如你写$PATH,shell 会自作主张把它替换成环境变量值,等到你用的时候内容已经不是你想存的了。现在的处理方式是:所有保存操作都用--raw参数,明确告诉工具“不要做任何展开”。

第三个坑:同一目录自动关联导致误加载。早期我做过一个“进入目录自动加载同名上下文”的功能,看起来方便,实际特别容易翻车。经常是进到项目目录,工具自动加载了旧的上下文,把当前还没整理好的新想法覆盖了。后来我彻底去掉了自动加载,改成手动cm load,一切以显式操作为准。

4.3 让 context-mode 更好用的小技巧

最后分享几个我用着非常顺手的小配置,按推荐程度排序:

  1. 绑定快捷键。在 Zsh 里我加了两个键位:Ctrl+g保存当前上下文,Ctrl+l加载最近使用的上下文。配合起来基本是零思考成本。
  2. 定时快照。我写了一个 cron 任务,每小时把 store.json 复制一份到~/.context-mode/snapshots/,只保留最近 30 份。这样就算误删了内容,也能从快照里捞回来。
  3. 和 AI 工具联动。写代码时,我会把cm load task --target clip的输出直接粘贴到 AI 助手的上下文里。这样 AI 不用再问“项目背景是什么”这种基础问题,直接就能进入具体实现。
  4. 不同项目用不同的 tag 前缀,比如tag: project-xxx,然后用cm list --tag project-xxx做项目级视图。时间一长,这其实就是个轻量版的项目知识库。

5. 后续还能怎么扩展

写到这,代码已经完整,工作流也能跑了。但任何工具用久了都会看到更多可能性,我给自己列了几个后续想做的方向:

  • 支持把某个上下文直接渲染成 Markdown 文件,作为周报素材。
  • 给cm list增加按标签过滤和模糊搜索,已经做了基础版。
  • 把 store.json 的同步交给用户自选方案。我把备份目录纳入私有 Git 仓库,每次cm save后顺手 commit 一下,多台机器之间靠着 Git push/pull 就能同步。
  • 等数据量大了之后,可以考虑在cm list里加入内容索引,这样搜索效率更高。

有意思的是,第一版 context-mode 只有 80 行 shell,现在已经扩展到了 300 多行。功能变多了,但设计原则还是那几条:本地存储、纯文本优先、命令简洁。我个人在实际操作中的体会是,工具越大越容易成为负担,context-mode 的价值恰恰在于它只做一件事:让你在任何时刻都能清晰地知道“当前在干什么、之前干到了哪里”。

如果你也有多任务并行和上下文丢失的困扰,我建议从最小版本开始,先跑通 save 和 load 两条命令,用上一周,再按自己的习惯加模板、加钩子。工具是次要的,把工作过程显性化记录下来的意识,比任何工具都重要。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询