rook-cli 0.2.0__py3-none-any.whl
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.
- rook/__init__.py +1 -0
- rook/__main__.py +48 -0
- rook/app.py +222 -0
- rook/branding.py +69 -0
- rook/domain/__init__.py +0 -0
- rook/domain/tasks.py +55 -0
- rook/formatting.py +10 -0
- rook/paths.py +31 -0
- rook/persistence/__init__.py +0 -0
- rook/persistence/archive.py +72 -0
- rook/persistence/database.py +18 -0
- rook/persistence/metadata.py +56 -0
- rook/persistence/migrations.py +111 -0
- rook/persistence/tasks.py +185 -0
- rook/services/__init__.py +0 -0
- rook/services/rollover.py +94 -0
- rook/services/tasks.py +85 -0
- rook/services/undo.py +72 -0
- rook/symbols.py +40 -0
- rook/widgets/__init__.py +0 -0
- rook/widgets/archive_screen.py +162 -0
- rook/widgets/shortcut_footer.py +79 -0
- rook/widgets/task_line_input.py +44 -0
- rook/widgets/task_list.py +415 -0
- rook/widgets/task_row.py +155 -0
- rook_cli-0.2.0.dist-info/METADATA +120 -0
- rook_cli-0.2.0.dist-info/RECORD +30 -0
- rook_cli-0.2.0.dist-info/WHEEL +4 -0
- rook_cli-0.2.0.dist-info/entry_points.txt +2 -0
- rook_cli-0.2.0.dist-info/licenses/LICENSE +10 -0
rook/widgets/task_row.py
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import textwrap
|
|
2
|
+
|
|
3
|
+
from rich.text import Text
|
|
4
|
+
from textual import events
|
|
5
|
+
from textual.app import ComposeResult
|
|
6
|
+
from textual.widget import Widget
|
|
7
|
+
from textual.widgets import Static
|
|
8
|
+
|
|
9
|
+
from rook.domain.tasks import Task, TaskState
|
|
10
|
+
from rook.symbols import PREFERRED, PREFIX_WIDTH, SAFE, state_symbol
|
|
11
|
+
from rook.widgets.task_line_input import TaskLineInput
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def render_task_row_text(task: Task, *, selected: bool, width: int, safe_symbols: bool) -> Text:
|
|
15
|
+
"""Build one Task row as a Rich Text.
|
|
16
|
+
|
|
17
|
+
Continuation lines of a wrapped Task align under the text rather than
|
|
18
|
+
repeating the selection or state-symbol columns (Section 11.3). A
|
|
19
|
+
Deleted Task keeps its normal bullet with strikethrough styling in the
|
|
20
|
+
preferred mode, or the ``~`` fallback symbol with muted styling when
|
|
21
|
+
``safe_symbols`` is set (Section 11.5, 11.7). The selection column
|
|
22
|
+
shows the accent-styled cursor only on the selected row (Section 11.6).
|
|
23
|
+
"""
|
|
24
|
+
symbols = SAFE if safe_symbols else PREFERRED
|
|
25
|
+
body_width = max(width - PREFIX_WIDTH, 1)
|
|
26
|
+
|
|
27
|
+
symbol = state_symbol(task.state, symbols, safe_mode=safe_symbols)
|
|
28
|
+
cursor = symbols.selected if selected else " "
|
|
29
|
+
prefix = f"{cursor} {symbol} "
|
|
30
|
+
wrapped_lines = textwrap.wrap(
|
|
31
|
+
task.text,
|
|
32
|
+
width=body_width,
|
|
33
|
+
break_long_words=False,
|
|
34
|
+
break_on_hyphens=False,
|
|
35
|
+
) or [""]
|
|
36
|
+
|
|
37
|
+
row = Text(prefix + wrapped_lines[0])
|
|
38
|
+
for continuation in wrapped_lines[1:]:
|
|
39
|
+
row.append("\n" + " " * PREFIX_WIDTH + continuation)
|
|
40
|
+
|
|
41
|
+
if task.state is TaskState.OPEN:
|
|
42
|
+
row.stylize("bold", len(prefix))
|
|
43
|
+
if task.state is TaskState.DELETED and not safe_symbols:
|
|
44
|
+
row.stylize("strike", len(prefix))
|
|
45
|
+
|
|
46
|
+
if selected:
|
|
47
|
+
row.stylize("bold", 0, 1)
|
|
48
|
+
|
|
49
|
+
return row
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class TaskRow(Widget):
|
|
53
|
+
"""One Task row within the scrollable Today task list.
|
|
54
|
+
|
|
55
|
+
Normally renders as read-only text via ``render_task_row_text``
|
|
56
|
+
(unchanged from Phase 2/3). While ``editing`` is True, it instead
|
|
57
|
+
composes a small prefix plus a live ``TaskLineInput`` in place of
|
|
58
|
+
that text - creation and editing happen directly on the row, never
|
|
59
|
+
in a separate dialog (Section 2.5).
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
DEFAULT_CSS = """
|
|
63
|
+
TaskRow {
|
|
64
|
+
height: auto;
|
|
65
|
+
}
|
|
66
|
+
TaskRow.-editing {
|
|
67
|
+
layout: horizontal;
|
|
68
|
+
height: 1;
|
|
69
|
+
}
|
|
70
|
+
TaskRow.-editing .task-row-prefix {
|
|
71
|
+
width: 4;
|
|
72
|
+
height: 1;
|
|
73
|
+
}
|
|
74
|
+
TaskRow.-editing TaskLineInput {
|
|
75
|
+
width: 1fr;
|
|
76
|
+
height: 1;
|
|
77
|
+
}
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
def __init__(
|
|
81
|
+
self,
|
|
82
|
+
item: Task,
|
|
83
|
+
*,
|
|
84
|
+
selected: bool = False,
|
|
85
|
+
safe_symbols: bool = False,
|
|
86
|
+
editing: bool = False,
|
|
87
|
+
edit_value: str = "",
|
|
88
|
+
id: str | None = None,
|
|
89
|
+
) -> None:
|
|
90
|
+
# Note: "item" (not "task") because Widget already reserves the
|
|
91
|
+
# read-only "task" property for its own backing asyncio Task.
|
|
92
|
+
super().__init__(id=id)
|
|
93
|
+
self.item = item
|
|
94
|
+
self.selected = selected
|
|
95
|
+
self._safe_symbols = safe_symbols
|
|
96
|
+
self.editing = editing
|
|
97
|
+
self._edit_value = edit_value
|
|
98
|
+
if editing:
|
|
99
|
+
self.add_class("-editing")
|
|
100
|
+
|
|
101
|
+
def compose(self) -> ComposeResult:
|
|
102
|
+
if self.editing:
|
|
103
|
+
symbols = SAFE if self._safe_symbols else PREFERRED
|
|
104
|
+
cursor = symbols.selected if self.selected else " "
|
|
105
|
+
symbol = state_symbol(self.item.state, symbols, safe_mode=self._safe_symbols)
|
|
106
|
+
# markup=False: the prefix is fixed chrome, never user text.
|
|
107
|
+
yield Static(f"{cursor} {symbol} ", classes="task-row-prefix", markup=False)
|
|
108
|
+
yield TaskLineInput(
|
|
109
|
+
self._edit_value,
|
|
110
|
+
compact=True,
|
|
111
|
+
select_on_focus=False,
|
|
112
|
+
max_length=1000,
|
|
113
|
+
)
|
|
114
|
+
else:
|
|
115
|
+
# markup=False: Task text must never be interpreted as Rich
|
|
116
|
+
# console markup, so a task literally containing "[bold]"
|
|
117
|
+
# renders unchanged.
|
|
118
|
+
yield Static(markup=False)
|
|
119
|
+
|
|
120
|
+
def on_mount(self) -> None:
|
|
121
|
+
if self.editing:
|
|
122
|
+
editor = self.query_one(TaskLineInput)
|
|
123
|
+
editor.cursor_position = len(editor.value)
|
|
124
|
+
editor.focus()
|
|
125
|
+
else:
|
|
126
|
+
self._refresh_display()
|
|
127
|
+
|
|
128
|
+
def on_resize(self, event: events.Resize) -> None:
|
|
129
|
+
if not self.editing:
|
|
130
|
+
self._refresh_display()
|
|
131
|
+
|
|
132
|
+
def set_selected(self, selected: bool) -> None:
|
|
133
|
+
if self.selected != selected:
|
|
134
|
+
self.selected = selected
|
|
135
|
+
if not self.editing:
|
|
136
|
+
self._refresh_display()
|
|
137
|
+
|
|
138
|
+
def set_item(self, item: Task) -> None:
|
|
139
|
+
"""Redraw after the underlying Task's state or text changed
|
|
140
|
+
(Section 8), without needing the whole list to recompose."""
|
|
141
|
+
self.item = item
|
|
142
|
+
if not self.editing:
|
|
143
|
+
self._refresh_display()
|
|
144
|
+
|
|
145
|
+
def _refresh_display(self) -> None:
|
|
146
|
+
width = self.size.width or 80
|
|
147
|
+
display = self.query_one(Static)
|
|
148
|
+
display.update(
|
|
149
|
+
render_task_row_text(
|
|
150
|
+
self.item,
|
|
151
|
+
selected=self.selected,
|
|
152
|
+
width=width,
|
|
153
|
+
safe_symbols=self._safe_symbols,
|
|
154
|
+
)
|
|
155
|
+
)
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rook-cli
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: A local, keyboard-first terminal daily journal.
|
|
5
|
+
Project-URL: Repository, https://github.com/JanviChawla/rook
|
|
6
|
+
Author: Janvi Chawla
|
|
7
|
+
License-Expression: LicenseRef-Proprietary
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Requires-Dist: textual>=0.60
|
|
11
|
+
Provides-Extra: dev
|
|
12
|
+
Requires-Dist: build>=1.2; extra == 'dev'
|
|
13
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
14
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
15
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# Rook ♖
|
|
19
|
+
|
|
20
|
+
A local, keyboard-first terminal journal for working through one day at a time.
|
|
21
|
+
|
|
22
|
+
```text
|
|
23
|
+
rook ♖ Today — Friday, July 24, 2026
|
|
24
|
+
⋆⁺₊ (。'▽'。)♡ ₊⁺⋆ "Showing up is the whole trick."
|
|
25
|
+
|
|
26
|
+
❯ • Finish the presentation
|
|
27
|
+
• Reply to Alex
|
|
28
|
+
> Read Chapter 3
|
|
29
|
+
× Submit the expense report
|
|
30
|
+
|
|
31
|
+
[n] new [e/Ent] edit [x] complete [>] migrate [d] delete [u] undo [a] archive [q] quit
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Rook is inspired by the simplicity of a paper bullet journal:
|
|
35
|
+
|
|
36
|
+
- one active list: **Today**
|
|
37
|
+
- direct, in-place editing
|
|
38
|
+
- local SQLite storage
|
|
39
|
+
- read-only history
|
|
40
|
+
- no accounts, cloud sync, reminders, priorities, or telemetry
|
|
41
|
+
|
|
42
|
+
## Installation
|
|
43
|
+
|
|
44
|
+
Requires Python 3.10 or newer. Install with [uv](https://docs.astral.sh/uv/):
|
|
45
|
+
|
|
46
|
+
```powershell
|
|
47
|
+
uv tool install rook-cli
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Then launch:
|
|
51
|
+
|
|
52
|
+
```powershell
|
|
53
|
+
rook
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
To upgrade later:
|
|
57
|
+
|
|
58
|
+
```powershell
|
|
59
|
+
uv tool upgrade rook-cli
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Keyboard reference
|
|
63
|
+
|
|
64
|
+
| Key | Action |
|
|
65
|
+
|-----|--------|
|
|
66
|
+
| `n` | New task |
|
|
67
|
+
| `e` or `Ent` | Edit selected task |
|
|
68
|
+
| `x` | Toggle complete |
|
|
69
|
+
| `>` | Toggle migrated |
|
|
70
|
+
| `d` | Soft-delete / permanently remove (press twice) |
|
|
71
|
+
| `u` | Undo last action |
|
|
72
|
+
| `a` | Open weekly archive |
|
|
73
|
+
| `↑` / `↓` | Move selection |
|
|
74
|
+
| `Esc` | Cancel edit |
|
|
75
|
+
| `q` | Quit |
|
|
76
|
+
|
|
77
|
+
## Data and privacy
|
|
78
|
+
|
|
79
|
+
Tasks are stored in a local SQLite database. Nothing leaves your machine.
|
|
80
|
+
|
|
81
|
+
| Platform | Location |
|
|
82
|
+
|----------|----------|
|
|
83
|
+
| Windows | `%LOCALAPPDATA%\Rook\data.sqlite3` |
|
|
84
|
+
| macOS | `~/Library/Application Support/Rook/data.sqlite3` |
|
|
85
|
+
| Linux | `~/.local/share/rook/data.sqlite3` |
|
|
86
|
+
|
|
87
|
+
To print the exact path on your system:
|
|
88
|
+
|
|
89
|
+
```powershell
|
|
90
|
+
rook --data-path
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Uninstalling Rook does not delete the database.
|
|
94
|
+
|
|
95
|
+
## FAQ
|
|
96
|
+
|
|
97
|
+
**How do I change the archive week layout from Sun–Sat to Mon–Sun?**
|
|
98
|
+
|
|
99
|
+
There is no in-app setting yet. Update the value directly in the SQLite database.
|
|
100
|
+
Find the exact path with `rook --data-path`, or use the default:
|
|
101
|
+
|
|
102
|
+
```powershell
|
|
103
|
+
sqlite3 "$env:LOCALAPPDATA\Rook\data.sqlite3"
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
```sql
|
|
107
|
+
INSERT OR REPLACE INTO app_meta (key, value) VALUES ('week_start_day', 'monday');
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
To switch back to Sunday-start:
|
|
111
|
+
|
|
112
|
+
```sql
|
|
113
|
+
INSERT OR REPLACE INTO app_meta (key, value) VALUES ('week_start_day', 'sunday');
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## License
|
|
117
|
+
|
|
118
|
+
Copyright (c) 2026 Janvi Chawla. All Rights Reserved.
|
|
119
|
+
|
|
120
|
+
Personal use permitted. No redistribution or derivative works without permission.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
rook/__init__.py,sha256=0D6srYi9ejPUnwpjVmDoJB-oMuBqPSiP50LPRoM8G0I,23
|
|
2
|
+
rook/__main__.py,sha256=iVul5IrHqK5RiTO2Htt6yLUJ92l7kHrJUB6b63_-Rv4,1411
|
|
3
|
+
rook/app.py,sha256=0J4vO22P9Axqc50Ra4GolAU23587Lm6Jkr1hJ0kRsTQ,8973
|
|
4
|
+
rook/branding.py,sha256=jqHqrJzmmmzEmcbLJQa07M6rUk-g0V_RP3TJ5fE4-Bc,2479
|
|
5
|
+
rook/formatting.py,sha256=OIcSerLfzdxRYPJrseNKD1aSXgfBcHfPSY6BSDL196M,338
|
|
6
|
+
rook/paths.py,sha256=amhsfZdeV3vgtuwD9SWT7f-NGan2xk1Y5tKpqkSZRK4,953
|
|
7
|
+
rook/symbols.py,sha256=qJRU-C9LBTpXy-KMvr2y9aakFNu60Lnoe92_GKd6KW8,1449
|
|
8
|
+
rook/domain/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
rook/domain/tasks.py,sha256=qxEj1FSIFJhUfXL_PYal528hW4kljnvWTHYfwNeMQIc,1588
|
|
10
|
+
rook/persistence/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
+
rook/persistence/archive.py,sha256=6F8NuA4n5BerfbyDa2umAxqecwMIy5wsjrGwp4eQwS4,2562
|
|
12
|
+
rook/persistence/database.py,sha256=O4ZkVqmUIRUI14D0H1iDeXslug2Mi82U57O3KpXL8zQ,669
|
|
13
|
+
rook/persistence/metadata.py,sha256=EbOy3Q_QvbyZj-9km6t0AVoa4R2ZWlOvypn9Mld3ZHs,2200
|
|
14
|
+
rook/persistence/migrations.py,sha256=94mwuMLoke39wuqx5fQY4B28T_UCQN4wk07rXVSfKhI,3625
|
|
15
|
+
rook/persistence/tasks.py,sha256=TE5qogIDjlwZB-Oh7R_BODKlsxOOJKAQsZt_kWEEymc,7215
|
|
16
|
+
rook/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
|
+
rook/services/rollover.py,sha256=q4PmSowmAibrdGPaUdg0G4HGu073V7szy9QtAa6Cu3s,3478
|
|
18
|
+
rook/services/tasks.py,sha256=gDJk94WIg-7DLqVrKmGO8_gvJ-0rJzXqc6UjVsF1zNo,3140
|
|
19
|
+
rook/services/undo.py,sha256=yq13HVwA_ihCbzMFzklkKSZWH5puzCZYXAaCtJGLrMg,1897
|
|
20
|
+
rook/widgets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
21
|
+
rook/widgets/archive_screen.py,sha256=hXLqBke0lGFBlle3psk9vsd_3SesGN6xbsBXIQsXQcc,5773
|
|
22
|
+
rook/widgets/shortcut_footer.py,sha256=7lzJksCcE5CBLsLnuVhiPrbaWVtZSMAYOuFbtIz8HKc,2675
|
|
23
|
+
rook/widgets/task_line_input.py,sha256=-5vK5lCrLX4QdowZmb0P_tDtZpxZCmrAgmTGo4OASeM,1548
|
|
24
|
+
rook/widgets/task_list.py,sha256=seBkSUINidmLswjUrswWMyA5Q9S9-N6Af9ofg2zNUr8,16047
|
|
25
|
+
rook/widgets/task_row.py,sha256=CPhVjaJ32PrNkuPRiCbivuc3wlJNAxAu2yB5XNoHNSg,5309
|
|
26
|
+
rook_cli-0.2.0.dist-info/METADATA,sha256=ikY98Six7uPvmAMwOb8KUJ8yV1fZWf-bFVWhPmacTHo,2885
|
|
27
|
+
rook_cli-0.2.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
28
|
+
rook_cli-0.2.0.dist-info/entry_points.txt,sha256=b_8aSSokv9p5H5RQayv0BlgjAxjK2ajC2TdbeSWIKq4,44
|
|
29
|
+
rook_cli-0.2.0.dist-info/licenses/LICENSE,sha256=a4j6fArfSspsaiIjtzTzoCSO9GBR3sHD8KQxdSu29eQ,505
|
|
30
|
+
rook_cli-0.2.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Copyright (c) 2026 Janvi Chawla. All Rights Reserved.
|
|
2
|
+
|
|
3
|
+
This software and its source code are proprietary and confidential.
|
|
4
|
+
No part of this software may be copied, modified, merged, distributed,
|
|
5
|
+
sublicensed, or used to create derivative works without explicit written
|
|
6
|
+
permission from the copyright owner.
|
|
7
|
+
|
|
8
|
+
You may install and use this software for personal, non-commercial purposes.
|
|
9
|
+
You may not redistribute it, repackage it, or incorporate it into another
|
|
10
|
+
product or service without permission.
|