OpenCode 实践课:4 分钟用 Python 写出一个命令行 TODO 管理器

项目介绍

在日常开发中,临时记个待办事项是很常见的需求。打开专门的 TODO 软件太重,用记事本文本文件又太简陋。今天我们用 OpenCode 在 4 分钟内 写一个命令行 TODO 管理器,支持增删查改,数据存本地 JSON 文件,不依赖任何第三方库。

  • 技术栈:Python 3(纯标准库)
  • 代码量:约 90 行
  • 功能:add(添加)、list(列出)、done(完成)、delete(删除)
  • 数据存储~/.todos.json

最终效果:

$ todo add 写完本周周报
[1] 写完本周周报

$ todo add 修复登录页BUG
[2] 修复登录页BUG

$ todo list
ID    Status   Description                              Created
----------------------------------------------------------------------
1     TODO     写完本周周报                              2026-08-06 09:00:00
2     TODO     修复登录页BUG                             2026-08-06 09:01:00

$ todo done 1
[1] marked as done.

准备工作

安装 OpenCode(参考 opencode.ai

确保本地有 Python 3.6+ 环境

实践过程

第一步:创建项目骨架

打开终端,进入工作目录,直接对 OpenCode 说出需求:

> :用 Python 写一个命令行 TODO 管理器,保存到 ~/.todos.json,纯标准库,然后告诉我怎么用。

OpenCode 立即开始分析需求,生成了完整的项目代码,并给出了使用说明。不到 30 秒,一个可运行的 TODO 管理器就诞生了。

关键要点:描述需求时,把"保存路径""技术约束"一次性说清楚,OpenCode 能一次生成高质量代码,减少来回调整。

第二步:增加完成和删除功能

第一版只有添加和列表,我们需要补全功能:

> :给这个 TODO 工具加上 done 和 delete 命令,done 通过 id 标记完成,delete 通过 id 删除。

OpenCode 理解了需求,在原有代码上增加了 mark_done()delete_todo() 两个函数,同时在 main() 里注册了对应的命令入口。整个过程就是在现有文件上精准编辑,没有改动无关代码。

关键要点:增量开发时,用自然语言描述"加什么功能",OpenCode 会理解上下文,只修改需要改的部分。

第三步:美化输出格式

默认的输出太简陋,我想让列表更好看:

> :把 list 命令的输出改成表格形式,对齐显示 id、状态、描述、创建时间。

OpenCode 立刻把输出改成了带分隔线的对齐表格,还自动处理了中文字符的宽度——尽管 Python 终端的中文对齐是个坑,OpenCode 在代码中用了固定宽度 :<40,简单实用。

关键要点:UI 层面的调整对 AI 来说很简单,直接描述你想要的视觉效果即可。

第四步:增加筛选和帮助

最后再加两个小优化:

> :list 命令默认只显示未完成的 TODO,加 -a 参数显示全部。另外完善帮助信息。

OpenCode 给 list_todos() 增加了 show_all 参数,默认过滤掉已完成项,并在 main() 开头输出了清晰的命令说明。

关键要点:参数扩展类需求,直接说"默认什么行为 + 可选参数怎么变",OpenCode 能准确理解条件逻辑。

完整代码

#!/usr/bin/env python3
"""CLI TODO Manager"""
import json
import os
import sys
from datetime import datetime

TODO_FILE = os.path.expanduser("~/.todos.json")


def load_todos():
    if not os.path.exists(TODO_FILE):
        return []
    with open(TODO_FILE, "r", encoding="utf-8") as f:
        return json.load(f)


def save_todos(todos):
    with open(TODO_FILE, "w", encoding="utf-8") as f:
        json.dump(todos, f, indent=2, ensure_ascii=False)


def add_todo(desc):
    todos = load_todos()
    tid = max((t["id"] for t in todos), default=0) + 1
    todo = {
        "id": tid,
        "desc": desc,
        "done": False,
        "created": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
    }
    todos.append(todo)
    save_todos(todos)
    print(f"[{tid}] {desc}")


def list_todos(show_all=False):
    todos = load_todos()
    if not todos:
        print("No todos found.")
        return
    filtered = todos if show_all else [t for t in todos if not t["done"]]
    if not filtered:
        print("All done!")
        return
    print(f"{'ID':<5} {'Status':<8} {'Description':<40} {'Created'}")
    print("-" * 70)
    for t in filtered:
        status = "DONE" if t["done"] else "TODO"
        print(f"{t['id']:<5} {status:<8} {t['desc']:<40} {t['created']}")


def mark_done(todo_id):
    todos = load_todos()
    for t in todos:
        if t["id"] == todo_id:
            t["done"] = True
            save_todos(todos)
            print(f"[{todo_id}] marked as done.")
            return
    print(f"Todo #{todo_id} not found.")


def delete_todo(todo_id):
    todos = load_todos()
    new_todos = [t for t in todos if t["id"] != todo_id]
    if len(new_todos) == len(todos):
        print(f"Todo #{todo_id} not found.")
        return
    save_todos(new_todos)
    print(f"[{todo_id}] deleted.")


def main():
    if len(sys.argv) < 2:
        print("Usage: todo <command> [args]")
        print("Commands:")
        print("  add <desc>     Add a new TODO")
        print("  list [-a]      List TODOs (-a to show all)")
        print("  done <id>      Mark TODO as done")
        print("  delete <id>    Delete a TODO")
        return

    cmd = sys.argv[1]

    if cmd == "add" and len(sys.argv) > 2:
        add_todo(" ".join(sys.argv[2:]))
    elif cmd == "list":
        list_todos("-a" in sys.argv)
    elif cmd == "done" and len(sys.argv) > 2:
        mark_done(int(sys.argv[2]))
    elif cmd == "delete" and len(sys.argv) > 2:
        delete_todo(int(sys.argv[2]))
    else:
        print("Unknown command. Run 'todo' for help.")


if __name__ == "__main__":
    main()

使用方法:将上述代码保存为 todo 文件(或 todo.py),添加到 PATH 中即可。

小结

用 OpenCode 开发这个 TODO 管理器的几点体会:

描述即开发:把需求说清楚,OpenCode 直接生成可运行代码,不需要手动查 Python 语法、JSON 操作之类的细节。

增量迭代自然:先有了基础版本,然后一句一句地加功能,像和同事讨论一样自然。每步都能看到结果,不必一次性把设计做完美。

效率提升明显:如果手写这 90 行代码,加上查文档、调试格式对齐、处理边界情况,大约要 15-20 分钟。用 OpenCode 全程不到 4 分钟,效率提升 4-5 倍。

可扩展性强:后续想加优先级标签、截止日期、按日期排序等功能,只要对 OpenCode 说一句话就能实现,项目可随时按需生长。

命令行 TODO 管理器虽小,但它证明了:有了 AI 编程助手,从脑子里的灵感到可运行的工具,中间只隔了几句话。