Install
openclaw skills install @awamwang/awam-todoPersonal todo management skill. Parses natural-language todos from the session (task text, importance, urgency, note, workspace dir, related docs, links, due time), appends them to date-partitioned storage files, and auto-maintains an index (todo / in_progress / urgent / done / archived). Detects identical and near-duplicate tasks on add: identical → confirm to update the existing task (default), similar → confirm update, force-create, or cancel. Supports dependency chains (hard-blocked progress when prerequisites are unfinished — exit 3, AI asks the user, --force overrides) and subtasks (--parent; finishing a parent with open children is blocked). Each todo can carry an if-then contingency plan (blocker/counter) shown on cards. Overdue todos get two exits (advance now / reschedule via postpone). Overdue/stalled/due-today items are pinned with a reason. Storage is schema-versioned with migrate, plus monthly archiving. Weekly stats start on Monday; derived metrics show "N/A" when data is insufficient — never invent numbers. Missing tags are suggested read-only (suggest-tags) and confirmed before writing. storage_dir and editor are configurable via env --set; an optional local web UI (server.py) is prompted after mutations (ui.port / ui.prompt). Language policy: the AI conversation follows the user's language; CLI/API output is fixed English; the web UI is i18n (English default, switchable to Chinese; inline, offline-safe). A triage entry (triage) decides add / update / status / delete from one sentence with pure rules (no LLM), returns JSON, and asks for confirmation when unsure; an optional capture service (global hotkey reads the clipboard — no filtering, the clipboard text is used as the task text — feeds the triage entry and shows system dialogs for anything that needs confirmation or input) runs inside the web UI process and is managed from a Settings page (hotkey + triage-type toggles). Use when the user says 记一下/加个待办/帮我记/安排XX/提醒我X日做XX (or English equivalents: add a todo, remind me to …, note this down) or invokes /awam-todo; also for listing, completing, starting, reopening, archiving, setting dependencies, and managing subtasks.
openclaw skills install @awamwang/awam-todoA personal todo management skill. All data lives in storage/ under this
skill's directory (not tracked by Git), the index lives in index.json
(not tracked by Git), and machine-local environment info lives in
env.json (not tracked by Git, auto-generated by init). The core
command is scripts/todo.py (Python 3).
Documentation layout (this package is bilingual):
SKILL.md — canonical runbook (English).SKILL.zh-CN.md — Chinese mirror of the same runbook.docs/OPTIMIZATION.md — optimization roadmap (English).docs/优化建议.md — the same roadmap in Chinese.| User intent | Action |
|---|---|
| First use / setting up on a new machine | init (detect the environment, probe the editor, save config, normalize workspaces) |
| View / modify environment config (path style etc.) | env (--set, --reformat-workspaces) |
| Move the storage location / migrate existing data | env --set storage_dir=<dir> + --migrate (see "Configurable capabilities") |
| Change the editor used for "open in editor" | env --set editor.path=<absolute path or command name> (see "Configurable capabilities") |
| Add a todo | parse → add (with duplicate detection, see below) |
| Check for duplicates before adding | check --text "..." (read-only) |
| Suggest tags read-only (suggest only, never write) | suggest-tags --text "..." (JSON output) |
| View todos | list (filter by state / date) |
| Complete a todo | done <id> (asks for confirmation when prerequisites are unfinished) |
| Mark in-progress / reopen | start <id> (asks when prerequisites unfinished) / reopen <id> |
| Continue working (mark in-progress and open the workspace in the configured editor) | work <id> (alias continue; asks when prerequisites unfinished) |
| Overdue: reschedule / clear the deadline | postpone <id> "next Monday" (aliases reschedule / replan; --clear cancels the deadline) |
| Set / view task dependencies | dep <id> [--add/--remove/--set/--clear] <dep_id...> |
| List subtasks under a parent task | children <id> (alias sub) |
| Add a subtask (belonging to a parent) | add --parent <parent-id> ... |
| Detach a subtask from its parent | deparent <id> |
| Force-archive one day file | archive YYYY-MM-DD |
| Monthly archive (previous months' completed records) | archive-month [--month YYYY-MM] [--all] [--dry-run] (alias monthly-archive) |
| Migrate old storage to the current schema | migrate (preview by default) / migrate --apply |
| View the index (urgent / today / overdue / in_progress / open / done) | index |
| View one task, a day file, or a month archive | `show <id |
| Triage one sentence (add/update/status/delete) | triage --text "..." (read-only, JSON; exit 3 = needs confirmation) |
| Delete a todo (also cleans dependency/parent references) | delete <id> |
| Capture the clipboard with a global hotkey (no filtering) | Start the web UI (server.py); press the hotkey (default Ctrl+Alt+T); clipboard text feeds the triage entry |
| Manage the capture service (hotkey / triage types / toggles) | Web UI Settings page, or env --set capture.* |
| Command | Purpose |
|---|---|
add | Add a todo (identical → default to updating; similar → confirm; --force skips checks) |
check | Read-only duplicate check |
suggest-tags | Read-only tag suggestion (JSON) |
done / start / reopen | Complete / mark in-progress / reopen (dependency guard on done & start) |
work (continue) | Mark in-progress + open the workspace in the configured editor |
postpone (reschedule, replan) | Reschedule or clear the deadline (overdue exit #2) |
dep | View / set / append / remove / clear dependencies |
children (sub) / deparent | List / detach subtasks |
archive / archive-month (monthly-archive) | Archive a day file / run monthly archiving |
migrate | Preview or apply schema migration |
list / show / index | List todos / view a task, day file or month / view the index |
triage | Read-only triage of one sentence → add/update/status/delete (+parsed fields), JSON; exit 3 when confirmation is needed |
delete | Delete a todo (also cleans other tasks' dependency/parent references) |
init / env | Detect & save the environment / view & modify config |
python "<skill-directory>\scripts\todo.py" <command> <args>
<skill-directory> is the installation directory of this skill (on Windows
typically C:\Users\<user>\.agents\skills\awam-todo; substitute the actual
install location). Commands below are abbreviated as python ...\todo.py.
| Layer | Language | Notes |
|---|---|---|
| AI conversation in session | Follows the user's language | Reply in the language the user is speaking (Chinese ↔ English, etc.); when the user writes Chinese, reply in Chinese; when English, in English. Never force one fixed language. |
CLI / API output (todo.py, server.py) | English, fixed | All user-facing CLI output, argparse help, the tkinter dialog, web API error/message fields and startup logs are English. Data values are exempt: statuses (维护 / 进行中 / 结束 / 待开始 / 其他), importance (重要 / 不重要), urgency (紧急 / 不紧急), tags, view names and the storage-file keys (状态: / 重要: / 内容: …) remain literal Chinese runtime values — pass them to the CLI exactly as written and display them as-is. |
Web UI (web/index.html) | i18n: English default, switchable to Chinese | Inline i18n word tables (zh/en) with no external libraries, offline-safe. Preference is stored in localStorage under awam-todo:lang; default en. A language selector sits in the top bar (English / 中文). Form <select> values submitted to the backend remain the Chinese data values; only displayed labels/options are translated. Data values from index.json are shown as-is, never translated — e.g. pin_reason is generated in English by the CLI's pinning logic and stays English in the UI. |
Changing the UI language affects display only — never the data. The AI must keep passing Chinese data values (status / importance / urgency / tags / view names) to the CLI in all languages.
awam-todo detects the runtime environment automatically and normalizes
workspace paths to one consistent format.
init — detect and save the environmentRun init on first use or after switching machines:
python ...\todo.py init
It probes the environment and saves it to env.json:
| Field | Meaning |
|---|---|
path_style | Path style: windows / posix / mixed (auto by OS; Windows → windows) |
platform.os | OS name (Windows / Linux / Darwin …) |
platform.is_windows | Whether the OS is Windows |
platform.python_version | Python version |
platform.hostname / platform.cwd | Hostname / current working directory |
dirs.skill_dir / dirs.storage_dir | Skill directory / effective storage directory (changeable via env --set storage_dir) |
editor | The editor used by "open in editor" (path / label / args; written by probing or env --set editor.*) |
init keeps any saved storage directory and editor config (probing never
overwrites them); if editor is unset, it is probed once automatically.
init also normalizes the workspace paths inside existing storage files
to the current path_style (e.g. on Windows, all converted to
G:\Projects\... backslashes).
--workspace is normalized to
G:\Projects\... (backslash) format before being written, regardless of
whether slashes or backslashes were passed.list / show / index / add always display
workspaces in Windows backslash format.--docs, --links (URLs) are never path-converted.env.json's path_style; when uninitialized, it is
inferred from the current OS (Windows → windows).env — view / modify environment configpython ...\todo.py env # view current config (storage dir + editor)
python ...\todo.py env --set path_style=windows # change path style (windows/posix/mixed)
python ...\todo.py env --reset # re-probe and overwrite (including editor)
python ...\todo.py env --set path_style=posix --reformat-workspaces
# change style and rewrite all workspaces
--reformat-workspaces: normalizes all workspace paths in every storage
file to the current path_style (use it together with a style change).env.json immediately; after a style change,
re-run init or --reformat-workspaces to sync existing data.The skill has four configurable items, all stored in env.json and all
editable via env --set. When the user mentions "store todos somewhere
else", "I don't use Cursor", "open with ", "web port", "stop
asking me to start the UI", "change the capture hotkey", or "disable
capture", go through these — do not edit code.
storage_dir| Item | Value |
|---|---|
| Default | storage/ under the skill directory (auto fallback when unset) |
| Authoritative location | dirs.storage_dir in env.json |
| Scope | All reads/writes: day files, archive/ monthly archives, index.json rebuild |
# When the old directory still has todos, you MUST choose one of the two,
# otherwise it errors (exit code 2) and leaves config untouched:
python ...\todo.py env --set storage_dir="D:\awam-todo-data" --migrate # move data too
python ...\todo.py env --set storage_dir="D:\awam-todo-data" --no-migrate # config only, keep data
Migration semantics (_migrate_storage, a hard data-safety constraint —
do not bypass):
YYYY-MM-DD.md + archive/);Paths support ~ and %VAR% / $VAR environment-variable expansion; relative
paths resolve against the skill directory. Writability is verified before
writing.
editor"Open workspace in editor" (CLI work, web path menu) no longer hard-codes
Cursor — it uses the editor section of env.json:
| Key | Meaning |
|---|---|
editor.path | Required. Absolute path to the editor executable, or a command name on PATH (e.g. code / cursor) |
editor.label | Optional. Display name for messages and the web menu (e.g. "Open with VS Code") |
editor.args | Optional. Fixed leading arguments, multiple separated by ; |
python ...\todo.py env --set editor.path="D:\Apps\Cursor\Cursor.exe" # absolute path
python ...\todo.py env --set editor.path=code # or a command on PATH
python ...\todo.py env --set editor.label="VS Code"
python ...\todo.py env --set "editor.args=--new-window"
python ...\todo.py env --set ui.port=9000 # web UI port (default 8796, see item 3)
python ...\todo.py env --set ui.prompt=off # stop asking to start the web UI (see item 3)
init / env --reset auto-probe common editors (Cursor, VS Code, Trae,
Windsurf, Sublime Text, Zed). The probe table EDITOR_CANDIDATES is a
constant in todo.py; to add another editor, add one entry.editor is left unset for you to specify.work prints an error plus a copyable config command (its primary job,
"mark in-progress", still completes with return code 0); the web does not
render the "open in editor" menu item. Do not degrade into "guess a
path from the software name" or "silently open with the system default".uiSwitch and port for "after add/maintain, check the local web service and ask whether to start the UI":
| Key | Meaning |
|---|---|
ui.port | Web service port, default 8796 (matches web/server.py default) |
ui.prompt | ask (default) / off. ask = after a successful mutation, if the service is not running, ask whether to start it; off = never check, never ask |
python ...\todo.py env --set ui.port=9000 # change port (probe & launch at this port when down)
python ...\todo.py env --set ui.prompt=off # disable "ask to start the web UI after mutations"
AWAM_TODO_UI_PROMPT=off; it takes precedence over env.json.todo.py; the web service's
own port still follows its launch argument (server.py --port).captureGlobal hotkey + clipboard capture that feeds the triage entry, running inside the web UI process (see "Capture service" below for the full flow):
python ...\todo.py env --set capture.enabled=false # stop listening with the UI
python ...\todo.py env --set capture.hotkey="Ctrl+Shift+X" # change the hotkey (re-registers on UI start)
python ...\todo.py env --set capture.confirm_delete=false # delete without asking (not recommended)
python ...\todo.py env --set capture.toast=false # no corner toast
python ...\todo.py env --set capture.triage_types.delete=false
# disable delete triage (falls back to Add + ask)
env output prints the current capture configuration line
(Capture service (hotkey reads clipboard -> triage): …). Invalid hotkeys
and unknown sub-keys are rejected with exit 2.
add)When the user states a todo in natural language, extract the following fields
and pass them to add; unspecified values get their defaults.
| Field | Argument | Parse rule | Default |
|---|---|---|---|
| Task text | --text | Required; the core thing the user wants done | — |
| Importance | --importance | 重要 / 不重要 | 不重要 |
| Urgency | --urgent | 紧急 / 不紧急 | 不紧急 |
| Status | --status | 维护 / 进行中 / 结束 / 待开始 / 其他 | 进行中 |
| Due time | --due | Auto-parsed time (see mapping below); if not mentioned → no deadline | none |
| Note | --note | Extra explanation from the user | empty |
| Blocker (contingency) | --blocker | The obstacle most likely to stop me; optional; shown directly on cards and lists | empty |
| Counter (contingency) | --counter | "If it happens, I will…"; optional; pairs with the blocker | empty |
| Tags | --tags | Category tags, multiple separated by ; (e.g. 工作;学习); when omitted the script suggests but does not write (see below) | empty |
| Workspace dir | --workspace | A project/directory the user names (e.g. "put it in the XX project") | empty |
| Related docs | --docs | Related files/docs, multiple separated by ; | empty |
| Links | --links | Related URLs, multiple separated by ; | empty |
| Dependencies | --deps | Prerequisite task IDs, multiple separated by ; (e.g. T-A;T-B); must exist and be acyclic | empty |
| Parent task | --parent | The parent task this task belongs to (as its subtask); must exist, no cycles | empty |
| Source | --from-type / --from-url | Source type (agent/link/web) + source link (e.g. an AI conversation link); URL left empty → not recorded | none |
| Storage date | --date | When the user says "put it on day X" → store in that day's file | today |
Note: statuses, importance, urgency, tags and view names are literal Chinese runtime values — pass them to the CLI exactly as written above; the English in this document is only the explanation.
add)Every add first scans all storage and compares normalized task text:
| Result | Script behavior | What the agent MUST do |
|---|---|---|
| Identical (normalized text matches fully) | No new task, exit code 3, lists the matched task | Confirm with the user; the default is to update the existing task's status (recommend --update-id <id>), do not create a duplicate |
| Similar (similarity ≥ ~72%, or one clearly contains the other) | No new task, exit code 3, lists the matched task | Confirm with the user: update a specific ID / still create / cancel |
| No match | Creates normally | Report the result |
| User confirmed the update | add --update-id <id> ... | Without --status, the script sets the status to 「进行中」; other fields are only overwritten when explicitly passed |
| User insists on creating | add --force ... | Skip duplicate detection and force-create |
Confirmation wording (identical):
An identical todo already exists:
T-…(进行中): …… By default I would update that task's status instead of creating a new one. Confirm the update? Or force-create / cancel?
Confirmation wording (similar):
Found a similar todo
T-…(similarity xx%): …… Please choose: update that one / still create / cancel.
Read-only precheck: check --text "..." (also returns exit 3 on a hit).
The AI creating todos in session is the primary creation path, and tags are often not stated by the user. To avoid "created it and only then realized it wasn't categorized", the script can suggest tags — but it never writes them on your behalf: tags are the user's own classification scheme; suggestions are only suggested values to confirm.
| Scenario | Script behavior | What the agent MUST do |
|---|---|---|
add without --tags (or empty) | After the suggestion, prints 建议标签: …(尚未写入;确认后用:add --update-id <id> --tags "…") and 依据: … | Repeat the suggestion to the user and confirm; only then persist with --update-id |
add with explicit --tags | No suggestion; writes the passed value | Nothing extra |
add --update-id updating an existing task | No suggestion (early return) | Nothing extra |
| Suggestion is uncertain | Returns empty, shows nothing | Don't press the user, and don't invent a tag |
| Ask first, then create (recommended) | suggest-tags --text "..." outputs JSON with suggestions / details / reason / written:false | Get suggestions, ask the user, then create with one add --tags "…" |
Evidence used (three kinds, combinable; only shown when confidence passes):
storage/; reuses tags
of historical tasks whose text is sufficiently related;src/dev/repo,
course/learn, doc/wiki) — auxiliary evidence only; never enough
alone for a suggestion.English matches use word boundaries (debug won't be misjudged as 开发
because it contains bug). The word tables and thresholds are Chinese
constants declared together at the top of scripts/todo.py
(TAG_RULES / TAG_WORKSPACE_RULES / TAG_MIN_SCORE etc.) — edit the words
or thresholds directly.
Iron rule, continuing this skill's no-invention principle: when unsure, give no suggestion — better the user fills it in than a plausible-looking tag is inserted.
Tasks can carry prerequisite dependencies (other todo IDs), forming dependency chains. The dependency check itself is a dead hard rule; when it conflicts, the AI recommends how to proceed (executed after user confirmation).
Setting dependencies (referenced IDs must exist; existence and cycle detection run — self-reference, cycles, or missing references error out):
python ...\todo.py dep T-20261002-005 # view dependencies
python ...\todo.py dep T-20261002-005 --add T-20261002-001 T-20261002-003 # append
python ...\todo.py dep T-20261002-005 --set T-20261002-001 # set (replace)
python ...\todo.py dep T-20261002-005 --remove T-20261002-001 # remove
python ...\todo.py dep T-20261002-005 --clear # clear
You can also carry dependencies at creation: add --deps "T-A;T-B".
Hard check rule (dead): when advancing a target to 进行中 (start /
work) or 结束 (done), if not all direct prerequisites are 结束,
the script does not persist, prints the conflict list, and returns exit
3 (the same "needs confirmation" signal as duplicate detection) prompting
the user to confirm. reopen (back to 待开始) is not subject to dependency
constraints.
Conflict handling (AI recommends): when the script returns exit 3 and
the output says "dependency check failed", the agent should give the user a
recommendation with options, e.g.:
This task depends on
T-…(待开始): ……, which is not finished yet, so it cannot advance to 「进行中/结束」. Please choose: ① ignore dependencies and force advance (--force) ② finish the prerequisite(s) first ③ cancel.
--force
(done <id> --force / start <id> --force / work <id> --force).结束, later advances pass automatically without
further confirmation.blocked field (whether any prerequisite is
unfinished); list marks such tasks "(被依赖阻塞)".Note: add's --force is the single override switch for both "skip
duplicate detection" and "skip the dependency hard check"; without --force,
a new task starting at 进行中/结束 with unfinished prerequisites is also
blocked.
A subtask belongs to one parent task; all other attributes (status / importance / urgency / note / workspace / docs / links / due / dependencies) are identical to ordinary tasks. A task can have a parent and its own subtasks (nestable, but parent-child chains must not cycle).
python ...\todo.py add --text "打包模块" --parent T-20261002-001 # add a subtask (to a parent)
python ...\todo.py children T-20261002-001 # list subtasks of a parent
python ...\todo.py sub T-20261002-001 # same (alias)
python ...\todo.py deparent T-20261002-005 # detach from parent
python ...\todo.py show T-20261002-005 # view (includes 父任务 field)
Parent-completion hard check (dead): marking a task that still has
unfinished subtasks as 结束 (done) does not persist; the script prints
the unfinished-subtask list and returns exit 3 (same "needs confirmation"
signal as dependency/duplicate checks) for the AI to confirm with the user.
Conflict handling (AI recommends):
This parent task still has unfinished subtasks
T-…(进行中): ……, so it cannot be marked 「结束」. Please choose: ① ignore subtasks and force-finish (--force) ② finish the subtasks first ③ cancel.
done <id> --force.--parent at creation validates that the parent exists, is not a
self-reference, and has no cycle; updating a task's parent (via
add --update-id or the web UI) validates the same way.parent (parent task ID) field.--due is handed to the script, parsed by these rules)X月X日, 2026-10-05 → the specified dateHH:MM, X点, X点半 → that time today明天 10:00, 周五 09:00, 3天后 15:00X小时后 / X分钟后 → computed from the current moment--due (no deadline), and tell the user in the
reply "no time recognized, no deadline set".User: 记一下,明天上午十点前写好季度报告,重要,备注里带上Q3数据,放到 19AI 项目,
参考 data.xlsx,附上 https://example.com/report,标签 工作;汇报
→ python ...\todo.py add --text "写好季度报告" --importance 重要 --due "明天 10:00" \
--note "带上Q3数据" --workspace "G:\Projects\19AI" --docs "data.xlsx" \
--links "https://example.com/report" --tags "工作;汇报"
# If exit 3 and the task is identical (T-20261002-001), and the user confirms "update":
→ python ...\todo.py add --update-id T-20261002-001 --text "写好季度报告" --importance 重要 \
--due "明天 10:00" --note "带上Q3数据" --workspace "G:\Projects\19AI"
# If the user insists on creating:
→ python ...\todo.py add --force --text "写好季度报告" ...
triage) — one sentence → add / update / status / deleteA read-only, pure-rule entry that decides what a sentence means for the todo list. No LLM involved — the AI conversation may use it directly (e.g. the user pastes a random sentence), and the capture service feeds it from the clipboard. It never writes anything by itself.
python ...\todo.py triage --text "把继续detect-agent软件的开发标记为完成" # JSON on stdout
python ...\todo.py triage --text "..." > triage.json
echo %ERRORLEVEL% # 0 = decisive, 3 = needs confirmation
Decision order: delete > status > update > add. Questions ("…?") and
utterances with no actionable verb fall back to unknown + need_confirm.
Promises with lead-ins like 需要/要/必须/应该/记得 that target no existing
task are forced back to add (a commitment to a new task). Fields extracted
by rule: due (明天/下周一/3天后/HH:MM…), important/urgent (重要/紧急),
备注:, 标签:, workspace paths, and update/delete subjects. The target is
resolved against real tasks by T-ID or by text similarity (prefers unfinished
tasks); when a target cannot be locked with enough confidence, the result is
need_confirm with candidates (or a fallback_action=add).
JSON contract (cmd_triage output):
| Field | Meaning |
|---|---|
action | add / update / status / delete / unknown |
confidence | high / medium / low |
need_confirm | whether the caller must confirm before executing (destructive delete always true) |
reasons | why confirmation is needed (destructive / question / duplicate / target_not_found / action_disabled …) |
text | the original sentence, verbatim (no rule filtering — this is the captured task content) |
target / candidates | matched task {id, text, score, kind} (kind: exact / similar) |
status | target status for a status action (结束/进行中/待开始/维护/其他) |
fields | parsed extras: due / importance / urgent / note / tags / workspace / text (for updates) |
suggested_tags | read-only tag suggestions for add (never written) |
fallback_action | what to propose when the target is missing (usually add) |
AI usage in session: run triage --text "<the user's sentence>"; if
need_confirm is true (or action=delete), confirm with the user first, then
execute the underlying command (add / done/start for status / add --update-id / delete). If need_confirm is false and action=add, proceed
with add and the parsed fields.
A no-filter capture: whatever text is on the clipboard when the hotkey is
pressed is handed verbatim to the triage entry as the task content. No
keyword filtering, no length check — the raw clipboard text is the task
content. The service runs inside the web UI process (web/server.py), so
it is started/stopped together with the UI and configured from the UI's
Settings page.
python ...\web\server.py). On startup it
registers the hotkey (default Ctrl+Alt+T) and prints
Capture service: listening: ….triage_text →
applies the enabled triage types → executes:
need_confirm=false (high-confidence add/status/update) → executes
directly, shows a corner toast (toggleable);capture.confirm_delete);Configuration (all in env.json under capture, managed from the web UI
Settings page or env --set capture.*):
| Key | Meaning |
|---|---|
capture.enabled | true (default) / false — start the hotkey listener with the UI |
capture.hotkey | Ctrl+Alt+T (default). Modifiers Ctrl/Alt/Shift/Win + F1–F24, a letter/digit, Space, Enter, Tab, Esc; at least one modifier required |
capture.confirm_delete | true (default) — always confirm before deleting from the capture dialog |
capture.toast | true (default) — corner toast after each capture |
capture.triage_types | {add, update, delete, status} all true by default; a disabled action always asks and falls back to add |
The Settings page also offers a Try triage box: paste a sentence, see the
JSON verdict read-only (POST /api/capture/triage) — nothing is written or
executed.
Each todo can carry an implementation intent (if-then) pair, persisted as
障碍: / 对策: lines and exposed in the index as blocker / counter:
python ...\todo.py add --text "背完 2000 个单词" \
--blocker "晚上刷手机" --counter "拿起手机前先背 20 个"
逾期 N 天; no deadline, completed, or not yet overdue never
shows the number (do not show 「逾期 0 天」).STALL_DAYS (default 3) since last update
(or creation) → 已 N 天未推进,滚入今日.pin / pin_reason in the index) — no silent
reordering.start <id> / done <id>postpone <id> "下周一" (reset the deadline) or
postpone <id> --clear (remove the deadline)python ...\todo.py postpone T-20261002-001 "下周一 10:00" # reschedule
python ...\todo.py postpone T-20261002-001 --clear # remove the deadline
python ...\todo.py list --state overdue # only overdue (with days + exit hints)
python ...\todo.py list --state today # what to handle today (overdue/stalled/due today)
When a time can't be parsed, postpone errors and keeps the original value —
never guess a date.
Derived metrics return None when input is missing, rendered as — or
暂无推算:
| Metric | With data | Without data |
|---|---|---|
| Overdue days | 逾期 N 天 | field absent |
| Days until due | N 天 | — |
| Stall days | N 天 | — |
| Projected finish | needs ≥ 3 progress-record samples to project | 暂无推算(缺少进展记录) |
The current version has no progress-record layer, so projected finish is
always 「暂无推算」. This is deliberate — the record-layer design is in
docs/OPTIMIZATION.md (items 7/8). Never output fake numbers just to look
complete.
Any weekly statistic uses Monday 00:00 through Sunday 23:59
(WEEK_START_ISO = 1). The index's week section provides
start / end / created / done. Future weekly comparisons must use the
same boundary.
schema: N (current SCHEMA_VERSION = 2);
index.json carries schema_version at the top level.schema line are treated as v1; migrate previews by
default and only --apply rewrites (read and write back in the current
format, filling in the schema header and defaults for new fields,
lossless)._parse_block / _write_block /
_task_view / build_index together and bump SCHEMA_VERSION,
otherwise the web front-end won't see the new field.When a new month begins, day files from previous months and earlier whose
tasks are all finished are merged into storage/archive/YYYY-MM.md and the
original day files are deleted; files with unfinished tasks stay put.
Archive files carry 月份: / 来源文件: headers and each task carries an
原日期: line; viewable via show YYYY-MM. Archive files do not participate
in daily indexing — they are only counted in index.json's
monthly_archives / summary.archived_tasks.
The automatic trigger lives in build_index() (runs once per process; read
commands can also trigger it — expected behavior); manual trigger is
archive-month, with --dry-run to preview first.
The defaults section of env.json controls the default status /
importance / urgency for new todos:
python ...\todo.py env --set defaults.status=待开始
python ...\todo.py env --set defaults.importance=重要
The web creation form prefills with the same values; the user can change them anytime. Defaults are suggested values, not constraints.
storage/YYYY-MM-DD.md; each file has 归档: true/false,
schema: N headers plus task blocks
(## T-YYYYMMDD-NNN + 状态/重要/紧急/内容/父任务/备注/障碍/对策/工作空间/文档/链接/标签/依赖/来源/截止/创建/更新 lines).
依赖: T-A; T-B records prerequisite task IDs; 父任务: T-X records the
parent task; 标签: 工作; 学习 records category tags (;-separated).from: optional object {type, url} recording where the todo
came from (e.g. an AI agent conversation link), written via
--from-type / --from-url; when the conversation link is unavailable,
leave it empty (not persisted, not indexed) — never invented.T-YYYYMMDD-NNN, incremented within the day, auto-generated.维护 [ ] · 进行中 [~] · 结束 [x] · 待开始 [ ] ·
其他 [ ]; new todos default to 「进行中」, open no longer appears.重要 / 不重要 (default 「不重要」).紧急 / 不紧急 (default 「不紧急」); a deadline within 24
hours or already passed is still auto-treated as urgent.done, the file is
auto-marked 归档: true and recorded in the index's archived_files;
reopening a task un-archives the file if it is no longer fully complete.index.json, auto-rebuilt after every change){ "schema_version": 2,
"generated": "...",
"summary": {"todo":n, "in_progress":n, "urgent":n, "done":n, "total":n,
"archived_files":n, "files":n, "today":n, "overdue":n,
"week_created":n, "week_done":n, "monthly_archives":n, "archived_tasks":n},
"week": {"start":"YYYY-MM-DD", "end":"YYYY-MM-DD", "created":n, "done":n},
"urgent": [...], "today": [...], "overdue": [...],
"in_progress": [...], "todo": [...], "done": [...],
"archived_files": ["YYYY-MM-DD", ...],
"monthly_archives": [{"month":"YYYY-MM","files":[...],"file_count":n,"tasks":n}] }
Index entries include depends_on (prerequisite ID list), blocked
(whether any prerequisite is unfinished), parent (parent task ID),
tags (tag list), blocker / counter (contingency plan), and derived
metrics overdue_days / due_in_days / stall_days / projected_finish /
pin / pin_reason (all null when there is no data; render as
「—」/「暂无推算」).
Sort rule: when presenting progress to the user (list / index),
unless the user says otherwise, order by
"pinned (overdue > stalled > due today) → urgent → importance (重要>不重要) →
has deadline → creation time" (implemented in the script).
When reporting, prefer a one-line overview from index's summary and these
buckets: look at today (what to handle today) and overdue first, then
in_progress; whenever mentioning overdue, also give the two exits
(advance / reschedule).
When editing web/index.html, always follow:
P icon table in the file + the
ic(name, cls) helper), no emoji.document.querySelector() for the first one in the document. Bridge the
gap between overlay and trigger with a transparent bridge band so :hover
doesn't break and the overlay doesn't vanish when the mouse passes over.python scripts/selftest.py (CLI
scenarios, temp dirs, never touches real data) and
node scripts/smoke-web.js (front-end render smoke, minimal DOM stubs).I18N, helper tr()), language preference uses
localStorage["awam-todo:lang"], default en; never pull an external
i18n library or CDN (offline constraint). Display-only: <select> values
and stored data stay Chinese.from source links can only be obtained by the agent from session context
(e.g. the current AI conversation link); when unavailable, leave it empty —
the script never invents one.When the user asks to "view todos on a web page / open the board / web view / operate todos in the browser", start the local web front-end:
start-awam-todo.cmd # single entry: stop stale server, start UI backend + capture service together
powershell -NoProfile -ExecutionPolicy Bypass -File "scripts\start-awam-todo.ps1" [-NoBrowser]
python "...\awam-todo\web\server.py" # direct; the capture service starts inside the same process
python "...\awam-todo\web\server.py" --port 9000 # specify a port
start-awam-todo.cmd (or the .ps1 it
calls): it first stops any stale awam-todo server (a leftover process
that holds the port — including ones launched with a relative
web\server.py path), waits for the port to free, then starts the web UI
backend and the capture service together in one process, and finally
self-checks /api/settings printing the capture-service state
(Capture service state: running). If the UI previously showed
「服务未运行 / 加载失败」in the Settings page, that usually means an
old pre-upgrade server still held the port — re-run this entry to fix it.http://127.0.0.1:8796/.add / add --update-id / done / start
/ work / reopen / postpone / dep change / deparent), the script
checks whether the local web service is running — if not running, it
pops a dialog asking 「是否现在启动网页 UI」: on "start", it launches
web/server.py in the background and opens the browser; on "skip", it
does nothing. If the service is already running, no interruption.
env --set ui.prompt=off (temporary override:
environment variable AWAM_TODO_UI_PROMPT=off).server.py start command.全部 / 今日 / 进行中 / 待开始 / 已结束 / 紧急 / 逾期.
「今日」 = overdue + stalled + due today, each with its pin reason, with a
one-line definition at the top. 「今日」 is a view, not the landing
entry — the default landing is 「全部」.; needed); cards display them directly; search also matches their
content.;-separated); cards show #标签
badges; the toolbar has a tag dropdown filter (with per-tag counts).env.json's editor.label (e.g. 「用 VS Code 打开」); when editor is
unset or the path is invalid, the entire menu item is not rendered
(no dead clicks). Backend endpoint: POST /api/workspace/editor
(/api/workspace/cursor is the legacy alias). The web only changes the
label — no config UI; configuration always goes through CLI env --set.localStorage: awam-todo:baseline / awam-todo:pending) as a patch
queue; on save, one POST /api/todos/apply-patches compares against
the file as of that moment and applies patches before persisting,
avoiding concurrent-write conflicts with the CLI. Change comparison
before saving: the content to write is compared character-by-character
with the current disk content; when identical (e.g. submitting the same
field value, same status, deleting an already-deleted task), no file is
written and no index rebuild happens (response changed=0), avoiding
needless rewrites. Save triggers: ① clicking 「保存更改」 on the page
② auto-save (interval configurable on the page, default 10 min, stored in
awam-todo:autosave-min) ③ on close/refresh, auto sendBeacon save.
Unsaved changes show a badge count; if there were unsaved changes at last
close, reopening prompts to restore.todo.py's parse / validate / index logic; when the CLI
changed files, the web merges into the latest file on save
(external_changed=true prompts 「检测到外部修改,已合并保存」).REST API (local machine only):
GET /api/todos list + summary (?state=, ?q=, ?tag=
filters; includes revision file fingerprint)GET /api/todos/<id> single taskPOST /api/todos create (duplicate hit → 409 + duplicates;
force / update_id supported)PUT /api/todos/<id> update fields (incl. status, with
dependency/subtask guards)PATCH /api/todos/<id>/status status only (conflict → 409 + conflicts;
force supported)DELETE /api/todos/<id> delete (also cleans other tasks'
references to it: dependencies / parent)POST /api/todos/apply-patches apply a patch batch and persist once
(web delayed-save entry)
body: {"revision": <fingerprint from GET /api/todos>, "patches": [{"op":"create|update|status|delete", ...}]};
before saving, the revision is compared with the current file fingerprint;
on mismatch the patches are applied to the latest file (merge semantics),
and the response includes external_changed, failed
(duplicate/conflict/not_found), id_map (temp_id → official ID), and
changed (number of files actually written; 0 = content identical to
disk, nothing written).POST /api/workspace/open open a directory with the system file
managerPOST /api/workspace/editor open a directory with the editor
configured in env.json (/api/workspace/cursor is the legacy alias)GET /api/settings capture-service config + current service statePUT /api/settings update capture settings
({capture:{enabled,hotkey,confirm_delete,toast,triage_types}}); re-registers
the hotkey immediately, response includes the new service statePOST /api/capture/triage run the triage entry on {text} — same JSON
contract as the CLI triage command (read-only)If an old service already occupies the port at startup, use the single entry
(start-awam-todo.cmd) which stops the stale server automatically before
restarting. Web changes and CLI todo.py share the same
source; whichever side operates last, the other side always sees the latest
state.