Simple spreadsheet-backed task manager CLI, built on gspread. One project = one Google Spreadsheet. Designed to be friendly for both humans and LLM callers.
# one-off run
uvx --from git+https://github.com/tmlksu/mtask mtask --help
# pin a branch / tag / commit
uvx --from git+https://github.com/tmlksu/mtask@main mtask list
uvx --from git+https://github.com/tmlksu/mtask@v0.1.0 mtask list
# install as a persistent tool, then just `mtask ...`
uv tool install git+https://github.com/tmlksu/mtask
mtask --help
uv tool upgrade mtask # update laterNotes:
--fromis the package source; the trailingmtaskis the command to run. Plainuvx mtaskwould look upmtaskon PyPI instead — always pass--from git+....- Private repo? Use SSH:
git+ssh://git@github.com/tmlksu/mtask. - Following a branch?
uvx --refresh ...(oruv tool upgrade mtask) re-fetches.
uvx --from . mtask --help # one-off
uv tool install . # install
mtask --helpmtask supports two methods. OAuth user login is the default.
- In Google Cloud, enable the Google Sheets + Drive APIs and create an OAuth client ID of type "Desktop app". Download its JSON.
- Save it to
~/.config/mtask/oauth_client.json. mtask auth login— opens a browser and starts a temporary local HTTP server to catch the redirect athttp://localhost:<port>/. The token is cached at~/.config/mtask/authorized_user.json. (mtask auth logoutremoves it.)
Because you log in as yourself, any spreadsheet you can already edit just works — no extra sharing needed.
The redirect endpoint is http://localhost:<port><path>. Defaults: port 0
(an OS-chosen ephemeral port) and path /. A Desktop-app OAuth client
accepts any localhost loopback port/path, so nothing needs registering.
Pin them — e.g. behind a firewall, or to match a redirect URI registered on a "Web application" client:
mtask auth port 8765 # persist a fixed port (0 = ephemeral)
mtask auth path /oauth2callback # persist a fixed redirect path ('/' default)
mtask auth port # show current port
mtask auth path # show current path
# override just for one login (not persisted):
mtask auth login --port 8765 --path /oauth2callback- Create a service account, download its JSON key, enable Sheets + Drive APIs.
- Point mtask at the key, either:
export GOOGLE_APPLICATION_CREDENTIALS=/path/to/key.json(always wins), or- place it at
~/.config/mtask/service_account.jsonand runmtask auth method service_account.
- Share the spreadsheet with the service account's email (as Editor).
Check or switch the method anytime: mtask auth method / mtask auth method oauth.
mtask sheet add myproj <spreadsheet-id> # register a project (slug -> sheet ID)
mtask sheet list # list projects ( * = current )
mtask sheet use myproj # switch current project
mtask sheet repair # reconcile columns to the schema (dry-run)
mtask sheet view # (re)build a collapsible WBS view tab
mtask config worksheet Tasks # which tab holds the task list (default: Tasks)
mtask user set alice # default reporter (起票者), git-style
mtask user # show current user
mtask add "fix login bug" --status 着手中 --assignee bob
mtask list # open tasks (完了 hidden)
mtask list --tree # WBS hierarchy by 親ID (numbers 1.2.3)
mtask list --show-completed --limit 100 --page 1
mtask list --status 保留 --json
mtask get T-0001
mtask update T-0001 --status 完了 --note "merged"
# WBS / scheduling
mtask add "design API" --plan-start 2026-07-01 --due 2026-07-10
mtask add "impl endpoint" --parent T-0001 --deps T-0001 --plan-start 2026-07-11
mtask update T-0002 --start 2026-07-11 --status 着手中
mtask schedule check # report dependency/date problemsPass a JSON array via --from (- reads stdin). One Sheets request is used per
call (append_rows / batch cell update), so it's also gentler on API limits.
Object keys are the same field names as the flags (title, status, due,
assignee, reporter, note; plus id for updates) — Japanese headers like
タイトル also work.
# bulk add — IDs are auto-assigned
mtask add --from tasks.json
echo '[{"title":"task A"},{"title":"task B","status":"着手中"}]' | mtask add --from -
# bulk update — each object needs an "id"
mtask update --from updates.json
echo '[{"id":"T-0001","status":"完了"},{"id":"T-0002","assignee":"alice"}]' \
| mtask update --from -mtask list --tree renders the 親ID hierarchy with derived WBS numbers
(1, 1.1, 1.2.1, …), indented by depth. Siblings are ordered by ID.
1 T-0001 [着手中] 設計
1.1 T-0002 [未着手] API定義
1.2 T-0003 [完了] スキーマ
2 T-0004 [未着手] 実装
- Numbers are computed from the full tree, so a task's number is stable
regardless of filtering (a hidden sibling just leaves a gap, e.g.
1.1then1.3). - Ancestors of a matching task are always shown (dimmed) for context — so filtering (e.g. the default hide-完了) never breaks the hierarchy.
--treeshows the whole tree and ignores--limit/--page.- Anomalies are flagged inline:
(親?)=親IDpoints to a missing/own ID (shown as a root),(循環)= part of a parent cycle (the loop is broken). --json --treeaddswbs,depth,context, andflagsto each row.
mtask schedule check reports scheduling and relationship problems — it never
modifies anything.
mtask schedule check # human-readable report
mtask schedule check --json # structured findings- Errors (structural):
親ID/先行タスクcycles, dangling or self references, and inverted date ranges (開始 > 完了, planned or actual). - Warnings (soft): a task is 着手中/完了 while a predecessor isn't 完了; a task's 開始予定日 falls before a predecessor's 完了予定日.
- Exits non-zero (1) if any errors are found — handy in CI. Warnings alone exit 0.
For people browsing the spreadsheet itself, mtask sheet view generates a
formatted, collapsible WBS into a separate tab (default name WBS):
mtask sheet view # build/refresh the 'WBS' tab
mtask sheet view --name 計画 # use a different tab name- WBS numbers + indentation, native row groups (the +/- outline) so the hierarchy collapses, rows color-coded by 状態, parent rows bold, header frozen.
- Built-in gantt: to the right of the WBS columns, a date grid whose cells
are colored to form a bar across each task's span (planned
開始予定日/完了予定日, falling back to actual), with a 今日 marker. The grid buckets adapt to the range (daily / weekly / monthly). - The tab is rebuilt from scratch each run (no stale formatting/groups) and is protected (warning-only). The data sheet is never modified.
- It's a snapshot — re-run after changes. (No Apps Script needed; it's all done through the Sheets API with your existing login.)
Update every task matching a condition. It's a dry-run by default — it prints
what would change and applies nothing until you add --yes.
mtask update --where assignee=bob --set status=完了 # preview only
mtask update --where assignee=bob --set status=完了 --yes # apply
mtask update --where 状態=着手中 --set assignee=alice --yes # repeatable --where (AND)ID ・ 親ID ・ 起票日 ・ 状態 ・ タイトル ・ 概要 ・ 起票者 ・ 作業者 ・
先行タスク ・ 状況 ・ 開始予定日 ・ 完了予定日 ・ 開始日 ・ 完了日 ・ 更新日
IDis auto-assigned asT-0001,T-0002, …起票日/更新日are managed automatically.状態is restricted to: 未着手 / 着手中 / 完了 / 保留 / キャンセル.状況is truncated to 50,000 chars by default — the Google Sheets per-cell limit — with a warning; override with--note-max.listhides 完了 and キャンセル by default; use--show-completed(or--statusto target a specific state).
These let you grow a flat task list into a schedule. The sheet stores the raw values; computed views (tree display, dependency/schedule checks) are planned as a follow-up.
| Field | Flag | Notes |
|---|---|---|
親ID |
--parent |
Parent task ID for a subtask (WBS hierarchy). Empty = top-level. |
先行タスク |
--deps |
Predecessor task IDs, comma-separated (e.g. T-0003,T-0005). |
概要 |
--summary |
Short description (distinct from 状況, the running notes). |
開始予定日 |
--plan-start |
Planned start. YYYY-MM-DD or empty. |
完了予定日 |
--due / -d |
Planned finish (deadline). YYYY-MM-DD or empty. |
開始日 |
--start |
Actual start. YYYY-MM-DD or empty. |
完了日 |
--finish |
Actual finish. YYYY-MM-DD or empty. |
All date fields accept '' to clear them on update. 親ID / 先行タスク are
stored as plain text for now — they aren't validated against existing IDs yet.
The header is fixed and checked on every run, so an existing populated sheet
whose columns differ (e.g. from before these fields were added) reports a header
mismatch. mtask sheet repair reconciles it:
mtask sheet repair # dry-run: show what would change
mtask sheet repair --yes # apply (a backup tab is created first)
mtask sheet repair --yes --no-backupIt matches columns by header name, reorders them to the schema order, adds
any missing columns (empty), and keeps unknown columns on the right so no data
is dropped. It reads the sheet once and rewrites it in one pass; cell formatting
and formulas are not preserved (values are). By default it first copies the
sheet to a backup_<name>_<timestamp> tab.
~/.config/mtask/config.toml (override dir with MTASK_CONFIG_DIR):
[user]
name = "alice"
[current]
sheet = "myproj"
[sheets]
myproj = "<spreadsheet-id>"
[defaults]
worksheet = "Tasks" # tab that holds the task listA project is one spreadsheet, but a spreadsheet can have several tabs (the data,
plus the generated WBS view and backup_* tabs). mtask reads the task list
from the tab named Tasks by default.
mtask config worksheet # show the current setting
mtask config worksheet Tasks # set the tab name
mtask config worksheet --clear # back to default
mtask list -w Tasks # per-command override (-w / --worksheet)- Resolution order:
--worksheet/-w→config worksheet→ built-inTasks. - For backward compatibility, if nothing is configured and there's no
Taskstab, mtask falls back to the first tab. If you explicitly set a name (flag or config) and that tab is missing, it errors and lists the available tabs.
- CHANGELOG.md — notable changes per version.
- docs/adr/ — architecture decision records (why it's built this way).
- HANDOFF.md — orientation for working on the code.