doingapp 0.6.0__tar.gz → 0.7.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.
- {doingapp-0.6.0 → doingapp-0.7.0}/PKG-INFO +17 -3
- {doingapp-0.6.0 → doingapp-0.7.0}/README.md +16 -2
- {doingapp-0.6.0 → doingapp-0.7.0}/doing_cli/api.py +28 -5
- {doingapp-0.6.0 → doingapp-0.7.0}/doing_cli/main.py +97 -11
- {doingapp-0.6.0 → doingapp-0.7.0}/doingapp.egg-info/PKG-INFO +17 -3
- {doingapp-0.6.0 → doingapp-0.7.0}/pyproject.toml +1 -1
- {doingapp-0.6.0 → doingapp-0.7.0}/LICENSE +0 -0
- {doingapp-0.6.0 → doingapp-0.7.0}/doing_cli/__init__.py +0 -0
- {doingapp-0.6.0 → doingapp-0.7.0}/doing_cli/auth.py +0 -0
- {doingapp-0.6.0 → doingapp-0.7.0}/doingapp.egg-info/SOURCES.txt +0 -0
- {doingapp-0.6.0 → doingapp-0.7.0}/doingapp.egg-info/dependency_links.txt +0 -0
- {doingapp-0.6.0 → doingapp-0.7.0}/doingapp.egg-info/entry_points.txt +0 -0
- {doingapp-0.6.0 → doingapp-0.7.0}/doingapp.egg-info/requires.txt +0 -0
- {doingapp-0.6.0 → doingapp-0.7.0}/doingapp.egg-info/top_level.txt +0 -0
- {doingapp-0.6.0 → doingapp-0.7.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: doingapp
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: A fast CLI task tracker — track what you're doing from the terminal.
|
|
5
5
|
Author-email: Parker Grimes <parker@altellus.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -73,13 +73,14 @@ doing apikey --key doing_xxxxx
|
|
|
73
73
|
| Command | Description |
|
|
74
74
|
|---------|-------------|
|
|
75
75
|
| `doing` | Show active tasks (same as `doing ls`) |
|
|
76
|
-
| `doing add <title>` | Add a task (`-n` notes, `-c` context ID) |
|
|
76
|
+
| `doing add <title>` | Add a task (`-n` notes, `-c` context ID, `-d "tomorrow 3pm"` due, `-r daily` repeat) |
|
|
77
77
|
| `doing get <id>` | Get a single task by ID |
|
|
78
78
|
| `doing done <id>` | Mark a task as done |
|
|
79
79
|
| `doing pause <id>` | Pause a task |
|
|
80
80
|
| `doing resume <id>` | Resume a paused task |
|
|
81
81
|
| `doing reopen <id>` | Reopen a completed task |
|
|
82
|
-
| `doing edit <id>` | Edit a task (`-t` title, `-n` notes, `-c` context, `--no-context`) |
|
|
82
|
+
| `doing edit <id>` | Edit a task (`-t` title, `-n` notes, `-c` context, `--no-context`, `-d` due, `--no-due`, `-r` repeat, `--no-repeat`) |
|
|
83
|
+
| `doing snooze <id> [10m\|1h\|3h\|1d\|morning]` | Push a due task back (default 10m) |
|
|
83
84
|
| `doing rm <id>` | Delete a task |
|
|
84
85
|
| `doing ls` | List active tasks (`-c` context ID) |
|
|
85
86
|
| `doing log` | Show tasks done today (`-d YYYY-MM-DD`, `-c` context ID) |
|
|
@@ -93,6 +94,19 @@ doing apikey --key doing_xxxxx
|
|
|
93
94
|
| `doing logout` | Log out |
|
|
94
95
|
| `doing apikey` | Save an API key (`-k` key) |
|
|
95
96
|
|
|
97
|
+
## Due dates and reminders
|
|
98
|
+
|
|
99
|
+
Give a task a due date and the Doing iOS app nags you every few minutes once it's overdue, until you mark it done or snooze it.
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
doing add "Call mom" -d "tomorrow 3pm"
|
|
103
|
+
doing add "Water plants" -d friday -r 3d # repeats 3 days after each completion
|
|
104
|
+
doing add "Standup notes" -d "9am" -r weekdays
|
|
105
|
+
doing snooze 12 1h
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
`-d` accepts natural language: `in 20m`, `tonight`, `fri 9am`, `oct 30`. Dates without a time use your morning time (9:00 by default). `-r` accepts `daily`, `weekdays`, days like `mon,thu`, or `Nd` (N days after done).
|
|
109
|
+
|
|
96
110
|
## JSON output
|
|
97
111
|
|
|
98
112
|
Every command supports `--json` (or `-j`) for machine-readable output. This is useful for scripting and AI coding agents like Claude Code or Codex CLI.
|
|
@@ -48,13 +48,14 @@ doing apikey --key doing_xxxxx
|
|
|
48
48
|
| Command | Description |
|
|
49
49
|
|---------|-------------|
|
|
50
50
|
| `doing` | Show active tasks (same as `doing ls`) |
|
|
51
|
-
| `doing add <title>` | Add a task (`-n` notes, `-c` context ID) |
|
|
51
|
+
| `doing add <title>` | Add a task (`-n` notes, `-c` context ID, `-d "tomorrow 3pm"` due, `-r daily` repeat) |
|
|
52
52
|
| `doing get <id>` | Get a single task by ID |
|
|
53
53
|
| `doing done <id>` | Mark a task as done |
|
|
54
54
|
| `doing pause <id>` | Pause a task |
|
|
55
55
|
| `doing resume <id>` | Resume a paused task |
|
|
56
56
|
| `doing reopen <id>` | Reopen a completed task |
|
|
57
|
-
| `doing edit <id>` | Edit a task (`-t` title, `-n` notes, `-c` context, `--no-context`) |
|
|
57
|
+
| `doing edit <id>` | Edit a task (`-t` title, `-n` notes, `-c` context, `--no-context`, `-d` due, `--no-due`, `-r` repeat, `--no-repeat`) |
|
|
58
|
+
| `doing snooze <id> [10m\|1h\|3h\|1d\|morning]` | Push a due task back (default 10m) |
|
|
58
59
|
| `doing rm <id>` | Delete a task |
|
|
59
60
|
| `doing ls` | List active tasks (`-c` context ID) |
|
|
60
61
|
| `doing log` | Show tasks done today (`-d YYYY-MM-DD`, `-c` context ID) |
|
|
@@ -68,6 +69,19 @@ doing apikey --key doing_xxxxx
|
|
|
68
69
|
| `doing logout` | Log out |
|
|
69
70
|
| `doing apikey` | Save an API key (`-k` key) |
|
|
70
71
|
|
|
72
|
+
## Due dates and reminders
|
|
73
|
+
|
|
74
|
+
Give a task a due date and the Doing iOS app nags you every few minutes once it's overdue, until you mark it done or snooze it.
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
doing add "Call mom" -d "tomorrow 3pm"
|
|
78
|
+
doing add "Water plants" -d friday -r 3d # repeats 3 days after each completion
|
|
79
|
+
doing add "Standup notes" -d "9am" -r weekdays
|
|
80
|
+
doing snooze 12 1h
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
`-d` accepts natural language: `in 20m`, `tonight`, `fri 9am`, `oct 30`. Dates without a time use your morning time (9:00 by default). `-r` accepts `daily`, `weekdays`, days like `mon,thu`, or `Nd` (N days after done).
|
|
84
|
+
|
|
71
85
|
## JSON output
|
|
72
86
|
|
|
73
87
|
Every command supports `--json` (or `-j`) for machine-readable output. This is useful for scripting and AI coding agents like Claude Code or Codex CLI.
|
|
@@ -49,14 +49,19 @@ def get_me() -> dict:
|
|
|
49
49
|
return r.json()
|
|
50
50
|
|
|
51
51
|
|
|
52
|
-
def create_task(title: str, notes: str | None = None, context_id: int | None = None
|
|
52
|
+
def create_task(title: str, notes: str | None = None, context_id: int | None = None,
|
|
53
|
+
due: str | None = None, recurrence: str | None = None) -> dict:
|
|
53
54
|
payload: dict = {"title": title}
|
|
54
55
|
if notes is not None:
|
|
55
56
|
payload["notes"] = notes
|
|
56
57
|
if context_id is not None:
|
|
57
58
|
payload["context_id"] = context_id
|
|
59
|
+
if due is not None:
|
|
60
|
+
payload["due"] = due
|
|
61
|
+
if recurrence is not None:
|
|
62
|
+
payload["recurrence"] = recurrence
|
|
58
63
|
with _client() as c:
|
|
59
|
-
r = c.post("/tasks", json=payload)
|
|
64
|
+
r = c.post("/tasks", json=payload, params={"tz": _local_tz()})
|
|
60
65
|
r.raise_for_status()
|
|
61
66
|
return r.json()
|
|
62
67
|
|
|
@@ -85,7 +90,7 @@ def list_tasks(status: str | None = None, date: str | None = None,
|
|
|
85
90
|
|
|
86
91
|
def mark_done(task_id: int) -> dict:
|
|
87
92
|
with _client() as c:
|
|
88
|
-
r = c.post(f"/tasks/{task_id}/done")
|
|
93
|
+
r = c.post(f"/tasks/{task_id}/done", params={"tz": _local_tz()})
|
|
89
94
|
r.raise_for_status()
|
|
90
95
|
return r.json()
|
|
91
96
|
|
|
@@ -112,7 +117,9 @@ def resume_task(task_id: int) -> dict:
|
|
|
112
117
|
|
|
113
118
|
|
|
114
119
|
def update_task(task_id: int, title: str | None = None, notes: str | None = None,
|
|
115
|
-
context_id: int | None = None, clear_context: bool = False
|
|
120
|
+
context_id: int | None = None, clear_context: bool = False,
|
|
121
|
+
due: str | None = None, recurrence: str | None = None,
|
|
122
|
+
clear_due: bool = False, clear_recurrence: bool = False) -> dict:
|
|
116
123
|
payload: dict = {}
|
|
117
124
|
if title is not None:
|
|
118
125
|
payload["title"] = title
|
|
@@ -122,8 +129,24 @@ def update_task(task_id: int, title: str | None = None, notes: str | None = None
|
|
|
122
129
|
payload["clear_context"] = True
|
|
123
130
|
elif context_id is not None:
|
|
124
131
|
payload["context_id"] = context_id
|
|
132
|
+
if clear_due:
|
|
133
|
+
payload["clear_due"] = True
|
|
134
|
+
elif due is not None:
|
|
135
|
+
payload["due"] = due
|
|
136
|
+
if clear_recurrence:
|
|
137
|
+
payload["clear_recurrence"] = True
|
|
138
|
+
elif recurrence is not None:
|
|
139
|
+
payload["recurrence"] = recurrence
|
|
125
140
|
with _client() as c:
|
|
126
|
-
r = c.patch(f"/tasks/{task_id}", json=payload)
|
|
141
|
+
r = c.patch(f"/tasks/{task_id}", json=payload, params={"tz": _local_tz()})
|
|
142
|
+
r.raise_for_status()
|
|
143
|
+
return r.json()
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def snooze_task(task_id: int, minutes: int | None = None, preset: str | None = None) -> dict:
|
|
147
|
+
payload: dict = {"minutes": minutes} if minutes is not None else {"preset": preset}
|
|
148
|
+
with _client() as c:
|
|
149
|
+
r = c.post(f"/tasks/{task_id}/snooze", json=payload, params={"tz": _local_tz()})
|
|
127
150
|
r.raise_for_status()
|
|
128
151
|
return r.json()
|
|
129
152
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import json
|
|
2
|
+
import re
|
|
2
3
|
import sys
|
|
4
|
+
from datetime import datetime, timezone
|
|
3
5
|
|
|
4
6
|
import httpx
|
|
5
7
|
import typer
|
|
@@ -99,6 +101,51 @@ def _validate_name(name: str) -> str:
|
|
|
99
101
|
return cleaned
|
|
100
102
|
|
|
101
103
|
|
|
104
|
+
WEEKDAYS = ("mon", "tue", "wed", "thu", "fri", "sat", "sun")
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _normalize_repeat(value: str) -> str:
|
|
108
|
+
"""Accept friendly repeat specs: daily, weekdays, mon,thu, 3d, or the raw API form."""
|
|
109
|
+
v = value.strip().lower().replace(" ", "")
|
|
110
|
+
if v in ("daily", "weekdays") or v.startswith(("weekly:", "after:")):
|
|
111
|
+
return v
|
|
112
|
+
if all(day in WEEKDAYS for day in v.split(",")):
|
|
113
|
+
return f"weekly:{v}"
|
|
114
|
+
if m := re.fullmatch(r"(\d+)d(ays?)?", v):
|
|
115
|
+
return f"after:{m.group(1)}"
|
|
116
|
+
_validation_error(f"Invalid --repeat '{value}'. Use daily, weekdays, mon,thu, or 3d (days after done).")
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _describe_repeat(rule: str) -> str:
|
|
120
|
+
if rule.startswith("weekly:"):
|
|
121
|
+
return "every " + ", ".join(d.title() for d in rule.split(":", 1)[1].split(","))
|
|
122
|
+
if rule.startswith("after:"):
|
|
123
|
+
n = rule.split(":", 1)[1]
|
|
124
|
+
return f"{n} day{'s' if n != '1' else ''} after done"
|
|
125
|
+
return rule
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _format_due(task: dict) -> str:
|
|
129
|
+
"""Rich-formatted due label, e.g. '[red]overdue · Mon 3:00 PM[/red]'."""
|
|
130
|
+
if not task.get("due_at"):
|
|
131
|
+
return ""
|
|
132
|
+
due = datetime.fromisoformat(task["due_at"])
|
|
133
|
+
if due.tzinfo is None:
|
|
134
|
+
due = due.replace(tzinfo=timezone.utc)
|
|
135
|
+
due = due.astimezone()
|
|
136
|
+
now = datetime.now().astimezone()
|
|
137
|
+
if task.get("due_all_day"):
|
|
138
|
+
when = due.strftime("%a %b %-d")
|
|
139
|
+
elif due.date() == now.date():
|
|
140
|
+
when = due.strftime("%-I:%M %p")
|
|
141
|
+
else:
|
|
142
|
+
when = due.strftime("%a %b %-d %-I:%M %p")
|
|
143
|
+
repeat = f" ↻ {_describe_repeat(task['recurrence'])}" if task.get("recurrence") else ""
|
|
144
|
+
if task.get("status") == "active" and due <= now:
|
|
145
|
+
return f" [bold red]⏰ overdue · {when}{repeat}[/bold red]"
|
|
146
|
+
return f" [cyan]⏰ {when}{repeat}[/cyan]"
|
|
147
|
+
|
|
148
|
+
|
|
102
149
|
def _print_notes(task: dict) -> None:
|
|
103
150
|
if task.get("notes"):
|
|
104
151
|
console.print(f" [dim]{task['notes']}[/dim]")
|
|
@@ -106,13 +153,13 @@ def _print_notes(task: dict) -> None:
|
|
|
106
153
|
|
|
107
154
|
def _print_active_task(task: dict) -> None:
|
|
108
155
|
ctx = f" [dim][{task['context_name']}][/dim]" if task.get("context_name") else ""
|
|
109
|
-
console.print(f" [green bold]●[/green bold] [dim]#{task['id']}[/dim] {task['title']}{ctx}")
|
|
156
|
+
console.print(f" [green bold]●[/green bold] [dim]#{task['id']}[/dim] {task['title']}{ctx}{_format_due(task)}")
|
|
110
157
|
_print_notes(task)
|
|
111
158
|
|
|
112
159
|
|
|
113
160
|
def _print_paused_task(task: dict) -> None:
|
|
114
161
|
ctx = f" [dim][{task['context_name']}][/dim]" if task.get("context_name") else ""
|
|
115
|
-
console.print(f" [yellow]⏸[/yellow] [dim]#{task['id']}[/dim] {task['title']}{ctx}")
|
|
162
|
+
console.print(f" [yellow]⏸[/yellow] [dim]#{task['id']}[/dim] {task['title']}{ctx}{_format_due(task)}")
|
|
116
163
|
_print_notes(task)
|
|
117
164
|
|
|
118
165
|
|
|
@@ -220,16 +267,19 @@ def add(
|
|
|
220
267
|
title: str = typer.Argument(..., help="What are you working on?"),
|
|
221
268
|
notes: str = typer.Option(None, "--notes", "-n", help="Optional notes"),
|
|
222
269
|
context: int = typer.Option(None, "--context", "-c", help="Context ID"),
|
|
270
|
+
due: str = typer.Option(None, "--due", "-d", help="When it's due, e.g. 'tomorrow 3pm', 'in 20m', 'friday'"),
|
|
271
|
+
repeat: str = typer.Option(None, "--repeat", "-r", help="daily, weekdays, mon,thu, or 3d (3 days after done)"),
|
|
223
272
|
):
|
|
224
273
|
"""Add something to your plate."""
|
|
225
274
|
title = _validate_title(title)
|
|
275
|
+
recurrence = _normalize_repeat(repeat) if repeat else None
|
|
226
276
|
try:
|
|
227
|
-
task = api.create_task(title, notes, context)
|
|
277
|
+
task = api.create_task(title, notes, context, due=due, recurrence=recurrence)
|
|
228
278
|
except Exception as exc:
|
|
229
279
|
_handle_api_error(exc)
|
|
230
280
|
if _json_mode:
|
|
231
281
|
_output_json(task)
|
|
232
|
-
console.print(f"[green bold]●[/green bold] Added: [bold]{task['title']}[/bold] [dim](#{task['id']})[/dim]")
|
|
282
|
+
console.print(f"[green bold]●[/green bold] Added: [bold]{task['title']}[/bold] [dim](#{task['id']})[/dim]{_format_due(task)}")
|
|
233
283
|
|
|
234
284
|
|
|
235
285
|
@app.command()
|
|
@@ -246,7 +296,7 @@ def get(
|
|
|
246
296
|
status_icon = {"active": "[green bold]●[/green bold]", "paused": "[yellow]⏸[/yellow]", "done": "[green]✓[/green]"}
|
|
247
297
|
icon = status_icon.get(task["status"], "?")
|
|
248
298
|
ctx = f" [dim][{task['context_name']}][/dim]" if task.get("context_name") else ""
|
|
249
|
-
console.print(f" {icon} [dim]#{task['id']}[/dim] {task['title']}{ctx}")
|
|
299
|
+
console.print(f" {icon} [dim]#{task['id']}[/dim] {task['title']}{ctx}{_format_due(task)}")
|
|
250
300
|
if task.get("notes"):
|
|
251
301
|
console.print(f" [dim]{task['notes']}[/dim]")
|
|
252
302
|
console.print(f" [dim]Status: {task['status']} Created: {task['created_at']}[/dim]")
|
|
@@ -311,7 +361,8 @@ def done(
|
|
|
311
361
|
continue
|
|
312
362
|
results.append(task)
|
|
313
363
|
if not _json_mode:
|
|
314
|
-
|
|
364
|
+
next_due = f" [dim]next:[/dim]{_format_due(task)}" if task.get("recurrence") else ""
|
|
365
|
+
console.print(f"[green]✓[/green] Done: [bold]{task['title']}[/bold]{next_due}")
|
|
315
366
|
if _json_mode:
|
|
316
367
|
_output_json(results[0] if len(task_ids) == 1 and results else results)
|
|
317
368
|
if errors:
|
|
@@ -367,25 +418,60 @@ def edit(
|
|
|
367
418
|
notes: str = typer.Option(None, "--notes", "-n", help="New notes"),
|
|
368
419
|
context: int = typer.Option(None, "--context", "-c", help="Move to context ID"),
|
|
369
420
|
no_context: bool = typer.Option(False, "--no-context", help="Remove context"),
|
|
421
|
+
due: str = typer.Option(None, "--due", "-d", help="When it's due, e.g. 'tomorrow 3pm'"),
|
|
422
|
+
no_due: bool = typer.Option(False, "--no-due", help="Remove due date (and repeat)"),
|
|
423
|
+
repeat: str = typer.Option(None, "--repeat", "-r", help="daily, weekdays, mon,thu, or 3d"),
|
|
424
|
+
no_repeat: bool = typer.Option(False, "--no-repeat", help="Stop repeating"),
|
|
370
425
|
):
|
|
371
|
-
"""Edit a task's title, notes, or
|
|
426
|
+
"""Edit a task's title, notes, context, due date, or repeat."""
|
|
372
427
|
if no_context and context is not None:
|
|
373
428
|
_validation_error("--no-context and --context cannot be used together")
|
|
429
|
+
if no_due and due is not None:
|
|
430
|
+
_validation_error("--no-due and --due cannot be used together")
|
|
431
|
+
if no_repeat and repeat is not None:
|
|
432
|
+
_validation_error("--no-repeat and --repeat cannot be used together")
|
|
374
433
|
|
|
375
|
-
if title is None and notes is None and context is None and not no_context
|
|
376
|
-
|
|
434
|
+
if (title is None and notes is None and context is None and not no_context
|
|
435
|
+
and due is None and not no_due and repeat is None and not no_repeat):
|
|
436
|
+
_validation_error("No changes specified. Use --title, --notes, --context, --due, or --repeat.")
|
|
377
437
|
|
|
378
438
|
if title is not None:
|
|
379
439
|
title = _validate_title(title)
|
|
440
|
+
recurrence = _normalize_repeat(repeat) if repeat else None
|
|
380
441
|
|
|
381
442
|
try:
|
|
382
443
|
task = api.update_task(task_id, title=title, notes=notes,
|
|
383
|
-
context_id=context, clear_context=no_context
|
|
444
|
+
context_id=context, clear_context=no_context,
|
|
445
|
+
due=due, clear_due=no_due,
|
|
446
|
+
recurrence=recurrence, clear_recurrence=no_repeat)
|
|
447
|
+
except Exception as exc:
|
|
448
|
+
_handle_api_error(exc)
|
|
449
|
+
if _json_mode:
|
|
450
|
+
_output_json(task)
|
|
451
|
+
console.print(f"[green bold]Updated:[/green bold] {task['title']} [dim](#{task['id']})[/dim]{_format_due(task)}")
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
@app.command()
|
|
455
|
+
def snooze(
|
|
456
|
+
task_id: int = typer.Argument(..., help="ID of the task to snooze"),
|
|
457
|
+
duration: str = typer.Argument("10m", help="10m, 1h, 3h, 1d, or 'morning' (tomorrow morning)"),
|
|
458
|
+
):
|
|
459
|
+
"""Push a task's due time back (stops the nagging until then)."""
|
|
460
|
+
d = duration.strip().lower()
|
|
461
|
+
minutes = preset = None
|
|
462
|
+
if d in ("morning", "tomorrow"):
|
|
463
|
+
preset = "tomorrow_morning"
|
|
464
|
+
elif m := re.fullmatch(r"(\d+)\s*(m|min|h|hr|d)", d):
|
|
465
|
+
minutes = int(m.group(1)) * {"m": 1, "min": 1, "h": 60, "hr": 60, "d": 1440}[m.group(2)]
|
|
466
|
+
else:
|
|
467
|
+
_validation_error(f"Invalid duration '{duration}'. Use 10m, 1h, 3h, 1d, or morning.")
|
|
468
|
+
try:
|
|
469
|
+
task = api.snooze_task(task_id, minutes=minutes, preset=preset)
|
|
384
470
|
except Exception as exc:
|
|
385
471
|
_handle_api_error(exc)
|
|
386
472
|
if _json_mode:
|
|
387
473
|
_output_json(task)
|
|
388
|
-
console.print(f"[
|
|
474
|
+
console.print(f"[cyan]💤 Snoozed:[/cyan] [bold]{task['title']}[/bold]{_format_due(task)}")
|
|
389
475
|
|
|
390
476
|
|
|
391
477
|
@app.command()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: doingapp
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: A fast CLI task tracker — track what you're doing from the terminal.
|
|
5
5
|
Author-email: Parker Grimes <parker@altellus.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -73,13 +73,14 @@ doing apikey --key doing_xxxxx
|
|
|
73
73
|
| Command | Description |
|
|
74
74
|
|---------|-------------|
|
|
75
75
|
| `doing` | Show active tasks (same as `doing ls`) |
|
|
76
|
-
| `doing add <title>` | Add a task (`-n` notes, `-c` context ID) |
|
|
76
|
+
| `doing add <title>` | Add a task (`-n` notes, `-c` context ID, `-d "tomorrow 3pm"` due, `-r daily` repeat) |
|
|
77
77
|
| `doing get <id>` | Get a single task by ID |
|
|
78
78
|
| `doing done <id>` | Mark a task as done |
|
|
79
79
|
| `doing pause <id>` | Pause a task |
|
|
80
80
|
| `doing resume <id>` | Resume a paused task |
|
|
81
81
|
| `doing reopen <id>` | Reopen a completed task |
|
|
82
|
-
| `doing edit <id>` | Edit a task (`-t` title, `-n` notes, `-c` context, `--no-context`) |
|
|
82
|
+
| `doing edit <id>` | Edit a task (`-t` title, `-n` notes, `-c` context, `--no-context`, `-d` due, `--no-due`, `-r` repeat, `--no-repeat`) |
|
|
83
|
+
| `doing snooze <id> [10m\|1h\|3h\|1d\|morning]` | Push a due task back (default 10m) |
|
|
83
84
|
| `doing rm <id>` | Delete a task |
|
|
84
85
|
| `doing ls` | List active tasks (`-c` context ID) |
|
|
85
86
|
| `doing log` | Show tasks done today (`-d YYYY-MM-DD`, `-c` context ID) |
|
|
@@ -93,6 +94,19 @@ doing apikey --key doing_xxxxx
|
|
|
93
94
|
| `doing logout` | Log out |
|
|
94
95
|
| `doing apikey` | Save an API key (`-k` key) |
|
|
95
96
|
|
|
97
|
+
## Due dates and reminders
|
|
98
|
+
|
|
99
|
+
Give a task a due date and the Doing iOS app nags you every few minutes once it's overdue, until you mark it done or snooze it.
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
doing add "Call mom" -d "tomorrow 3pm"
|
|
103
|
+
doing add "Water plants" -d friday -r 3d # repeats 3 days after each completion
|
|
104
|
+
doing add "Standup notes" -d "9am" -r weekdays
|
|
105
|
+
doing snooze 12 1h
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
`-d` accepts natural language: `in 20m`, `tonight`, `fri 9am`, `oct 30`. Dates without a time use your morning time (9:00 by default). `-r` accepts `daily`, `weekdays`, days like `mon,thu`, or `Nd` (N days after done).
|
|
109
|
+
|
|
96
110
|
## JSON output
|
|
97
111
|
|
|
98
112
|
Every command supports `--json` (or `-j`) for machine-readable output. This is useful for scripting and AI coding agents like Claude Code or Codex CLI.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|