td-task 0.2.4__tar.gz → 0.2.6__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.
- td_task-0.2.6/.github/workflows/pages.yml +50 -0
- {td_task-0.2.4 → td_task-0.2.6}/PKG-INFO +43 -3
- {td_task-0.2.4 → td_task-0.2.6}/README.md +41 -1
- td_task-0.2.6/index.html +78 -0
- {td_task-0.2.4 → td_task-0.2.6}/pyproject.toml +1 -1
- {td_task-0.2.4 → td_task-0.2.6}/src/td/CHANGELOG.md +13 -0
- {td_task-0.2.4 → td_task-0.2.6}/src/td/__main__.py +100 -0
- td_task-0.2.6/src/td/backup.py +304 -0
- td_task-0.2.6/src/td/config.py +55 -0
- {td_task-0.2.4 → td_task-0.2.6}/src/td/terminal.py +72 -20
- {td_task-0.2.4 → td_task-0.2.6}/src/td/tui.py +391 -70
- td_task-0.2.6/tests/test_backup.py +129 -0
- {td_task-0.2.4 → td_task-0.2.6}/tests/test_cli_notes.py +7 -0
- td_task-0.2.6/tests/test_resize.py +333 -0
- {td_task-0.2.4 → td_task-0.2.6}/tests/test_tui_notes.py +89 -0
- {td_task-0.2.4 → td_task-0.2.6}/uv.lock +1 -1
- td_task-0.2.6/website/README.md +29 -0
- td_task-0.2.6/website/favicon.svg +4 -0
- td_task-0.2.6/website/main.js +21 -0
- td_task-0.2.6/website/style.css +57 -0
- {td_task-0.2.4 → td_task-0.2.6}/.env.enc +0 -0
- {td_task-0.2.4 → td_task-0.2.6}/.github/workflows/publish.yml +0 -0
- {td_task-0.2.4 → td_task-0.2.6}/.gitignore +0 -0
- {td_task-0.2.4 → td_task-0.2.6}/.python-version +0 -0
- {td_task-0.2.4 → td_task-0.2.6}/assets/demo.gif +0 -0
- {td_task-0.2.4 → td_task-0.2.6}/demo.tape +0 -0
- {td_task-0.2.4 → td_task-0.2.6}/src/td/__init__.py +0 -0
- {td_task-0.2.4 → td_task-0.2.6}/src/td/db.py +0 -0
- {td_task-0.2.4 → td_task-0.2.6}/tests/test_notes.py +0 -0
- {td_task-0.2.4 → td_task-0.2.6}/tests/test_update.py +0 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
name: Publish homepage
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
push:
|
|
6
|
+
branches: [main]
|
|
7
|
+
paths:
|
|
8
|
+
- index.html
|
|
9
|
+
- website/**
|
|
10
|
+
- assets/demo.gif
|
|
11
|
+
- .github/workflows/pages.yml
|
|
12
|
+
|
|
13
|
+
permissions:
|
|
14
|
+
contents: read
|
|
15
|
+
|
|
16
|
+
concurrency:
|
|
17
|
+
group: github-pages
|
|
18
|
+
cancel-in-progress: false
|
|
19
|
+
|
|
20
|
+
jobs:
|
|
21
|
+
deploy:
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
permissions:
|
|
24
|
+
pages: write
|
|
25
|
+
id-token: write
|
|
26
|
+
environment:
|
|
27
|
+
name: github-pages
|
|
28
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
29
|
+
steps:
|
|
30
|
+
- name: Checkout
|
|
31
|
+
uses: actions/checkout@v6
|
|
32
|
+
|
|
33
|
+
- name: Configure Pages
|
|
34
|
+
uses: actions/configure-pages@v5
|
|
35
|
+
|
|
36
|
+
- name: Stage public files
|
|
37
|
+
run: |
|
|
38
|
+
mkdir -p _site/assets
|
|
39
|
+
cp index.html _site/
|
|
40
|
+
cp -R website _site/website
|
|
41
|
+
cp assets/demo.gif _site/assets/
|
|
42
|
+
|
|
43
|
+
- name: Upload site
|
|
44
|
+
uses: actions/upload-pages-artifact@v4
|
|
45
|
+
with:
|
|
46
|
+
path: _site
|
|
47
|
+
|
|
48
|
+
- name: Deploy
|
|
49
|
+
id: deployment
|
|
50
|
+
uses: actions/deploy-pages@v4
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: td-task
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.6
|
|
4
4
|
Summary: Fast TUI and CLI manager for tasks and Markdown notes
|
|
5
5
|
Requires-Python: >=3.14
|
|
6
6
|
Requires-Dist: cryptography>=42.0.0
|
|
@@ -66,6 +66,8 @@ td [command] [--list <name>]
|
|
|
66
66
|
| `td archive` | Open the archive screen |
|
|
67
67
|
| `td export [file]` | Export database to JSON (stdout if no file) |
|
|
68
68
|
| `td import <file>` | Merge a JSON backup into the database |
|
|
69
|
+
| `td backup github [repo]` | Encrypt and upload a backup to a private GitHub repo |
|
|
70
|
+
| `td restore github [repo]` | Download and restore an encrypted GitHub backup |
|
|
69
71
|
| `td update` | Upgrade to the latest version |
|
|
70
72
|
| `td --version` | Print the installed version |
|
|
71
73
|
| `td --help` | Print the help menu |
|
|
@@ -110,6 +112,9 @@ td notes delete 12 --json
|
|
|
110
112
|
|
|
111
113
|
## TUI Keybindings
|
|
112
114
|
|
|
115
|
+
The TUI redraws automatically when you resize the terminal, keeping the current
|
|
116
|
+
selection and unsaved edits. Help supports arrow-key and PageUp/PageDown scrolling.
|
|
117
|
+
|
|
113
118
|
### Tasks
|
|
114
119
|
|
|
115
120
|
| Key | Action |
|
|
@@ -121,6 +126,7 @@ td notes delete 12 --json
|
|
|
121
126
|
| `d` | Delete selected task |
|
|
122
127
|
| `Space` | Toggle done / active |
|
|
123
128
|
| `s` | Star task (pin to top, bold yellow) |
|
|
129
|
+
| `S` | Cycle task sort: A-Z / Z-A / original order |
|
|
124
130
|
| `c` | Archive all completed tasks |
|
|
125
131
|
| `y` | Copy task list to clipboard |
|
|
126
132
|
| `f` | Filter notes by title in current list; submit empty query to clear |
|
|
@@ -203,7 +209,8 @@ marker. Press `Enter` or `Backspace` on an empty continuation to leave list mode
|
|
|
203
209
|
| `q` / `Esc` | Return |
|
|
204
210
|
|
|
205
211
|
Settings you can change: max tasks per list (3–50), max starred tasks, whole-database
|
|
206
|
-
encryption, current-list encryption,
|
|
212
|
+
encryption, current-list encryption, backup export / import, GitHub backup repo,
|
|
213
|
+
GitHub backup now, auto-sync, and GitHub backup encryption.
|
|
207
214
|
|
|
208
215
|
## Agent-friendly
|
|
209
216
|
|
|
@@ -288,9 +295,42 @@ td export > backup.json
|
|
|
288
295
|
|
|
289
296
|
# restore on another machine
|
|
290
297
|
td import backup.json
|
|
298
|
+
|
|
299
|
+
# encrypt and upload to a private GitHub repo (creates td-backup if missing)
|
|
300
|
+
td backup github --save-password
|
|
301
|
+
td backup github lucaspon/my-td-backup --save-password
|
|
302
|
+
|
|
303
|
+
# optional: store plain JSON instead of an encrypted backup
|
|
304
|
+
td backup github --no-encrypt
|
|
305
|
+
|
|
306
|
+
# restore and merge from GitHub
|
|
307
|
+
td restore github
|
|
291
308
|
```
|
|
292
309
|
|
|
293
310
|
You can encrypt the whole database or only the current list from settings (`/`).
|
|
294
311
|
Each encrypted list uses its own password-derived key. Tasks and note bodies stay
|
|
295
312
|
encrypted in JSON backups. Whole-database and per-list encryption cannot run
|
|
296
313
|
together. If you forget a password, there is no recovery.
|
|
314
|
+
|
|
315
|
+
By default, GitHub backups add a separate password-based encryption layer around
|
|
316
|
+
the full JSON export. In encrypted mode, `td` sends only the encrypted envelope
|
|
317
|
+
to GitHub. The `gh` CLI must be
|
|
318
|
+
installed and authenticated. `td backup github` creates the target repository as
|
|
319
|
+
private when it does not exist, and refuses to write to a public repository.
|
|
320
|
+
|
|
321
|
+
Auto-sync uses `~/.config/td/config.toml`:
|
|
322
|
+
|
|
323
|
+
```toml
|
|
324
|
+
[github_backup]
|
|
325
|
+
auto_sync = true
|
|
326
|
+
repo = "owner/td-backup"
|
|
327
|
+
encrypt = true
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
Auto-sync runs after TUI sessions and CLI writes. Encrypted auto-sync needs a
|
|
331
|
+
password saved in macOS Keychain with `td backup github --save-password`.
|
|
332
|
+
Set `encrypt = false` to
|
|
333
|
+
store plain JSON. The repository remains private, but GitHub can then read the
|
|
334
|
+
backup. Encrypted mode remains the default. `td` stores a digest of the last
|
|
335
|
+
successful export and skips the GitHub update when the data has not changed.
|
|
336
|
+
Manual backups update the same digest.
|
|
@@ -55,6 +55,8 @@ td [command] [--list <name>]
|
|
|
55
55
|
| `td archive` | Open the archive screen |
|
|
56
56
|
| `td export [file]` | Export database to JSON (stdout if no file) |
|
|
57
57
|
| `td import <file>` | Merge a JSON backup into the database |
|
|
58
|
+
| `td backup github [repo]` | Encrypt and upload a backup to a private GitHub repo |
|
|
59
|
+
| `td restore github [repo]` | Download and restore an encrypted GitHub backup |
|
|
58
60
|
| `td update` | Upgrade to the latest version |
|
|
59
61
|
| `td --version` | Print the installed version |
|
|
60
62
|
| `td --help` | Print the help menu |
|
|
@@ -99,6 +101,9 @@ td notes delete 12 --json
|
|
|
99
101
|
|
|
100
102
|
## TUI Keybindings
|
|
101
103
|
|
|
104
|
+
The TUI redraws automatically when you resize the terminal, keeping the current
|
|
105
|
+
selection and unsaved edits. Help supports arrow-key and PageUp/PageDown scrolling.
|
|
106
|
+
|
|
102
107
|
### Tasks
|
|
103
108
|
|
|
104
109
|
| Key | Action |
|
|
@@ -110,6 +115,7 @@ td notes delete 12 --json
|
|
|
110
115
|
| `d` | Delete selected task |
|
|
111
116
|
| `Space` | Toggle done / active |
|
|
112
117
|
| `s` | Star task (pin to top, bold yellow) |
|
|
118
|
+
| `S` | Cycle task sort: A-Z / Z-A / original order |
|
|
113
119
|
| `c` | Archive all completed tasks |
|
|
114
120
|
| `y` | Copy task list to clipboard |
|
|
115
121
|
| `f` | Filter notes by title in current list; submit empty query to clear |
|
|
@@ -192,7 +198,8 @@ marker. Press `Enter` or `Backspace` on an empty continuation to leave list mode
|
|
|
192
198
|
| `q` / `Esc` | Return |
|
|
193
199
|
|
|
194
200
|
Settings you can change: max tasks per list (3–50), max starred tasks, whole-database
|
|
195
|
-
encryption, current-list encryption,
|
|
201
|
+
encryption, current-list encryption, backup export / import, GitHub backup repo,
|
|
202
|
+
GitHub backup now, auto-sync, and GitHub backup encryption.
|
|
196
203
|
|
|
197
204
|
## Agent-friendly
|
|
198
205
|
|
|
@@ -277,9 +284,42 @@ td export > backup.json
|
|
|
277
284
|
|
|
278
285
|
# restore on another machine
|
|
279
286
|
td import backup.json
|
|
287
|
+
|
|
288
|
+
# encrypt and upload to a private GitHub repo (creates td-backup if missing)
|
|
289
|
+
td backup github --save-password
|
|
290
|
+
td backup github lucaspon/my-td-backup --save-password
|
|
291
|
+
|
|
292
|
+
# optional: store plain JSON instead of an encrypted backup
|
|
293
|
+
td backup github --no-encrypt
|
|
294
|
+
|
|
295
|
+
# restore and merge from GitHub
|
|
296
|
+
td restore github
|
|
280
297
|
```
|
|
281
298
|
|
|
282
299
|
You can encrypt the whole database or only the current list from settings (`/`).
|
|
283
300
|
Each encrypted list uses its own password-derived key. Tasks and note bodies stay
|
|
284
301
|
encrypted in JSON backups. Whole-database and per-list encryption cannot run
|
|
285
302
|
together. If you forget a password, there is no recovery.
|
|
303
|
+
|
|
304
|
+
By default, GitHub backups add a separate password-based encryption layer around
|
|
305
|
+
the full JSON export. In encrypted mode, `td` sends only the encrypted envelope
|
|
306
|
+
to GitHub. The `gh` CLI must be
|
|
307
|
+
installed and authenticated. `td backup github` creates the target repository as
|
|
308
|
+
private when it does not exist, and refuses to write to a public repository.
|
|
309
|
+
|
|
310
|
+
Auto-sync uses `~/.config/td/config.toml`:
|
|
311
|
+
|
|
312
|
+
```toml
|
|
313
|
+
[github_backup]
|
|
314
|
+
auto_sync = true
|
|
315
|
+
repo = "owner/td-backup"
|
|
316
|
+
encrypt = true
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
Auto-sync runs after TUI sessions and CLI writes. Encrypted auto-sync needs a
|
|
320
|
+
password saved in macOS Keychain with `td backup github --save-password`.
|
|
321
|
+
Set `encrypt = false` to
|
|
322
|
+
store plain JSON. The repository remains private, but GitHub can then read the
|
|
323
|
+
backup. Encrypted mode remains the default. `td` stores a digest of the last
|
|
324
|
+
successful export and skips the GitHub update when the data has not changed.
|
|
325
|
+
Manual backups update the same digest.
|
td_task-0.2.6/index.html
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<meta name="theme-color" content="#141414">
|
|
7
|
+
<meta name="description" content="td: tasks and Markdown notes in your terminal.">
|
|
8
|
+
<meta property="og:type" content="website">
|
|
9
|
+
<meta property="og:title" content="td">
|
|
10
|
+
<meta property="og:description" content="Tasks and Markdown notes in your terminal.">
|
|
11
|
+
<title>td — terminal tasks and notes</title>
|
|
12
|
+
<link rel="icon" href="website/favicon.svg" type="image/svg+xml">
|
|
13
|
+
<link rel="stylesheet" href="website/style.css">
|
|
14
|
+
<script src="website/main.js" defer></script>
|
|
15
|
+
</head>
|
|
16
|
+
<body>
|
|
17
|
+
<a class="skip-link" href="#main">Skip to content</a>
|
|
18
|
+
<div class="page">
|
|
19
|
+
<header>
|
|
20
|
+
<p>td — tasks and Markdown notes in your terminal.</p>
|
|
21
|
+
</header>
|
|
22
|
+
|
|
23
|
+
<main id="main">
|
|
24
|
+
<ul class="features" aria-label="Features">
|
|
25
|
+
<li>Multiple lists and keyboard navigation.</li>
|
|
26
|
+
<li>Markdown notes with a built-in editor.</li>
|
|
27
|
+
<li>Optional database and list encryption.</li>
|
|
28
|
+
<li>JSON export and a scriptable CLI.</li>
|
|
29
|
+
</ul>
|
|
30
|
+
<figure>
|
|
31
|
+
<a href="assets/demo.gif" aria-label="Open the demo GIF at full size">
|
|
32
|
+
<img src="assets/demo.gif" width="680" height="360" alt="Demo of td: adding tasks, starring priorities, and switching lists with the keyboard." fetchpriority="high">
|
|
33
|
+
</a>
|
|
34
|
+
</figure>
|
|
35
|
+
|
|
36
|
+
<section id="install" aria-label="Installation and usage">
|
|
37
|
+
<dl class="commands">
|
|
38
|
+
<div>
|
|
39
|
+
<dt>install (uv)</dt>
|
|
40
|
+
<dd>
|
|
41
|
+
<code>uv tool install td-task</code>
|
|
42
|
+
<button type="button" data-copy="uv tool install td-task" aria-label="Copy uv install command" hidden>[copy]</button>
|
|
43
|
+
</dd>
|
|
44
|
+
</div>
|
|
45
|
+
<div>
|
|
46
|
+
<dt>or pip</dt>
|
|
47
|
+
<dd>
|
|
48
|
+
<code>pip install td-task</code>
|
|
49
|
+
<button type="button" data-copy="pip install td-task" aria-label="Copy pip install command" hidden>[copy]</button>
|
|
50
|
+
</dd>
|
|
51
|
+
</div>
|
|
52
|
+
<div>
|
|
53
|
+
<dt>run</dt>
|
|
54
|
+
<dd>
|
|
55
|
+
<code>td</code>
|
|
56
|
+
<button type="button" data-copy="td" aria-label="Copy td launch command" hidden>[copy]</button>
|
|
57
|
+
</dd>
|
|
58
|
+
</div>
|
|
59
|
+
<div>
|
|
60
|
+
<dt>or uvx</dt>
|
|
61
|
+
<dd>
|
|
62
|
+
<code>uvx td-task</code>
|
|
63
|
+
<button type="button" data-copy="uvx td-task" aria-label="Copy run without installing command" hidden>[copy]</button>
|
|
64
|
+
</dd>
|
|
65
|
+
</div>
|
|
66
|
+
</dl>
|
|
67
|
+
<p class="requirement">Python 3.14+. <a href="https://docs.astral.sh/uv/getting-started/installation/">uv docs</a>.</p>
|
|
68
|
+
<p class="copy-status" role="status" aria-live="polite"></p>
|
|
69
|
+
</section>
|
|
70
|
+
</main>
|
|
71
|
+
|
|
72
|
+
<footer>
|
|
73
|
+
<a href="https://github.com/lucaspon/td">github.com/lucaspon/td</a>
|
|
74
|
+
<a href="https://twitter.com/lucasponn">@lucasponn</a>
|
|
75
|
+
</footer>
|
|
76
|
+
</div>
|
|
77
|
+
</body>
|
|
78
|
+
</html>
|
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.2.6] - 2026-11-04
|
|
4
|
+
- Back up and restore data through private GitHub repositories using the gh CLI.
|
|
5
|
+
- Encrypt backups by default, with optional plain JSON storage.
|
|
6
|
+
- Add configurable auto-sync that skips unchanged data and supports macOS Keychain passwords.
|
|
7
|
+
- Add Sync settings, backup progress screens, and a link to open the GitHub repository.
|
|
8
|
+
- Support backups to new and existing empty repositories.
|
|
9
|
+
|
|
10
|
+
## [0.2.5] - 2026-11-04
|
|
11
|
+
- Redraw automatically when terminal dimensions change, without waiting for a keypress.
|
|
12
|
+
- Preserve selection, unsaved edits, note cursor, and confirmations while resizing.
|
|
13
|
+
- Fit frames to smaller windows and reflow note content when the terminal grows.
|
|
14
|
+
- Scroll help with arrow keys and PageUp/PageDown.
|
|
15
|
+
|
|
3
16
|
## [0.2.4] - 2026-08-05
|
|
4
17
|
- Add live, case-insensitive note-title filtering with `f` in the current list.
|
|
5
18
|
|
|
@@ -72,6 +72,10 @@ def main() -> None:
|
|
|
72
72
|
_run_export(args)
|
|
73
73
|
elif len(args) > 1 and args[1] in ("import", "-import", "--import"):
|
|
74
74
|
_run_import(args)
|
|
75
|
+
elif len(args) > 1 and args[1] in ("backup", "-backup", "--backup"):
|
|
76
|
+
_run_backup(args)
|
|
77
|
+
elif len(args) > 1 and args[1] in ("restore", "-restore", "--restore"):
|
|
78
|
+
_run_restore(args)
|
|
75
79
|
else:
|
|
76
80
|
from . import db
|
|
77
81
|
lists = db.get_all_lists()
|
|
@@ -123,6 +127,8 @@ def _run_help() -> None:
|
|
|
123
127
|
commands_table.add_row(" archive", "Open TUI directly in the completed archive screen")
|
|
124
128
|
commands_table.add_row(" export [file]", "Export database to JSON file or print to stdout")
|
|
125
129
|
commands_table.add_row(" import <file>", "Import and merge database records from a JSON file")
|
|
130
|
+
commands_table.add_row(" backup github [repo]", "Encrypt and upload a backup to a private GitHub repo")
|
|
131
|
+
commands_table.add_row(" restore github [repo]", "Download and restore an encrypted GitHub backup")
|
|
126
132
|
commands_table.add_row(" update", "Upgrade the `td` package to the latest version")
|
|
127
133
|
|
|
128
134
|
console.print(commands_table)
|
|
@@ -136,6 +142,8 @@ def _run_help() -> None:
|
|
|
136
142
|
|
|
137
143
|
options_table.add_row(" -l, --list <name>", "Specify/create list context (TUI list-lock, CLI scope)")
|
|
138
144
|
options_table.add_row(" --dev", "Watch source code directory and restart interactive TUI on changes")
|
|
145
|
+
options_table.add_row(" --no-encrypt", "Store a GitHub backup as plain JSON (private repo required)")
|
|
146
|
+
options_table.add_row(" --save-password", "Save encrypted backup password in macOS Keychain for auto-sync")
|
|
139
147
|
options_table.add_row(" -h, --help", "Print this detailed, LLM-friendly help menu and exit")
|
|
140
148
|
|
|
141
149
|
console.print(options_table)
|
|
@@ -256,6 +264,7 @@ def _run_notes(list_name: str, has_list: bool, args: list[str]) -> None:
|
|
|
256
264
|
print(json.dumps(note, indent=2))
|
|
257
265
|
else:
|
|
258
266
|
print(f"✓ Note added to '{list_name}' (note ID: {note['id']}, task ID: {note['task_id']})")
|
|
267
|
+
_auto_sync()
|
|
259
268
|
return
|
|
260
269
|
|
|
261
270
|
if action == "list":
|
|
@@ -315,6 +324,7 @@ def _run_notes(list_name: str, has_list: bool, args: list[str]) -> None:
|
|
|
315
324
|
print(json.dumps(updated, indent=2))
|
|
316
325
|
else:
|
|
317
326
|
print(f"✓ Note {note_id} updated.")
|
|
327
|
+
_auto_sync()
|
|
318
328
|
return
|
|
319
329
|
|
|
320
330
|
deleted = db.delete_note(note_id)
|
|
@@ -322,6 +332,7 @@ def _run_notes(list_name: str, has_list: bool, args: list[str]) -> None:
|
|
|
322
332
|
print(json.dumps({"id": note_id, "deleted": deleted}))
|
|
323
333
|
else:
|
|
324
334
|
print(f"✓ Note {note_id} deleted.")
|
|
335
|
+
_auto_sync()
|
|
325
336
|
|
|
326
337
|
|
|
327
338
|
def _run_add(list_name: str, args: list[str]) -> None:
|
|
@@ -338,6 +349,7 @@ def _run_add(list_name: str, args: list[str]) -> None:
|
|
|
338
349
|
print("✗ Failed to add task (maximum active tasks reached).")
|
|
339
350
|
sys.exit(1)
|
|
340
351
|
print(f"✓ Task added successfully to list '{list_name}' (ID: {result['id']})")
|
|
352
|
+
_auto_sync()
|
|
341
353
|
|
|
342
354
|
|
|
343
355
|
def _run_list(list_name: str) -> None:
|
|
@@ -571,10 +583,98 @@ def _run_import(args: list[str]) -> None:
|
|
|
571
583
|
json_str = f.read()
|
|
572
584
|
db.import_from_json(json_str)
|
|
573
585
|
print(f"✓ Database successfully imported and merged from {filepath}")
|
|
586
|
+
_auto_sync()
|
|
574
587
|
except Exception as e:
|
|
575
588
|
print(f"✗ Import failed: {e}")
|
|
576
589
|
sys.exit(1)
|
|
577
590
|
|
|
578
591
|
|
|
592
|
+
def _github_backup_repo(args: list[str]) -> str:
|
|
593
|
+
repo = _option_value(args, "--repo")
|
|
594
|
+
if repo is None and len(args) > 3 and not args[3].startswith("-"):
|
|
595
|
+
repo = args[3]
|
|
596
|
+
return (repo or "td-backup").strip()
|
|
597
|
+
|
|
598
|
+
|
|
599
|
+
def _backup_password(*, confirm: bool) -> str:
|
|
600
|
+
import getpass
|
|
601
|
+
|
|
602
|
+
try:
|
|
603
|
+
password = getpass.getpass("Backup password: ")
|
|
604
|
+
if confirm:
|
|
605
|
+
repeated = getpass.getpass("Confirm backup password: ")
|
|
606
|
+
if password != repeated:
|
|
607
|
+
raise ValueError("Backup passwords do not match")
|
|
608
|
+
if not password:
|
|
609
|
+
raise ValueError("Backup password cannot be empty")
|
|
610
|
+
return password
|
|
611
|
+
except (KeyboardInterrupt, EOFError):
|
|
612
|
+
print("\nCancelled.")
|
|
613
|
+
sys.exit(0)
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
def _auto_sync() -> None:
|
|
617
|
+
from . import backup, config, db
|
|
618
|
+
|
|
619
|
+
try:
|
|
620
|
+
repo = backup.sync_if_configured(db.export_to_json())
|
|
621
|
+
if repo:
|
|
622
|
+
print(
|
|
623
|
+
f"✓ auto-synced backup to {repo}/{backup.BACKUP_FILENAME}",
|
|
624
|
+
file=sys.stderr,
|
|
625
|
+
)
|
|
626
|
+
except Exception as error:
|
|
627
|
+
print(f"✗ auto-sync failed: {error}", file=sys.stderr)
|
|
628
|
+
|
|
629
|
+
|
|
630
|
+
def _run_backup(args: list[str]) -> None:
|
|
631
|
+
if len(args) < 3 or args[2] != "github":
|
|
632
|
+
print("Usage: td backup github [repo|--repo <owner/name>] [--no-encrypt] [--save-password]")
|
|
633
|
+
sys.exit(1)
|
|
634
|
+
|
|
635
|
+
from . import backup, config, db
|
|
636
|
+
|
|
637
|
+
_cli_ensure_unlocked()
|
|
638
|
+
try:
|
|
639
|
+
repo_name = _github_backup_repo(args)
|
|
640
|
+
should_encrypt = "--no-encrypt" not in args
|
|
641
|
+
if not should_encrypt and "--save-password" in args:
|
|
642
|
+
raise ValueError("--save-password requires encrypted backup mode")
|
|
643
|
+
password = _backup_password(confirm=True) if should_encrypt else None
|
|
644
|
+
data = db.export_to_json()
|
|
645
|
+
payload = backup.encrypt_backup(data, password) if password else data
|
|
646
|
+
repo = backup.upload_backup(repo_name, payload)
|
|
647
|
+
config.set_github_backup(
|
|
648
|
+
repo=repo,
|
|
649
|
+
last_sync_digest=backup.backup_digest(data, should_encrypt),
|
|
650
|
+
)
|
|
651
|
+
if password and "--save-password" in args:
|
|
652
|
+
backup.store_password(repo, password)
|
|
653
|
+
mode = "encrypted" if password else "unencrypted"
|
|
654
|
+
print(f"✓ {mode} backup uploaded to {repo}/{backup.BACKUP_FILENAME}")
|
|
655
|
+
except Exception as error:
|
|
656
|
+
print(f"✗ GitHub backup failed: {error}")
|
|
657
|
+
sys.exit(1)
|
|
658
|
+
|
|
659
|
+
|
|
660
|
+
def _run_restore(args: list[str]) -> None:
|
|
661
|
+
if len(args) < 3 or args[2] != "github":
|
|
662
|
+
print("Usage: td restore github [repo|--repo <owner/name>]")
|
|
663
|
+
sys.exit(1)
|
|
664
|
+
|
|
665
|
+
from . import backup, db
|
|
666
|
+
|
|
667
|
+
_cli_ensure_unlocked()
|
|
668
|
+
try:
|
|
669
|
+
payload = backup.download_backup(_github_backup_repo(args))
|
|
670
|
+
password = _backup_password(confirm=False) if backup.is_encrypted_backup(payload) else None
|
|
671
|
+
db.import_from_json(backup.decode_backup(payload, password))
|
|
672
|
+
mode = "encrypted" if password else "unencrypted"
|
|
673
|
+
print(f"✓ {mode} GitHub backup restored and merged")
|
|
674
|
+
except Exception as error:
|
|
675
|
+
print(f"✗ GitHub restore failed: {error}")
|
|
676
|
+
sys.exit(1)
|
|
677
|
+
|
|
678
|
+
|
|
579
679
|
if __name__ == "__main__":
|
|
580
680
|
main()
|