☰
caveman:纯文本命令行任务管理工具的设计与实践
2026/10/7 21:59:53 网站建设 项目流程

我给手头的这个工具起名叫caveman,中文直译过来就是“穴居人”。名字听着像开玩笑,但它其实是我用过之后觉得最贴切的一个代号——这个工具做的事非常原始:任务写在文本文件里,操作靠命令行,没有数据库,没有网络依赖,没有花哨界面。它解决的是我被各种“全家桶式”效率软件折磨到崩溃之后,最朴素的那个需求:随手记下来、随时查得到、数据永远归我。

这篇文章就把这个叫 caveman 的小项目掰开揉碎讲清楚,包括它名字的由来、核心设计思路、真实的开发过程、进入日常使用后的组合玩法,以及极简工具在什么场景下该用、什么时候千万别用。如果你也受够了打开一个 App 要等三秒、数据存在别人服务器上、功能多到根本用不完的现状,这篇内容应该能给你一点不一样的参考。

1. 从“洞穴人”这个名字说起:一个工具为什么要故意做旧

1.1 软件复杂到让人喘不过气

我做 caveman 之前,试过不少任务管理工具。Notion 功能全,但每次打开都要经历漫长的加载;Jira 适合团队,但为了看自己的待办还得穿过好几个面板;手机上的效率 App 更别提了,一个比一个精致,一个比一个费电,而且几乎所有在线工具都在“收集数据”这件事上有自己的想法。

时间长了你会发现一个矛盾:我明明只是想在指尖闪过一个念头的时候,用五秒钟把它留住,结果却要先登录、再建文档、再选模板、再调样式。等这一套走完,那个念头早就没了。

后来我想明白一件事:对个人日常记录来说,工具的复杂度和记录频率是成反比的。越是“强大”的工具,启动成本越高,你越是懒得用它。真正撑起日常记录的,往往是那些不起眼的、随时能抓起来写的载体。caveman 就是在这种反思里冒出来的。

1.2 三个设计约束决定了它的全部性格

caveman 的定位不是一个功能齐全的软件,而是一个故意“做旧”的个人任务台账。为了让这种“原始”不只是一句口号,我从第一天就给它定下了三个硬性约束:

  • 单文件可运行:整个工具就是一个caveman.py,日常使用只需python3 caveman.py list,不需要安装、不需要服务、不需要前端构建。
  • 零第三方依赖:只用 Python 标准库,连requests都不用。这意味着不管机器是新的还是旧的,Python 装了就能跑,断网也能跑。
  • 纯文本即数据:所有数据落在一个叫tasks.txt的文件里,没有 SQLite 的二进制文件,没有 JSON 的多层嵌套。

这三个约束不是拍脑袋定的。单文件是为了降低使用门槛——你不需要记住“配置文件在哪”“数据库在哪”“日志在哪”,一切都在一起;零依赖是为了让生命周期足够长——Python 2 到 Python 3 的迁移很难,但一个只用标准库的脚本,十年后拉出来改两行还能跑;纯文本则是为了数据主权——无论工具以后还活不活着,.txt文件永远能用记事本打开。

1.3 数据主权:比“顺手”更重要的价值

市面上大多数任务工具都在做“托管服务”,你负责输入,它负责替你保管和渲染。听起来很方便,但你有没有想过:如果这个产品停止运营了,你的数据怎么办?如果是订阅制,你停止付费之后,那些写了三年的日志还能不能导出成可读格式?

caveman 对这个问题给出了一个非常直接的答案:你的数据本来就是一个文本文件,有一份就在你自己电脑上。你可以拿它做 Git 版本管理,可以用网盘同步,可以随手拷进 U 盘,可以放到任何一台有 Python 的机器上直接查看。它不产生“平台锁定”,因为文本格式本身就是最底层的兼容协议。

我身边很多开发者朋友第一次看这个工具,都会问一句“就这?”。但我用了几个月后最大的体会是:工具的价值从来不在于它提供了多少个按钮,而在于它在你最需要记录的时候,能不能零阻力地出现。caveman 这个名字,就是在提醒自己不要忘记这一点。

2. caveman 的核心设计:文本即数据库,命令即思维

2.1 为什么不用 SQLite,也不用 JSON

有人会说,既然要写任务管理工具,那至少用个 SQLite 吧,或者把数据存成 JSON,结构一目了然,解析也方便。我最初也犹豫过,但把几种方案放在一起对比之后,发现纯文本反而是最适合这个场景的。

存储方案人眼可读性手工修改友好度git diff 友好度依赖要求
纯文本极好极好极好无
JSON一般容易改错括号勉强可用标准库
YAML较好缩进容易出错一般第三方
SQLite差必须借助工具无法直接 diff第三方

核心逻辑是这样的:如果数据格式设计成“人眼也能直接读”,那么即使某天 caveman 这个程序不见了,你依然可以用任何文本编辑器打开tasks.txt,一眼看明白有哪些任务、哪些做完了、优先级是什么。数据不应该被程序绑架,程序只是数据的过客。这一点,纯文本是做得最好的。

JSON 也有它的优势,比如解析标准、结构清晰,但对于“手工维护”这个场景,JSON 的括号和转义规则太反人类了。而且 JSON 文件在 git 里做 diff 的时候,一行的改动往往导致整个结构看起来都变了,实际几乎没法逐行审查。纯文本则完全不同,每一行是一条独立记录,哪个任务改了、哪条完成了,git diff 出来非常清楚。

2.2 tasks.txt 的任务结构和读写规则

caveman 的数据文件结构长这样:

# caveman task file # format: [status] [priority] [date] [description] x [1] 2025-01-10 完成 API 接口联调 [2] 2025-01-12 修复登录页按钮错位 x [1] 2025-01-12 整理会议纪要并发送给相关同事 [3] 2025-01-13 给服务器做一次安全巡检

每一行一条任务,规则非常简单:

  • 行首是x表示已完成,空一个字符表示待办;
  • 第三位是[优先级],1 最高,3 最低;
  • 接下来是YYYY-MM-DD日期;
  • 日期后面剩下所有内容都是任务描述,允许带空格、标点、方括号等任意字符。

这里有个刻意设计:已完成的任务行不会被删除,只会把行首从空格改成x。这样一来,tasks.txt就自动变成了一本流水账——你不仅能看到“现在还有哪些没做”,还能回顾“过去某一天完成了什么”。这个特性在周报、月度总结的时候特别好用。

关于任务编号,我做了另一个故意简单的决定:行号就是任务 ID。文件里第几行就是几号。虽然任务增删时行号会变,但反正所有操作都是操作一个任务文件,先list看到当前行号,再对那个行号执行操作就行。这个方案省掉了自增 ID 的所有维护成本,也避免了“删掉中间一条后编号断档”的问题。

2.3 命令设计:先想人类怎么说,再想代码怎么写

我见过不少工具,功能不错,但命令命名非常劝退,比如task --create --title "xxx" --priority high。这种设计对程序是友好的,对人不友好。caveman 的命令设计原则是:先想人在真实对话里会怎么表达,再映射到命令上。

命令实际作用人在说话时的自然表达
caveman list列出所有未完成任务“现在有啥事在排队?”
caveman add 任务描述 -p 1添加一条任务“记一下这个事,优先级高”
caveman done 3完成指定编号的任务“第3件事搞定了”
caveman note 记录内容写一条工作日志“顺便记一笔”
caveman review复盘今天的完成情况“今天这一天过得怎么样?”

所有命令都尽量控制在两三个单词内,参数能省就省。比如添加任务时,优先级默认是 2(普通),只有需要标红的时候才手动写-p 1;done允许多个编号一次性传入,比如caveman done 3 5 7,对应人类的“连划三件事”这个动作。

2.4 核心代码逻辑:解析与写回

解析逻辑我一开始用正则写,后来踩了几个坑(下一章详细讲),改成位置式解析。核心思路很朴素:顺着每一行从头往后读,状态位、优先级、日期都是固定格式,读完之后剩下的整段都当作描述,不再做任何规则匹配。

def parse_tasks(lines): tasks = [] for idx, line in enumerate(lines, 1): if not line.strip() or line.startswith('#'): continue done = line.startswith('x') rest = line[1:].lstrip() try: prio = int(rest[1:2]) # 形如 "[1]" date = rest[3:13] # "2025-01-12" desc = rest[14:].strip() # 剩下的全当描述 tasks.append({ 'id': idx, 'done': done, 'priority': prio, 'date': date, 'desc': desc, }) except (ValueError, IndexError): # 非标准行直接跳过,保证整个文件可读 continue return tasks

写回的时候有一个很重要的细节:必须做原子写入。也就是先把修改后的内容写到一个临时文件里,再通过os.replace把临时文件替换成原文件。这样即使写的过程中断电了,原文件也不会变成半个坏文件。

def write_tasks(path, lines): tmp = path + '.tmp' with open(tmp, 'w', encoding='utf-8', newline='') as f: f.writelines(lines) os.replace(tmp, path)

文本 파일이 특별한 건 아닙니다. 핵심은 “누구나 읽을 수 있고, 어떤 에디터로도 고칠 수 있고, 프로그램이 사라져도 데이터가 남는다”는 것입니다.

3. 从零到上线:caveman 的实际开发时间线

3.1 第一版原型:一个下午跑起来

caveman 的第一版是在一个周日下午写出来的。当时手上有一个项目排期表乱成一团,下午刚开完需求会,我坐在电脑前把需求写在纸上:

  • 能加任务
  • 能列出任务
  • 能勾掉任务
  • 数据要存在本地文本里

这四个需求听起来少得可怜,但几乎覆盖了日常任务管理的全部核心动作。我从命令行入口开始写,用的就是 Python 自带的argparse,命令设计成子命令的形式,先写list,再写add,最后补上done。第一版大概三百行,跑通之后,我没有立刻加功能,而是直接用真实的日常工作开始测试——这也是我觉得最重要的一步:先放回真实场景里去用,验证它是否真的解决问题,再决定要不要继续加代码。

大概一周之后,我才补上了note和review。note对应的是“随手记一笔”的需求,review对应的是“每天下班前看一眼今天完成了什么”。这两个需求在原型阶段就存在,但我刻意压着不加,因为想确认少了它们,流程是不是真的跑不顺。结果发现,只靠list和done,任务台账更像是“待办清单”,缺少“记录发生过的内容”这个维度。后来把note加上之后,整个工具才从任务管理变成了一个真正的工作日志。

3.2 踩坑实录:文本解析的五个边界问题

纯文本方案看起来简单,但真正写起来还是会遇到不少边界情况。我把过程中踩过的比较有价值的坑列出来,给同样想做文本型工具的朋友提个醒。

第一个坑是任务描述里的方括号会导致正则解析错乱。最早我用^[ x] \[(\d)\] (\d{4}-\d{2}-\d{2}) (.*)$这一套正则去切行,表面正常,可一旦任务描述里出现[2025-01-12]或[bug#123]之类的内容,匹配就直接失败了。解决方式很简单:放弃正则,改成位置式解析。状态位、日期、优先级都是固定长度或固定格式的前缀,读完之后剩下的部分不做任何匹配,全部视为描述。

第二个坑是中文对齐问题。一开始我想让list输出得漂漂亮亮,用str.ljust做列对齐,结果中文字符的宽度和英文字符不一样,输出直接错位。我纠结了几分钟后决定:不做对齐。输出只用一个简单的[1]前缀加优先级标记,反而更清晰。这个选择也符合 caveman 的整体气质——不追求美观,追求一眼能看懂。

第三个坑是Windows 编码兼容。我的主要使用环境是 macOS,但部分脚本会在 Windows 上跑。最初用 UTF-8 写文件,Windows 记事本打开就乱码。后来读写统一使用utf-8-sig编码(带 BOM 的 UTF-8),记事本能正常识别,Linux/macOS 也能正常读,两边都照顾到了。

第四个坑是换行符差异。Windows 用\r\n,Linux/macOS 用\n。如果直接按文本模式读写,在 Windows 上可能会遇到多换一行的问题。我的做法是读文件时用newline=None让 Python 自动识别,写文件时统一指定newline='',保证跨平台行为一致。

第五个坑是done 操作的幂等性。用户对一条已经完成的记录再次执行done,程序应该怎么办?我最初直接报错,后来发现这个设计很烦人——因为在真实操作中,你可能会连续对同一个编号按回车,或者脚本批量执行时重复调用。最后改成:如果任务已经是完成状态,不做任何修改,直接打印“已是完成状态”。命令要幂等,这是工具脚本一个很重要的原则。

3.3 平台适配:编码、换行与终端别名

caveman 的跨平台适配没有做什么高级处理,真正的重点都集中在文件读写这一层,因为其他逻辑都是纯 Python 字符串处理,不涉及平台差异。我把文件读写单独封装成一个模块,里面统一处理编码、换行、临时文件替换,这样不管是 Windows 还是 macOS,行为都是一致的。

日常使用的时候,给命令加个别名能省掉大量键盘输入。macOS 或 Linux 用户可以在~/.zshrc或~/.bashrc里加:

alias cm="python3 ~/tools/caveman.py" alias cmadd="python3 ~/tools/caveman.py add"

Windows 用户可以创建一个cm.bat放在任意目录并加入 PATH:

@echo off python %USERPROFILE%\tools\caveman.py %*

加完别名之后,日常操作基本就变成了:cm看任务列表,cmadd "给博客加一篇草稿"记录任务,cm done 5划掉一条。整个过程都是在终端里完成的,速度和手感不是图形化应用能比的。

3.4 给零依赖项目写自测脚本

有人会担心:一个零依赖的命令行工具,怎么保证改来改去不出问题?我用的是 Python 标准库自带的unittest,测试用例直接操作临时文件目录,跑完自动清理,不需要任何额外安装。

测试覆盖了几个关键场景:

  • 添加任务后,文件里是否追加了正确格式的行;
  • 完成一个任务后,行首是否从空格变成了x;
  • 对已完成任务重复执行done,文件内容是否不变;
  • 任务描述包含中文、方括号、特殊字符时,解析是否正常;
  • 空文件初始化时,能否正常运行不报错。

这些用例虽然简单,但给了我折腾后续版本的安全感。一个工具脚本不需要测试覆盖每一行,但几个核心场景必须有回归保护,尤其是格式解析这类容易改坏的地方。

4. caveman 进入日常:真实工作流中的组合用法

4.1 每天的任务台账:早晨列、傍晚清

我用 caveman 的方式已经基本固定成了一日流程。

早上到工位,第一件事是cm,屏幕上会按优先级列出当前所有未完成任务。这时候我会把今天必须推进的事排在心里,然后开启工作。遇到新任务、新反馈,随手cmadd一条,优先级按“重要且紧急”的原则给 1,普通顺手的事情给 2 或 3。这里有个经验:不要事事都给 P1,P1 给多了等于没有 P1。普通任务默认 P2,只有真正影响今天目标的事情才标 P1。

傍晚下班前,我会跑一次cm review,它会统计今天的完成数和剩余数,同时把今天note的内容一起列出来。这套流程坚持下来之后,我对“今天有没有做正事”这个问题有了非常直观的答案——不用回忆,不用翻聊天记录,打开终端跑一条命令,一天的工作轨迹都在那里。

4.2 让 caveman 融入自动化:别名与定时任务

caveman 是纯命令行工具,所以天然适合和其他桌面自动化流程嵌在一起。

我常用的一个做法是配合系统的快捷键绑定:在 macOS 的 Automator 或第三方快捷指令里调用cmadd,绑定一个全局快捷键,比如Ctrl + Option + A。这样不管我在写代码、看文档还是开会,只要脑子里闪过一个需要跟进的事项,按一下快捷键,输入框弹出来,敲完回车,任务就已经落到tasks.txt里了。整个过程三秒以内,没有解锁手机、没有打开 App、没有等待加载。

另一个用法是配合系统的定时任务。比如我每天下午 5 点半会跑一个任务,自动执行caveman review并把输出重定向到当天的工作日志文件里。这样即使我某天忘记做复盘,数据也会被自动保存下来,事后补看完全没问题。

4.3 多端同步与文件备份:纯文本的天然优势

纯文本的另一个巨大优势是同步和备份极其简单。我自己的方案有两层:

第一层是Git 版本管理。tasks.txt所在的目录初始化成一个 Git 仓库,每天或每周 commit 一次。因为每一行都是一条独立记录,git diff 看得清清楚楚:哪天加了哪条任务、哪天完成了哪条、哪天写了什么笔记。这种“任务变更历史”在没有任何额外代码的情况下就自动获得了。

第二层是SyncThing 或网盘同步。我用 SyncThing 把整个目录同步到手机和工作电脑上。在手机上也可以用任意文本编辑器直接查看tasks.txt,虽然体验不如原生 App 舒服,但应急查一条任务完全够用。纯文本同步几乎不会出现“文件损坏”的问题,最多只是两个端都有修改时产生版本冲突,而文本冲突的合并成本很低。

4.4 从 txt 到周报:几条命令的导出方案

caveman 没有内置生成周报的功能,但纯文本配合 shell 管道,导出方案可以非常灵活。

比如我要统计这周完成了多少条任务,只需要:

grep '^x' tasks.txt | grep '2025-01-' | wc -l

要列出本周完成的内容变成 Markdown 列表:

grep '^x' tasks.txt | grep '2025-01-' | sed 's/^/ - [完成] /'

再配合note的内容,一个工作周报就基本成型了。你还可以把这个导出命令写进一个report.sh,每周五下午跑一次,输出直接贴到文档或邮件里。整个过程没有任何图形界面,但恰恰是这种“低科技”的做法,让周报这件事变成了一条可复用的命令,而不是打开一个软件找半天导出按钮。

5. 极简工具的边界:什么时候该用 caveman,什么时候别用

5.1 谁适合用 caveman:我观察到的用户画像

caveman 不是给所有人准备的。到目前为止,我发现用它用得顺手的主要是这几类人:

  • 开发者或重度命令行用户:不排斥终端,甚至觉得终端比图形界面更高效;
  • 数据敏感者:不希望自己的任务列表、工作日志存在第三方服务器上;
  • 极简主义实践者:意识到记录的关键在于“快”,功能复杂度反而妨碍记录频率;
  • 有长期主义心态的人:希望自己的记录十年后依然可以用纯文本打开,而不是被困在一个停止维护的 App 里。

如果你符合其中的一两类,用这类工具大概率会很顺手。但反过来,如果你不希望接触命令行,或者你更喜欢用手机随手记录,那这个工具就不一定合适。

5.2 caveman 不会做的事:协作、提醒与复杂排期

极简工具的代价是它放弃了很多现代软件的默认能力,这一点必须说清楚。

caveman不适合团队协作。它没有权限系统,没有通知机制,没有评论互动。同一个文件理论上可以多人编辑,但冲突处理和同步协作都需要额外的手段,这不是它擅长的事。如果你需要的是一个团队项目管理系统,Jira、Trello、飞书这类产品仍然是更好的选择。

caveman也没有主动提醒能力。它不会在你忘记某个截止日期的时候弹通知,因为它根本没有后台进程。所有提醒都必须靠外部机制实现,比如系统定时任务发通知、或者你自己养成每天看两次list的习惯。对提醒依赖很强的人,这个工具会让你失望。

caveman更不适合做复杂排期。它每条任务只有一个简单优先级和一个日期,不支持开始时间、截止时间、依赖关系、里程碑这些概念。它更像一个“第二大脑的速记本”,而不是一个项目调度器。把甘特图之类的需求往这个工具上套,属于用错工具。

5.3 “原始”的反思与后续可能的扩展

做 caveman 的过程中,我无数次被问到同一个问题:“都什么年代了,还用文本文件管任务?”一开始我会解释理由,后来我发现自己也经常思考这个问题的反面:是不是我把极简当成了目的本身?

我的结论是,极简不是目的,而是手段。caveman 让我愿意记录,原因是它足够快、足够可靠、数据足够可控。如果哪天有新的需求出现,我完全不排斥给这个工具做扩展。目前我已经在考虑几个未来的方向:

  • 给tasks.txt增加一个索引文件,支持全文搜索;
  • 加一个report子命令,直接把周报导出成 Markdown 文件;
  • 通过系统通知机制实现简单的“每日摘要提醒”;
  • 支持多文件数据源,比如把工作和生活分成两个 txt 文件。

这些改动都不会破坏现有格式,因为数据的根基是纯文本,程序怎么变都不会影响数据本身。这也是为什么我敢把这个工具“做旧”——因为文本这种“旧格式”,恰恰是它最不容易过时的部分。

我把这个项目陆续维护了快一年,代码量从最初的三百行变成现在的一千出头,但使用方式几乎没有变过。我最满意的不是它有多少功能,而是无论过了多久,打开终端敲几个字,数据就在那里。最后再分享一个我一直在用的小技巧:把cm add的调用封装成一个系统级快捷键,在任意窗口里按下快捷键,弹出的输入框直接写任务描述,回车后任务就进了 txt。这个动作从有想法到落盘不超过三秒钟,比解锁手机打开 App 再新建任务快得多。如果你也被各种“功能过剩”的工具搞得心烦,不妨从这类原始但可靠的方案开始试试。工具是拿来用的,不是拿来供的——这是我做 caveman 一年来最大的体会。

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

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

立即咨询