td-task 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,42 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - 'v*'
7
+
8
+ jobs:
9
+ deploy:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - name: Checkout code
13
+ uses: actions/checkout@v4
14
+
15
+ - name: Install uv
16
+ uses: astral-sh/setup-uv@v1
17
+ with:
18
+ enable-cache: true
19
+
20
+ - name: Install Doppler CLI
21
+ run: curl -Ls https://doppler.com/install.sh | sh
22
+
23
+ - name: Set up Python
24
+ uses: actions/setup-python@v5
25
+ with:
26
+ python-version: '3.12'
27
+
28
+ - name: Install build dependencies
29
+ run: uv pip install build twine
30
+
31
+ - name: Build package
32
+ # Using doppler run to ensure PYPI_API is available during the build process
33
+ run: doppler run -- python -m build
34
+ env:
35
+ DOPPLER_TOKEN: ${{ secrets.DOPPLER_TOKEN }}
36
+
37
+ - name: Publish to PyPI
38
+ # Using doppler run to inject the PYPI_API secret into the twine environment
39
+ run: doppler run -- twine upload dist/*
40
+ env:
41
+ DOPPLER_TOKEN: ${{ secrets.DOPPLER_TOKEN }}
42
+ TWINE_USERNAME: __token__
@@ -0,0 +1,7 @@
1
+ .td.db
2
+ dist/
3
+ *.pyc
4
+ __pycache__/
5
+ .venv/
6
+ .env
7
+ AGENTS.md
@@ -0,0 +1 @@
1
+ 3.14
td_task-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,8 @@
1
+ Metadata-Version: 2.4
2
+ Name: td-task
3
+ Version: 0.1.0
4
+ Summary: Minimal TUI todo app
5
+ Requires-Python: >=3.14
6
+ Requires-Dist: cryptography>=42.0.0
7
+ Requires-Dist: rich>=15.0.0
8
+ Requires-Dist: watchdog>=6.0.0
@@ -0,0 +1,45 @@
1
+ # td
2
+
3
+ A minimal TUI todo CLI. No setup required — just run it.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pip install .
9
+ ```
10
+
11
+ Or run directly with `uv`:
12
+
13
+ ```bash
14
+ uv run td
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ ```bash
20
+ td # Open the task manager
21
+ td archive # View archived tasks
22
+ ```
23
+
24
+ ## Keybindings
25
+
26
+ | Key | Action |
27
+ |-----|--------|
28
+ | ↑/k | Move up |
29
+ | ↓/j | Move down |
30
+ | Enter | Edit hovered item |
31
+ | n | Add new task (max 10) |
32
+ | d | Delete hovered task |
33
+ | Space | Toggle done |
34
+ | a | Archive all done tasks |
35
+ | q | Quit |
36
+
37
+ In edit mode, type to modify text, Enter to confirm, Esc to cancel.
38
+
39
+ ## Storage
40
+
41
+ Tasks are stored in `~/.td.db` (portable SQLite file). Delete it to start fresh.
42
+
43
+ ## Show some love
44
+
45
+ Ethereum: `0x88a0e1b80B92F0cFaa89a936b827Ce291cFb0028`
@@ -0,0 +1,23 @@
1
+ [project]
2
+ name = "td-task"
3
+ version = "0.1.0"
4
+ description = "Minimal TUI todo app"
5
+ requires-python = ">=3.14"
6
+ dependencies = [
7
+ "rich>=15.0.0",
8
+ "watchdog>=6.0.0",
9
+ "cryptography>=42.0.0",
10
+ ]
11
+
12
+ [project.scripts]
13
+ td = "td.__main__:main"
14
+
15
+ [build-system]
16
+ requires = ["hatchling"]
17
+ build-backend = "hatchling.build"
18
+
19
+ [tool.hatch.build.targets.wheel]
20
+ packages = ["src/td"]
21
+
22
+ [dependency-groups]
23
+ dev = []
File without changes
@@ -0,0 +1,160 @@
1
+ from __future__ import annotations
2
+
3
+ import sys
4
+ import os
5
+
6
+ from .tui import run_main, run_archive, run_settings
7
+
8
+
9
+ def main() -> None:
10
+ if "--dev" in sys.argv:
11
+ _run_dev()
12
+ elif len(sys.argv) > 1 and sys.argv[1] == "archive":
13
+ run_archive()
14
+ elif len(sys.argv) > 1 and sys.argv[1] == "update":
15
+ _run_update()
16
+ elif len(sys.argv) > 1 and sys.argv[1] == "add":
17
+ _run_add()
18
+ elif len(sys.argv) > 1 and sys.argv[1] == "list":
19
+ _run_list()
20
+ else:
21
+ run_main()
22
+
23
+
24
+ def _cli_ensure_unlocked() -> None:
25
+ from . import db
26
+ if db.is_encryption_enabled():
27
+ import getpass
28
+ attempts = 0
29
+ while attempts < 3:
30
+ prompt_text = "Database is encrypted. Enter password: " if attempts == 0 else f"Incorrect password. Try again: "
31
+ try:
32
+ password = getpass.getpass(prompt_text)
33
+ except (KeyboardInterrupt, EOFError):
34
+ print("\nCancelled.")
35
+ sys.exit(0)
36
+ if db.set_encryption_key_from_password(password):
37
+ return
38
+ attempts += 1
39
+ print("✗ Too many incorrect attempts. Exiting.")
40
+ sys.exit(1)
41
+
42
+
43
+ def _run_add() -> None:
44
+ if len(sys.argv) < 3 or not sys.argv[2].strip():
45
+ print("Usage: td add <task_text>")
46
+ sys.exit(1)
47
+
48
+ task_text = sys.argv[2].strip()
49
+ _cli_ensure_unlocked()
50
+
51
+ from . import db
52
+ result = db.add_task(task_text)
53
+ if result is None:
54
+ print("✗ Failed to add task (maximum active tasks reached).")
55
+ sys.exit(1)
56
+ print(f"✓ Task added successfully (ID: {result['id']})")
57
+
58
+
59
+ def _run_list() -> None:
60
+ _cli_ensure_unlocked()
61
+ from . import db
62
+ from rich.console import Console
63
+ from rich.text import Text
64
+
65
+ tasks = db.get_active_tasks()
66
+ console = Console()
67
+
68
+ open_count = sum(1 for t in tasks if t["status"] == "active")
69
+ completed_count = db.get_completed_count()
70
+ header = Text("td • ", style="bold")
71
+ header.append(Text(f"{open_count} open", style="dim"))
72
+ header.append(Text(" / ", style="dim"))
73
+ header.append(Text(f"{completed_count} completed", style="dim"))
74
+ console.print(header)
75
+
76
+ console.print(Text("─" * 40, style="dim"))
77
+
78
+ if not tasks:
79
+ console.print(Text(" No tasks found.", style="dim"))
80
+ return
81
+
82
+ for i, task in enumerate(tasks, 1):
83
+ is_done = task["status"] == "done"
84
+ marker = "✓" if is_done else "○"
85
+
86
+ text = task["text"]
87
+ if is_done:
88
+ line_text = Text(text, style="strike dim")
89
+ marker_text = Text(marker, style="green bold")
90
+ else:
91
+ line_text = Text(text)
92
+ marker_text = Text(marker, style="yellow")
93
+
94
+ line = Text(" ")
95
+ line.append(marker_text)
96
+ line.append(" ")
97
+ line.append(line_text)
98
+ console.print(line)
99
+
100
+
101
+ def _run_update() -> None:
102
+ """Update td to the latest version from PyPI."""
103
+ import subprocess
104
+ print("Updating td...")
105
+ result = subprocess.run(
106
+ ["uv", "tool", "upgrade", "td-task"],
107
+ capture_output=True, text=True, timeout=60,
108
+ )
109
+ if result.returncode == 0:
110
+ print("✓ td updated successfully")
111
+ else:
112
+ print(f"✗ update failed: {result.stderr.strip()}")
113
+ sys.exit(1)
114
+
115
+
116
+ def _run_dev() -> None:
117
+ """Watch src/td/ for changes and restart the TUI automatically."""
118
+ from watchdog.observers import Observer
119
+ from watchdog.events import FileSystemEventHandler
120
+
121
+ import subprocess
122
+ import time
123
+
124
+ src_dir = os.path.dirname(__file__)
125
+
126
+ class RestartHandler(FileSystemEventHandler):
127
+ def __init__(self):
128
+ self.changed = False
129
+
130
+ def on_modified(self, event):
131
+ if event.src_path.endswith(".py"):
132
+ self.changed = True
133
+
134
+ def on_created(self, event):
135
+ if event.src_path.endswith(".py"):
136
+ self.changed = True
137
+
138
+ observer = Observer()
139
+ handler = RestartHandler()
140
+ observer.schedule(handler, src_dir, recursive=True)
141
+ observer.start()
142
+
143
+ print("td --dev: watching for changes... (Ctrl+C to stop)")
144
+ subprocess.run(["uv", "run", "td"])
145
+ try:
146
+ while True:
147
+ time.sleep(0.5)
148
+ if handler.changed:
149
+ handler.changed = False
150
+ print("\n⟳ Change detected, restarting...\n")
151
+ subprocess.run(["uv", "run", "td"])
152
+ except KeyboardInterrupt:
153
+ pass
154
+ finally:
155
+ observer.stop()
156
+ observer.join()
157
+
158
+
159
+ if __name__ == "__main__":
160
+ main()