todoium 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. todoium-0.1.0/.claude/settings.local.json +18 -0
  2. todoium-0.1.0/.gitignore +30 -0
  3. todoium-0.1.0/DESIGN.md +539 -0
  4. todoium-0.1.0/PKG-INFO +14 -0
  5. todoium-0.1.0/README.md +66 -0
  6. todoium-0.1.0/alembic.ini +149 -0
  7. todoium-0.1.0/docs/superpowers/specs/2026-04-30-claude-dispatch-design.md +168 -0
  8. todoium-0.1.0/docs/superpowers/specs/2026-04-30-pomodoro-timer-design.md +198 -0
  9. todoium-0.1.0/pyproject.toml +36 -0
  10. todoium-0.1.0/tests/__init__.py +0 -0
  11. todoium-0.1.0/tests/conftest.py +99 -0
  12. todoium-0.1.0/tests/test_pomodoro.py +228 -0
  13. todoium-0.1.0/tests/test_server.py +223 -0
  14. todoium-0.1.0/tests/test_store.py +234 -0
  15. todoium-0.1.0/tests/test_tree.py +206 -0
  16. todoium-0.1.0/tests/test_tui.py +196 -0
  17. todoium-0.1.0/todo_cli/__init__.py +119 -0
  18. todoium-0.1.0/todo_cli/alembic/README +1 -0
  19. todoium-0.1.0/todo_cli/alembic/env.py +72 -0
  20. todoium-0.1.0/todo_cli/alembic/script.py.mako +28 -0
  21. todoium-0.1.0/todo_cli/alembic/versions/21439761fa41_init_schema.py +65 -0
  22. todoium-0.1.0/todo_cli/alembic/versions/a3f1b7c92e01_add_pomodoro_sessions.py +39 -0
  23. todoium-0.1.0/todo_cli/alembic/versions/d75c6f862ec4_add_desc_column.py +36 -0
  24. todoium-0.1.0/todo_cli/cli.py +97 -0
  25. todoium-0.1.0/todo_cli/client.py +156 -0
  26. todoium-0.1.0/todo_cli/db.py +125 -0
  27. todoium-0.1.0/todo_cli/models.py +102 -0
  28. todoium-0.1.0/todo_cli/pomodoro.py +163 -0
  29. todoium-0.1.0/todo_cli/server/__init__.py +3 -0
  30. todoium-0.1.0/todo_cli/server/app.py +53 -0
  31. todoium-0.1.0/todo_cli/server/auth.py +26 -0
  32. todoium-0.1.0/todo_cli/server/routes_pomodoro.py +33 -0
  33. todoium-0.1.0/todo_cli/server/routes_todo.py +109 -0
  34. todoium-0.1.0/todo_cli/server/runner.py +63 -0
  35. todoium-0.1.0/todo_cli/server/schemas.py +78 -0
  36. todoium-0.1.0/todo_cli/store.py +292 -0
  37. todoium-0.1.0/todo_cli/tree.py +181 -0
  38. todoium-0.1.0/todo_cli/tui.py +1021 -0
  39. todoium-0.1.0/uv.lock +1219 -0
@@ -0,0 +1,18 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(uv run:*)",
5
+ "Bash(tree:*)",
6
+ "Bash(uv add:*)",
7
+ "Bash(ls:*)",
8
+ "Bash(alembic init:*)",
9
+ "Bash(git -C /Users/tangkikodo/Documents/t log --oneline -15)",
10
+ "Bash(git -C /Users/tangkikodo/Documents/t add alembic.ini todo_cli/alembic/ todo_cli/db.py todo_cli/tui.py pyproject.toml .gitignore README.md todo_cli/__init__.py todo_cli/cli.py todo_cli/models.py todo_cli/store.py tests/conftest.py tests/test_store.py tests/test_tui.py uv.lock)",
11
+ "Bash(git -C /Users/tangkikodo/Documents/t commit:*)",
12
+ "Bash(git -C /Users/tangkikodo/Documents/t status)",
13
+ "Bash(git -C /Users/tangkikodo/Documents/t checkout:*)",
14
+ "Bash(git -C /Users/tangkikodo/Documents/t add todo_cli/tui.py tests/test_tui.py)",
15
+ "Bash(git -C /Users/tangkikodo/Documents/t push:*)"
16
+ ]
17
+ }
18
+ }
@@ -0,0 +1,30 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ *.egg
8
+
9
+ # Virtual environments
10
+ .venv/
11
+ venv/
12
+
13
+ # IDE
14
+ .idea/
15
+ .vscode/
16
+ *.swp
17
+ *.swo
18
+
19
+ # OS
20
+ .DS_Store
21
+ Thumbs.db
22
+
23
+ # Testing
24
+ .pytest_cache/
25
+ .coverage
26
+ htmlcov/
27
+
28
+ # Dev database
29
+ dev.db*
30
+ .superpowers
@@ -0,0 +1,539 @@
1
+ # Plan: `todo` CLI + TUI 命令行工具(树形嵌套版)
2
+
3
+ ## Context
4
+
5
+ 构建一个 Python 命令行 todo 工具,支持嵌套树形任务、CLI 快捷操作和 **Textual TUI** 交互界面。用 `uv tool install` 管理安装。**UI 与数据分离**,方便后续切换 TUI 组件库。包含 **audit 审计日志**,**软删除**,**SQLite 存储**(支持万级数据 + 内置并发锁 + ACID 事务)。
6
+
7
+ ## 项目结构
8
+
9
+ ```
10
+ /home/tangkikodo/todo/
11
+ ├── pyproject.toml
12
+ └── todo_cli/
13
+ ├── __init__.py # 暴露 main(), argparse 入口
14
+ ├── models.py # 数据模型 (dataclass)
15
+ ├── store.py # 数据层 — SQLite, 纯逻辑,无 UI 依赖
16
+ ├── tree.py # 树操作 (flatten, 宽字符处理)
17
+ ├── cli.py # CLI 视图 (rich)
18
+ └── tui.py # TUI 视图 (curses) — 可替换
19
+ ```
20
+
21
+ ### 分层架构
22
+
23
+ ```
24
+ ┌──────────────────────────────────────────┐
25
+ │ main() / argparse │ __init__.py
26
+ ├──────────┬───────────────────────────────┤
27
+ │ cli.py │ tui.py │ 视图层(可替换)
28
+ │ (rich) │ (textual) │
29
+ ├──────────┴───────────────────────────────┤
30
+ │ store.py + models.py │ 数据层(SQLite, UI 无关)
31
+ │ tree.py │ 树操作(UI 无关)
32
+ └──────────────────────────────────────────┘
33
+ ```
34
+
35
+ **关键原则**: `store.py` 和 `tree.py` 不 import 任何 UI 库。视图层通过公开 API 操作数据。切换 TUI 库时只需替换 `tui.py`。
36
+
37
+ **为什么用 SQLite 而非 JSON**:
38
+ - 万级数据无性能问题(增量更新,不需全量序列化)
39
+ - 内置文件锁(无需 `lock.py`,WAL 模式支持并发读)
40
+ - ACID 事务(崩溃安全,无需 signal handler 保数据)
41
+ - `sqlite3` 是 Python 内置模块,零额外依赖
42
+ - audit 日志与 todo 数据放同一个 DB,原子写入
43
+
44
+ ### pyproject.toml
45
+
46
+ ```toml
47
+ [project]
48
+ name = "todo-cli"
49
+ version = "0.1.0"
50
+ requires-python = ">=3.10"
51
+ dependencies = ["rich", "textual"]
52
+
53
+ [project.scripts]
54
+ todo = "todo_cli:main"
55
+ ```
56
+
57
+ 安装: `uv tool install .`
58
+ 更新: `uv tool install . --force`
59
+
60
+ ---
61
+
62
+ ## 数据库设计
63
+
64
+ 文件: `~/.todo.db`
65
+
66
+ ```sql
67
+ CREATE TABLE IF NOT EXISTS todos (
68
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
69
+ text TEXT NOT NULL,
70
+ done INTEGER NOT NULL DEFAULT 0, -- 0/1 boolean
71
+ parent INTEGER REFERENCES todos(id),
72
+ created TEXT NOT NULL, -- ISO 8601
73
+ deleted_at TEXT -- NULL=active, ISO 8601=软删除
74
+ );
75
+
76
+ CREATE TABLE IF NOT EXISTS audit (
77
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
78
+ timestamp TEXT NOT NULL, -- ISO 8601
79
+ action TEXT NOT NULL, -- 'add'|'toggle'|'delete'
80
+ todo_id INTEGER NOT NULL,
81
+ details TEXT -- JSON string
82
+ );
83
+
84
+ CREATE INDEX IF NOT EXISTS idx_todos_parent ON todos(parent);
85
+ CREATE INDEX IF NOT EXISTS idx_todos_active ON todos(deleted_at) WHERE deleted_at IS NULL;
86
+ CREATE INDEX IF NOT EXISTS idx_audit_time ON audit(timestamp);
87
+ ```
88
+
89
+ **连接配置:**
90
+ ```python
91
+ conn = sqlite3.connect(db_path)
92
+ conn.execute("PRAGMA journal_mode=WAL") # 并发读不阻塞
93
+ conn.execute("PRAGMA foreign_keys=ON") # 外键约束
94
+ conn.execute("PRAGMA busy_timeout=5000") # 写冲突等 5s
95
+ conn.row_factory = sqlite3.Row # 按列名访问
96
+ ```
97
+
98
+ **WAL 模式并发行为:**
99
+ - TUI 持有长连接:读写自由
100
+ - CLI `-l`:读操作,不阻塞 TUI
101
+ - CLI `-a`/`-x`:写操作,SQLite 自动排队(busy_timeout=5s)
102
+ - 无需手动文件锁
103
+
104
+ ---
105
+
106
+ ## 模块详细设计
107
+
108
+ ### models.py — 数据模型
109
+
110
+ ```python
111
+ @dataclass
112
+ class Todo:
113
+ id: int
114
+ text: str
115
+ done: bool
116
+ parent: int | None
117
+ created: str
118
+ deleted_at: str | None
119
+
120
+ @dataclass
121
+ class FlatRow:
122
+ """树扁平化后的一行,供 TUI/CLI 渲染"""
123
+ todo: Todo
124
+ depth: int
125
+ prefix: str # 树形连线前缀
126
+ has_children: bool
127
+ is_last_child: bool
128
+ ```
129
+
130
+ ### store.py — 数据层 (SQLite)
131
+
132
+ ```python
133
+ class TodoStore:
134
+ def __init__(self, db_path="~/.todo.db"):
135
+ self.db_path = os.path.expanduser(db_path)
136
+ self.conn = None
137
+
138
+ def connect(self) -> None
139
+ """打开连接,建表,WAL 模式。首次运行插入引导数据。"""
140
+
141
+ def close(self) -> None
142
+
143
+ # ── 查询 ──
144
+ def list_active(self) -> list[Todo] # WHERE deleted_at IS NULL
145
+ def get(self, todo_id: int) -> Todo | None
146
+ def get_children(self, todo_id: int) -> list[Todo] # active children
147
+ def get_descendants(self, todo_id: int) -> list[Todo]
148
+ def has_children(self, todo_id: int) -> bool
149
+
150
+ # ── 修改(每个操作一个事务,自动写 audit)──
151
+ def add(self, text: str, parent_id: int | None = None) -> Todo
152
+ def toggle(self, todo_id: int) -> bool # 仅叶子可 toggle + bubble_up
153
+ def delete(self, todo_id: int) -> int # 软删除 + 级联子孙
154
+ def bubble_up(self, todo_id: int) -> None # 内部调用
155
+
156
+ # ── Audit ──
157
+ def _log_audit(self, action: str, todo_id: int, details: dict) -> None
158
+ def get_audit(self, limit=50, action=None) -> list[dict]
159
+
160
+ # ── Context manager ──
161
+ def __enter__(self): ...
162
+ def __exit__(self): ...
163
+ ```
164
+
165
+ **关键实现细节:**
166
+
167
+ ```python
168
+ def add(self, text, parent_id=None):
169
+ with self.conn: # 自动 commit/rollback
170
+ cur = self.conn.execute(
171
+ "INSERT INTO todos (text, done, parent, created) VALUES (?, 0, ?, ?)",
172
+ (text, parent_id, datetime.now().isoformat())
173
+ )
174
+ todo_id = cur.lastrowid
175
+ self._log_audit("add", todo_id, {"text": text, "parent": parent_id})
176
+ return self.get(todo_id)
177
+
178
+ def toggle(self, todo_id):
179
+ todo = self.get(todo_id)
180
+ if not todo:
181
+ return False
182
+ if self.has_children(todo_id):
183
+ return False # 父节点不可手动 toggle
184
+ with self.conn:
185
+ new_done = not todo.done
186
+ self.conn.execute("UPDATE todos SET done=? WHERE id=?", (new_done, todo_id))
187
+ self._log_audit("toggle", todo_id, {"done": new_done})
188
+ self.bubble_up(todo_id)
189
+ return True
190
+
191
+ def delete(self, todo_id):
192
+ now = datetime.now().isoformat()
193
+ descendants = self.get_descendants(todo_id)
194
+ ids = [todo_id] + [d.id for d in descendants]
195
+ with self.conn:
196
+ placeholders = ",".join("?" * len(ids))
197
+ self.conn.execute(
198
+ f"UPDATE todos SET deleted_at=? WHERE id IN ({placeholders}) AND deleted_at IS NULL",
199
+ [now] + ids
200
+ )
201
+ self._log_audit("delete", todo_id, {"subtasks_count": len(descendants)})
202
+ return len(ids)
203
+
204
+ def bubble_up(self, todo_id):
205
+ """从当前节点向上冒泡,更新祖先的 done 状态"""
206
+ todo = self.get(todo_id)
207
+ if not todo or not todo.parent:
208
+ return
209
+ parent = self.get(todo.parent)
210
+ if not parent:
211
+ return
212
+ children = self.get_children(parent.id)
213
+ all_done = all(c.done for c in children) and len(children) > 0
214
+ if parent.done != all_done:
215
+ self.conn.execute("UPDATE todos SET done=? WHERE id=?", (all_done, parent.id))
216
+ self._log_audit("auto_toggle", parent.id, {"done": all_done, "triggered_by": todo_id})
217
+ self.bubble_up(parent.id) # 递归向上
218
+ ```
219
+
220
+ **Audit 合并到 store.py**: 不再需要独立的 `audit.py`。审计记录和 todo 数据在同一事务中写入,保证一致性。
221
+
222
+ ### tree.py — 树操作
223
+
224
+ ```python
225
+ def build_tree(todos: list[Todo]) -> dict[int | None, list[Todo]]
226
+ """构建 parent_id → children 映射"""
227
+
228
+ def get_roots(todos: list[Todo]) -> list[Todo]
229
+ """获取根任务列表"""
230
+
231
+ def flatten_tree(todos: list[Todo], collapsed: set[int]) -> list[FlatRow]
232
+ """扁平化,CLI -l 输出用"""
233
+
234
+ def display_width(s: str) -> int
235
+ def truncate_to_width(s: str, max_w: int) -> str
236
+ def format_time(iso_str: str) -> str # → "MM/DD HH:MM"
237
+ ```
238
+
239
+ 注: `strikethrough` 不再需要 — Textual 和 Rich 都原生支持 `strike` 样式。
240
+ `flatten_tree` 主要供 CLI 使用;TUI 由 Textual Tree 控件自行管理树结构。
241
+
242
+ ### tui.py — Textual TUI(可替换)
243
+
244
+ 使用 Textual 框架的内置控件,大幅简化 TUI 实现:
245
+
246
+ ```python
247
+ from textual.app import App, ComposeResult
248
+ from textual.widgets import Tree, Header, Footer, Input
249
+ from textual.binding import Binding
250
+
251
+ class TodoApp(App):
252
+ """TUI 主应用"""
253
+ BINDINGS = [
254
+ Binding("a", "add_root", "Add"),
255
+ Binding("tab", "add_child", "Sub-task"),
256
+ Binding("d", "delete", "Delete"),
257
+ Binding("space", "toggle", "Toggle"),
258
+ Binding("q", "quit", "Quit"),
259
+ ]
260
+
261
+ def __init__(self, store: TodoStore):
262
+ super().__init__()
263
+ self.store = store
264
+
265
+ def compose(self) -> ComposeResult:
266
+ yield Header()
267
+ yield TodoTree(self.store) # 自定义 Tree 子类
268
+ yield Footer()
269
+
270
+ # action methods...
271
+
272
+ class TodoTree(Tree):
273
+ """基于 Textual Tree 控件的 todo 树"""
274
+ # 利用 Tree 内置的展开/折叠/导航
275
+ # 自定义节点渲染 (checkbox, strikethrough, time)
276
+
277
+ def run_tui(store: TodoStore) -> None:
278
+ app = TodoApp(store)
279
+ app.run()
280
+ ```
281
+
282
+ **Textual 带来的简化:**
283
+ - **Tree 控件**: 内置展开/折叠、键盘导航、滚动,无需手动实现
284
+ - **Input 控件**: 内置文本输入+中文支持,无需手动 `get_wch()` 循环
285
+ - **Footer**: 自动显示快捷键绑定
286
+ - **CSS 样式**: 用 TCSS 文件或内联 CSS 控制颜色/布局
287
+ - **事件系统**: 装饰器绑定按键事件,比 curses 主循环清晰
288
+ - **自动 resize**: Textual 自动处理终端大小变化
289
+ - **鼠标支持**: 点击节点、滚轮免费获得
290
+
291
+ **自定义渲染:**
292
+ ```python
293
+ # 节点标签格式
294
+ def render_label(todo: Todo, has_children: bool) -> Text:
295
+ mark = "[✓]" if todo.done else "[ ]"
296
+ time = format_time(todo.created)
297
+ text = Text()
298
+ text.append(f"{mark} ", style="green" if todo.done else "")
299
+ text.append(f"#{todo.id} ", style="cyan")
300
+ if todo.done:
301
+ text.append(todo.text, style="strike dim")
302
+ else:
303
+ text.append(todo.text)
304
+ text.append(f" {time}", style="yellow")
305
+ return text
306
+ ```
307
+
308
+ ### cli.py — rich CLI
309
+
310
+ ```python
311
+ def cli_list(store: TodoStore) -> None
312
+ def cli_add(store: TodoStore, text: str, parent_id: int | None) -> None
313
+ def cli_toggle(store: TodoStore, todo_id: int) -> None
314
+ ```
315
+
316
+ ---
317
+
318
+ ## 交互方式
319
+
320
+ | 命令 | 功能 |
321
+ |---|---|
322
+ | `todo` | 进入 TUI |
323
+ | `todo -a '文本'` | 添加根任务 |
324
+ | `todo -a '文本' -p <id>` | 添加子任务 |
325
+ | `todo -l` | 树形列出(rich) |
326
+ | `todo -x <id>` | toggle 完成状态 |
327
+
328
+ ## 初始引导数据
329
+
330
+ 首次连接 DB(表为空)时自动插入 8 条引导数据:
331
+
332
+ ```
333
+ ▼ [ ] #1 快速上手 Todo CLI
334
+ ├── [ ] #2 按 Space 切换完成状态
335
+ ├── [ ] #3 按 Tab 添加子任务
336
+ └── [ ] #4 按 ←→ 折叠/展开树节点
337
+ ▼ [ ] #5 示例项目
338
+ ├── [✓] #6 设计方案
339
+ └── [ ] #7 编码实现
340
+ [ ] #8 按 d 删除此任务试试
341
+ ```
342
+
343
+ 在 `store.connect()` 中: 建表后检查 `SELECT COUNT(*) FROM todos` = 0 → 插入引导数据。
344
+
345
+ ## 自动完成(仅向上冒泡)
346
+
347
+ - 叶子 toggle → `bubble_up()` 逐级检查
348
+ - 同级 active 子任务全完成 → 父 `done=1` → 递归向上
349
+ - 取消子任务 → 父 `done=0` → 递归向上
350
+ - 父节点不可手动 toggle(`has_children` 返回 True 时拒绝)
351
+ - Space 在父节点 → 折叠/展开
352
+
353
+ ## TUI 线框图(Textual)
354
+
355
+ ### 正常状态
356
+
357
+ ```
358
+ ╭─ TODO ──────────────────────────────────────────── 8 items ─╮
359
+ │ │
360
+ │ ▼ [ ] #1 项目重构 04/27 14:30 │
361
+ │ ├── [ ] #2 重写认证模块 04/27 14:31 │
362
+ │ ├── [✓] #3 更新数据库 04/27 14:32 │
363
+ │ └── ▼ [ ] #4 写测试 04/27 14:33 │
364
+ │ ├── [ ] #5 单元测试 04/27 14:34 │
365
+ │ └── [✓] #6 集成测试 04/27 14:35 │
366
+ │ ▶ [✓] #7 日常事务 04/27 15:00 │
367
+ │ [ ] #8 独立任务 04/27 16:00 │
368
+ │ │
369
+ ╰──────────────────────────────────────────────────────────────╯
370
+ a Add Tab Sub-task Space Toggle d Delete q Quit
371
+ ```
372
+
373
+ 注: Textual 的 Tree 控件自带 ▶/▼ 折叠指示器和树形缩进连线。
374
+ Header 显示标题+计数,Footer 自动显示 BINDINGS 快捷键。
375
+ 高亮行由 Textual 的 cursor 机制自动处理(无需手动 `A_REVERSE`)。
376
+
377
+ ### 添加任务(弹出 Input)
378
+
379
+ ```
380
+ ╭─ TODO ──────────────────────────────────────────── 8 items ─╮
381
+ │ ...树形内容... │
382
+ ╰──────────────────────────────────────────────────────────────╯
383
+ ╭─ New todo ───────────────────────────────────────────────────╮
384
+ │ 写周报_ │
385
+ ╰──────────────────────────────────────────────────────────────╯
386
+ ```
387
+
388
+ ### 删除确认
389
+
390
+ ```
391
+ ╭─ Confirm ────────────────────────────────────────────────────╮
392
+ │ Delete "#4 写测试" and 2 subtasks? [Yes] [No] │
393
+ ╰──────────────────────────────────────────────────────────────╯
394
+ ```
395
+
396
+ ## TUI 快捷键(Textual Bindings)
397
+
398
+ | 键 | 操作 | 备注 |
399
+ |---|---|---|
400
+ | `↑` / `k` | 上移 | Tree 控件内置 |
401
+ | `↓` / `j` | 下移 | Tree 控件内置 |
402
+ | `Space` | 叶子: toggle; 父: 折叠/展开 | 自定义 action |
403
+ | `Enter` | 折叠/展开 | Tree 控件内置 |
404
+ | `Tab` | 添加子任务 | 弹出 Input 控件 |
405
+ | `a` | 添加根任务 | 弹出 Input 控件 |
406
+ | `d` | 删除(确认对话框) | 弹出 confirm |
407
+ | `t` | 切换过滤: All → Pending → Done | 循环切换 |
408
+ | `q` | 退出 | App.quit() |
409
+
410
+ ## 过滤功能
411
+
412
+ 三种过滤模式循环切换(按 `t`),**仅按根任务的 done 状态过滤**,子树整体跟随:
413
+
414
+ | 模式 | 显示内容 | Header 指示 |
415
+ |---|---|---|
416
+ | `All` | 所有根任务及其子树 | `TODO (8 items)` |
417
+ | `Pending` | 仅 `done=false` 的根任务 + 完整子树 | `TODO (5 pending)` |
418
+ | `Done` | 仅 `done=true` 的根任务 + 完整子树 | `TODO (3 done)` |
419
+
420
+ **CLI 支持**:
421
+ - `todo -l` — 所有任务
422
+ - `todo -l --pending` — 仅未完成根任务
423
+ - `todo -l --done` — 仅已完成根任务
424
+
425
+ **store.py**:
426
+ ```python
427
+ def list_roots(self, filter_done: bool | None = None) -> list[Todo]:
428
+ """获取根任务,可按 done 过滤"""
429
+ # WHERE parent IS NULL AND deleted_at IS NULL
430
+ # + optional: AND done = ?
431
+ ```
432
+
433
+ **tree.py**:
434
+ ```python
435
+ def filter_roots(todos: list[Todo], filter_done: bool | None) -> list[Todo]:
436
+ """按根任务 done 状态过滤,返回匹配的根及其所有子孙"""
437
+ ```
438
+
439
+ ## TUI 视觉(Textual CSS)
440
+
441
+ ```css
442
+ TodoTree {
443
+ height: 1fr;
444
+ }
445
+ TodoTree > .tree--cursor {
446
+ background: $accent; /* 高亮选中行 */
447
+ }
448
+ .done-label {
449
+ text-style: strike;
450
+ color: $text-muted;
451
+ }
452
+ .check-done {
453
+ color: green;
454
+ }
455
+ .todo-id {
456
+ color: cyan;
457
+ }
458
+ .todo-time {
459
+ color: yellow;
460
+ }
461
+ ```
462
+
463
+ ## CLI 输出(rich Tree)
464
+
465
+ ```
466
+ 📋 TODO (8 items)
467
+ ├── [ ] #1 项目重构 04/27 14:30
468
+ │ ├── [ ] #2 重写认证模块 04/27 14:31
469
+ │ ├── [✓] #3 更新数据库 04/27 14:32
470
+ │ └── [ ] #4 写测试 04/27 14:33
471
+ │ ├── [ ] #5 单元测试 04/27 14:34
472
+ │ └── [✓] #6 集成测试 04/27 14:35
473
+ ├── [✓] #7 日常事务 04/27 15:00
474
+ │ ├── [✓] #8 买菜 04/27 15:01
475
+ │ └── [✓] #9 寄快递 04/27 15:02
476
+ └── [ ] #10 独立任务 04/27 16:00
477
+ ```
478
+
479
+ ## 边界情况
480
+
481
+ | 场景 | 处理 |
482
+ |---|---|
483
+ | DB 不存在 | `connect()` 自动建库建表 |
484
+ | DB 损坏 | sqlite3 报错,打印提示退出 |
485
+ | `-x` 父节点 | "Has subtasks, complete them instead" |
486
+ | 删除父节点 | 软删除节点+所有子孙,确认显示子任务数 |
487
+ | `-x` 已软删除的节点 | "Todo #N not found" |
488
+ | 并发写冲突 | SQLite `busy_timeout=5s` 自动重试 |
489
+ | TUI + CLI 同时运行 | WAL 模式: 读不阻塞,写排队 |
490
+ | 终端太小 | Textual 自动处理 |
491
+ | 深嵌套 | Tree 控件自动缩进 |
492
+ | CJK | Rich/Textual 原生支持 CJK 宽字符 |
493
+ | resize | Textual 自动处理 |
494
+ | SIGKILL | SQLite WAL 自动恢复,事务级数据安全 |
495
+
496
+ ## 实现顺序
497
+
498
+ 1. `pyproject.toml` + 包骨架 (`__init__.py`, `models.py`)
499
+ 2. `store.py` — SQLite 连接 + 建表 + CRUD + bubble_up + audit + 引导数据
500
+ 3. `tree.py` — flatten_tree + 显示辅助
501
+ 4. `cli.py` — rich tree 输出 + CLI 命令
502
+ 5. `__init__.py` — argparse + main()
503
+ 6. `uv tool install .` 验证 CLI 可用
504
+ 7. `tui.py` — Textual App 骨架: Header + TodoTree + Footer + quit
505
+ 8. `tui.py` — TodoTree 自定义渲染 (checkbox, strikethrough, time)
506
+ 9. `tui.py` — Space toggle + 数据刷新
507
+ 10. `tui.py` — Input 弹出 (a 添加根任务, Tab 添加子任务)
508
+ 11. `tui.py` — 删除确认对话框
509
+ 12. 收尾: 空状态提示, 边界处理
510
+
511
+ ## 验证
512
+
513
+ ```bash
514
+ cd /home/tangkikodo/todo
515
+ uv tool install .
516
+
517
+ # CLI 测试(首次运行自动建库+引导数据)
518
+ todo -l # 树形展示引导数据
519
+ todo -a '新任务'
520
+ todo -a '子任务' -p 9
521
+ todo -x 10 # toggle 叶子
522
+ todo -l # 确认变更
523
+
524
+ # TUI 测试
525
+ todo # 导航、toggle、折叠、添加、删除
526
+
527
+ # 并发测试
528
+ todo & # 后台 TUI
529
+ todo -l # 应正常读取(WAL 不阻塞读)
530
+ todo -a 'concurrent' # 应正常写入(排队等待)
531
+ fg # 回到 TUI 确认新任务可见
532
+
533
+ # 审计
534
+ sqlite3 ~/.todo.db "SELECT * FROM audit ORDER BY id DESC LIMIT 10"
535
+
536
+ # 首次运行
537
+ rm ~/.todo.db
538
+ todo # 自动建库 + 引导数据
539
+ ```
todoium-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,14 @@
1
+ Metadata-Version: 2.4
2
+ Name: todoium
3
+ Version: 0.1.0
4
+ Author-email: tangkikodo <allmonday@126.com>
5
+ Requires-Python: >=3.10
6
+ Requires-Dist: aiosqlite>=0.22.1
7
+ Requires-Dist: alembic>=1.18.4
8
+ Requires-Dist: fastapi>=0.136.1
9
+ Requires-Dist: httpx>=0.28.1
10
+ Requires-Dist: pydantic-resolve>=5.3.1
11
+ Requires-Dist: rich
12
+ Requires-Dist: sqlalchemy[aiosqlite]>=2.0.49
13
+ Requires-Dist: textual
14
+ Requires-Dist: uvicorn[standard]>=0.46.0
@@ -0,0 +1,66 @@
1
+ # t
2
+
3
+ 命令行树形 TODO 工具,支持 CLI 和 TUI 两种交互方式。
4
+
5
+ ## 安装
6
+
7
+ 需要 Python >= 3.10,推荐使用 [uv](https://github.com/astral-sh/uv)。
8
+
9
+ ```bash
10
+ # 首次安装
11
+ uv tool install .
12
+
13
+ # 代码改动后重新安装(uv 有构建缓存,需要先清缓存再装)
14
+ uv cache clean todo_cli && uv tool install --force .
15
+ ```
16
+
17
+ 安装后 `todo` 命令会放在 `~/.local/bin/todo`,确保 `~/.local/bin` 在 `$PATH` 中。
18
+
19
+ ## 使用
20
+
21
+ ```bash
22
+ t # 进入 TUI 交互界面
23
+ t -l # 树形列出所有任务
24
+ t -l --pending # 仅未完成
25
+ t -l --done # 仅已完成
26
+ t 任务内容 # 添加根任务
27
+ t 子任务 -p <id> # 添加子任务
28
+ t -x <id> # 切换完成状态
29
+ ```
30
+
31
+ ## TUI 快捷键
32
+
33
+ | 键 | 操作 |
34
+ |---|---|
35
+ | `j` / `k` | 上下移动 |
36
+ | `Space` | 切换完成状态(仅叶子节点) |
37
+ | `h` / `l` | 折叠 / 展开 |
38
+ | `m` | 全部折叠 / 展开 |
39
+ | `a` | 添加根任务 |
40
+ | `Tab` | 添加子任务 |
41
+ | `d` | 删除任务 |
42
+ | `t` | 切换过滤:All -> Pending -> Done |
43
+ | `q` | 退出 |
44
+
45
+ 过滤状态显示在顶部标题栏。过滤按根任务的完成状态生效,子任务跟随根任务显示。
46
+
47
+ ## 数据存储
48
+
49
+ 数据库文件:`~/.todo.db`(SQLite,首次运行自动创建,自动执行数据库迁移)。
50
+
51
+ ## 开发
52
+
53
+ ```bash
54
+ # 首次:初始化开发数据库
55
+ uv run alembic upgrade head
56
+
57
+ # 修改 ORM model 后生成迁移
58
+ uv run alembic revision --autogenerate -m "描述"
59
+ uv run alembic upgrade head
60
+
61
+ # 使用开发数据库运行
62
+ TODO_DB=dev.db uv run t
63
+
64
+ # 重置开发数据库
65
+ rm dev.db && uv run alembic upgrade head
66
+ ```