jott-cli 0.5.1__tar.gz → 0.5.2__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.
- {jott_cli-0.5.1/jott_cli.egg-info → jott_cli-0.5.2}/PKG-INFO +1 -1
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/__init__.py +1 -1
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/app.py +2 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/commands/_metadata_mixin.py +115 -5
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/commands/_transfer_mixin.py +48 -72
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/core/_metadata_mixin.py +8 -3
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/core/_subtask_mixin.py +1 -1
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/ui/__init__.py +5 -2
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/ui/display_archive.py +5 -4
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/ui/display_footer.py +5 -4
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/ui/display_help.py +11 -6
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/ui/display_projects.py +10 -6
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/ui/display_tasks.py +125 -56
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/ui/styles.py +43 -2
- {jott_cli-0.5.1 → jott_cli-0.5.2/jott_cli.egg-info}/PKG-INFO +1 -1
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jott_cli.egg-info/SOURCES.txt +1 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/pyproject.toml +1 -1
- {jott_cli-0.5.1 → jott_cli-0.5.2}/tests/test_highlight.py +64 -44
- {jott_cli-0.5.1 → jott_cli-0.5.2}/tests/test_jot.py +4 -4
- {jott_cli-0.5.1 → jott_cli-0.5.2}/tests/test_styles.py +14 -4
- jott_cli-0.5.2/tests/test_subtask_notes.py +545 -0
- jott_cli-0.5.2/tests/test_terminal_wrap.py +212 -0
- jott_cli-0.5.1/tests/test_subtask_notes.py +0 -246
- {jott_cli-0.5.1 → jott_cli-0.5.2}/LICENSE +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/README.md +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/_app_navigation_mixin.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/_dispatch_mixin.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/categories/__init__.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/categories/config.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/categories/manager.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/categories/templates.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/cli/__init__.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/cli/archive.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/cli/config.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/cli/views.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/commands/__init__.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/commands/_ai_analysis_mixin.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/commands/_ai_suggest_mixin.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/commands/_audio_timer_mixin.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/commands/_bulk_mixin.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/commands/_context_mixin.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/commands/_core_mixin.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/commands/_gcal_mixin.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/commands/_notes_mixin.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/commands/_web_clipboard_mixin.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/commands/handler.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/core/__init__.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/core/_age_backlog_mixin.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/core/_compress_mixin.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/core/_crud_mixin.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/core/_delete_mixin.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/core/_export_mixin.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/core/_id_migration_mixin.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/core/_navigation_mixin.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/core/_persistence_mixin.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/core/archive_manager.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/core/constants.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/core/id_manager.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/core/task_manager.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/integrations/__init__.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/integrations/gcal/__init__.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/integrations/gcal/account_manager.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/integrations/gcal/auth.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/integrations/gcal/events.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/integrations/keywords/__init__.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/integrations/keywords/_config_mixin.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/integrations/keywords/_handlers_mixin.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/integrations/keywords/handler.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/mcp/__init__.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/mcp/handlers.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/mcp/schemas.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/mcp/server.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/projects/__init__.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/projects/backup.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/projects/registry.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/ui/display.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/ui/formatting.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/ui/input.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/ui/picker.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/utils/__init__.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/utils/date_utils.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/utils/text_utils.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jot/utils/validation.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jott_cli.egg-info/dependency_links.txt +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jott_cli.egg-info/entry_points.txt +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jott_cli.egg-info/requires.txt +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/jott_cli.egg-info/top_level.txt +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/setup.cfg +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/setup.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/tests/test_command_handler.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/tests/test_dispatch.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/tests/test_edit_edge_cases.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/tests/test_fuzzy_search.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/tests/test_gcal_notes.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/tests/test_input.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/tests/test_picker.py +0 -0
- {jott_cli-0.5.1 → jott_cli-0.5.2}/tests/test_today_filter.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: jott-cli
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.2
|
|
4
4
|
Summary: Feature-rich interactive CLI task manager with AI integration, calendar sync, and keyword automation
|
|
5
5
|
Author-email: Scott Anderson <sonander@gmail.com>
|
|
6
6
|
Maintainer-email: Scott Anderson <sonander@gmail.com>
|
|
@@ -7,7 +7,7 @@ import importlib.util
|
|
|
7
7
|
import os
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
|
|
10
|
-
__version__ = "0.5.
|
|
10
|
+
__version__ = "0.5.2"
|
|
11
11
|
|
|
12
12
|
# When building Sphinx docs, skip the dynamic import bridge and heavy deps.
|
|
13
13
|
# Set JOT_SPHINX_BUILD=1 in docs/sphinx/conf.py before importing jot.
|
|
@@ -80,6 +80,8 @@ class App(DispatchMixin, AppNavigationMixin):
|
|
|
80
80
|
'#': 'reauthenticate_google_calendar',
|
|
81
81
|
'\x12': 'register_current_project', # Ctrl+R
|
|
82
82
|
'*': 'toggle_highlight',
|
|
83
|
+
'~': 'quick_highlight',
|
|
84
|
+
'\x0c': 'assign_parent', # Ctrl+L
|
|
83
85
|
}
|
|
84
86
|
|
|
85
87
|
def __init__(self, task_manager, command_handler, registry,
|
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
from datetime import datetime
|
|
4
4
|
|
|
5
5
|
from jot.core.constants import DAY_COLORS, DAY_ABBREVIATIONS
|
|
6
|
-
from jot.ui.picker import PickerItem, quick_picker
|
|
6
|
+
from jot.ui.picker import PickerItem, quick_picker, fuzzy_picker
|
|
7
7
|
from jot.ui.styles import (
|
|
8
8
|
RESET, BOLD, DIM, GREEN, RED,
|
|
9
9
|
PRIORITY_COLORS, PRIORITY_SYMBOLS, STATUS_COLORS, STATUS_SYMBOLS,
|
|
10
|
+
HIGHLIGHT_COLORS,
|
|
10
11
|
)
|
|
11
12
|
from jot.ui.input import restore_terminal
|
|
12
13
|
|
|
@@ -124,14 +125,52 @@ class MetadataMixin:
|
|
|
124
125
|
return True
|
|
125
126
|
|
|
126
127
|
def toggle_highlight(self):
|
|
127
|
-
"""
|
|
128
|
+
"""Pick a highlight color for the current task, or clear it."""
|
|
128
129
|
current_task = self.task_manager.get_current_task()
|
|
129
130
|
if not current_task:
|
|
130
131
|
return True
|
|
132
|
+
|
|
133
|
+
items = [PickerItem(value="_clear", label="none (clear)", color=DIM)]
|
|
134
|
+
for name, fmt in HIGHLIGHT_COLORS.items():
|
|
135
|
+
items.append(PickerItem(
|
|
136
|
+
value=name, label=name, color=fmt,
|
|
137
|
+
))
|
|
138
|
+
|
|
139
|
+
result = quick_picker(
|
|
140
|
+
items,
|
|
141
|
+
prompt=f"Highlight color for: {current_task['text']}",
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
if result.value is None:
|
|
145
|
+
return True
|
|
146
|
+
|
|
147
|
+
if result.value == "_clear":
|
|
148
|
+
self.task_manager.set_highlight(color=None)
|
|
149
|
+
print(f"\n{GREEN}✓{RESET} Highlight cleared")
|
|
150
|
+
else:
|
|
151
|
+
self.task_manager.set_highlight(color=result.value)
|
|
152
|
+
fmt = HIGHLIGHT_COLORS[result.value]
|
|
153
|
+
print(f"\n{GREEN}✓{RESET} Highlight set to {fmt} {result.value} {RESET}")
|
|
154
|
+
|
|
155
|
+
print("\nPress Enter to continue...", end='', flush=True)
|
|
156
|
+
restore_terminal()
|
|
157
|
+
input()
|
|
158
|
+
return True
|
|
159
|
+
|
|
160
|
+
def quick_highlight(self):
|
|
161
|
+
"""Toggle default highlight color on the current task."""
|
|
162
|
+
current_task = self.task_manager.get_current_task()
|
|
163
|
+
if not current_task:
|
|
164
|
+
return True
|
|
165
|
+
from jot.ui.styles import HIGHLIGHT_DEFAULT
|
|
131
166
|
was_highlighted = current_task.get('highlight', False)
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
167
|
+
if was_highlighted:
|
|
168
|
+
self.task_manager.set_highlight(color=None)
|
|
169
|
+
print(f"\n{GREEN}✓{RESET} Highlight cleared")
|
|
170
|
+
else:
|
|
171
|
+
self.task_manager.set_highlight(color=HIGHLIGHT_DEFAULT)
|
|
172
|
+
fmt = HIGHLIGHT_COLORS[HIGHLIGHT_DEFAULT]
|
|
173
|
+
print(f"\n{GREEN}✓{RESET} Highlighted {fmt} {HIGHLIGHT_DEFAULT} {RESET}")
|
|
135
174
|
return True
|
|
136
175
|
|
|
137
176
|
def set_priority_high(self):
|
|
@@ -176,3 +215,74 @@ class MetadataMixin:
|
|
|
176
215
|
input()
|
|
177
216
|
|
|
178
217
|
return True
|
|
218
|
+
|
|
219
|
+
def assign_parent(self):
|
|
220
|
+
"""Assign current task as a subtask of another task (Ctrl+L)."""
|
|
221
|
+
current_task = self.task_manager.get_current_task()
|
|
222
|
+
if not current_task:
|
|
223
|
+
print("\n✗ No current task selected")
|
|
224
|
+
return True
|
|
225
|
+
|
|
226
|
+
task_id = current_task['id']
|
|
227
|
+
labels = current_task.get('labels', [])
|
|
228
|
+
|
|
229
|
+
# Find existing parent label
|
|
230
|
+
current_parent = None
|
|
231
|
+
for lbl in labels:
|
|
232
|
+
if lbl.startswith('parent:'):
|
|
233
|
+
current_parent = lbl.split(':', 1)[1]
|
|
234
|
+
break
|
|
235
|
+
|
|
236
|
+
# Build picker items
|
|
237
|
+
items = []
|
|
238
|
+
if current_parent is not None:
|
|
239
|
+
items.append(PickerItem(
|
|
240
|
+
value="_clear", label="Remove parent link", color=DIM,
|
|
241
|
+
))
|
|
242
|
+
|
|
243
|
+
for task in self.task_manager.get_tasks():
|
|
244
|
+
if task['id'] == task_id:
|
|
245
|
+
continue
|
|
246
|
+
if task.get('status') == 'done':
|
|
247
|
+
continue
|
|
248
|
+
annotation = "(current parent)" if str(task['id']) == str(current_parent) else ""
|
|
249
|
+
items.append(PickerItem(
|
|
250
|
+
value=str(task['id']),
|
|
251
|
+
label=f"#{task['id']} {task['text']}",
|
|
252
|
+
annotation=annotation,
|
|
253
|
+
))
|
|
254
|
+
|
|
255
|
+
if not items:
|
|
256
|
+
print("\n✗ No candidate tasks found")
|
|
257
|
+
return True
|
|
258
|
+
|
|
259
|
+
result = fuzzy_picker(
|
|
260
|
+
items,
|
|
261
|
+
prompt=f"Assign parent for: {current_task['text']}",
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
if result.value is None:
|
|
265
|
+
return True
|
|
266
|
+
|
|
267
|
+
# Strip existing parent: label
|
|
268
|
+
new_labels = [l for l in labels if not l.startswith('parent:')]
|
|
269
|
+
|
|
270
|
+
if result.value != "_clear":
|
|
271
|
+
new_labels.append(f"parent:{result.value}")
|
|
272
|
+
|
|
273
|
+
# Save updated labels
|
|
274
|
+
for task in self.task_manager.tasks:
|
|
275
|
+
if task['id'] == task_id:
|
|
276
|
+
task['labels'] = new_labels
|
|
277
|
+
self.task_manager._save_tasks()
|
|
278
|
+
break
|
|
279
|
+
|
|
280
|
+
if result.value == "_clear":
|
|
281
|
+
print(f"\n{GREEN}✓{RESET} Parent link removed")
|
|
282
|
+
else:
|
|
283
|
+
print(f"\n{GREEN}✓{RESET} Assigned as subtask of #{result.value}")
|
|
284
|
+
|
|
285
|
+
print("\nPress Enter to continue...", end='', flush=True)
|
|
286
|
+
restore_terminal()
|
|
287
|
+
input()
|
|
288
|
+
return True
|
|
@@ -5,9 +5,8 @@ from pathlib import Path
|
|
|
5
5
|
from jot.categories.config import CategoryConfig
|
|
6
6
|
from jot.categories.manager import CategoryManager
|
|
7
7
|
from jot.core.task_manager import TaskManager
|
|
8
|
-
from jot.ui.picker import PickerItem, fuzzy_picker
|
|
8
|
+
from jot.ui.picker import PickerItem, fuzzy_picker, quick_picker
|
|
9
9
|
from jot.ui.styles import RESET, RED
|
|
10
|
-
from jot.ui.input import restore_terminal
|
|
11
10
|
|
|
12
11
|
|
|
13
12
|
class TransferMixin:
|
|
@@ -211,78 +210,55 @@ class TransferMixin:
|
|
|
211
210
|
|
|
212
211
|
cat_config = CategoryConfig(project_dir=project_dir)
|
|
213
212
|
|
|
214
|
-
|
|
215
|
-
for
|
|
213
|
+
items = []
|
|
214
|
+
for display_name, cat_name, is_global in categories:
|
|
216
215
|
if display_name == "default":
|
|
217
|
-
|
|
218
|
-
|
|
216
|
+
items.append(PickerItem(
|
|
217
|
+
value=(None, False),
|
|
218
|
+
label="default",
|
|
219
|
+
annotation="(no category)",
|
|
220
|
+
color=cat_config.get_color('default'),
|
|
221
|
+
))
|
|
219
222
|
else:
|
|
220
|
-
cat_color = cat_config.get_color(cat_name, for_global=is_global)
|
|
221
223
|
scope = "[global]" if is_global else "[local]"
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
return True
|
|
232
|
-
|
|
233
|
-
selected_category = None
|
|
234
|
-
selected_is_global = False
|
|
235
|
-
|
|
236
|
-
try:
|
|
237
|
-
choice_index = int(user_input) - 1
|
|
238
|
-
if 0 <= choice_index < len(categories):
|
|
239
|
-
_, cat_name, is_global = categories[choice_index]
|
|
240
|
-
selected_category = cat_name
|
|
241
|
-
selected_is_global = is_global
|
|
242
|
-
else:
|
|
243
|
-
print("✗ Invalid number")
|
|
244
|
-
return True
|
|
245
|
-
except ValueError:
|
|
246
|
-
found = False
|
|
247
|
-
for display_name, cat_name, is_global in categories:
|
|
248
|
-
if cat_name == user_input or display_name == user_input:
|
|
249
|
-
selected_category = cat_name
|
|
250
|
-
selected_is_global = is_global
|
|
251
|
-
found = True
|
|
252
|
-
break
|
|
253
|
-
if not found:
|
|
254
|
-
print(f"✗ Category '{user_input}' not found")
|
|
255
|
-
return True
|
|
256
|
-
|
|
257
|
-
if selected_is_global:
|
|
258
|
-
dest_tm = TaskManager(
|
|
259
|
-
category=selected_category, is_global=True, project_registry=self.registry
|
|
260
|
-
)
|
|
261
|
-
else:
|
|
262
|
-
dest_tm = TaskManager(
|
|
263
|
-
directory=project_dir,
|
|
264
|
-
category=selected_category,
|
|
265
|
-
is_global=False,
|
|
266
|
-
project_registry=self.registry,
|
|
267
|
-
)
|
|
268
|
-
|
|
269
|
-
dest_tm.add_task(task_text)
|
|
270
|
-
if task_done:
|
|
271
|
-
new_task_id = dest_tm.tasks[-1]['id']
|
|
272
|
-
dest_tm.set_task_status(new_task_id, 'done')
|
|
273
|
-
|
|
274
|
-
self.task_manager.remove_task(task_id)
|
|
275
|
-
|
|
276
|
-
dest_name = selected_category or 'default'
|
|
277
|
-
if selected_is_global:
|
|
278
|
-
print(f"✓ Transferred task to global:{dest_name}")
|
|
279
|
-
else:
|
|
280
|
-
print(f"✓ Transferred task to {dest_name}")
|
|
281
|
-
|
|
282
|
-
print("\nPress Enter to continue...", end='', flush=True)
|
|
283
|
-
input()
|
|
224
|
+
items.append(PickerItem(
|
|
225
|
+
value=(cat_name, is_global),
|
|
226
|
+
label=cat_name,
|
|
227
|
+
annotation=scope,
|
|
228
|
+
color=cat_config.get_color(cat_name, for_global=is_global),
|
|
229
|
+
))
|
|
230
|
+
|
|
231
|
+
result = quick_picker(items, prompt=f"Transfer '{task_text}' to")
|
|
232
|
+
if result.value is None:
|
|
284
233
|
return True
|
|
285
234
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
235
|
+
selected_category, selected_is_global = result.value
|
|
236
|
+
|
|
237
|
+
if selected_is_global:
|
|
238
|
+
dest_tm = TaskManager(
|
|
239
|
+
category=selected_category, is_global=True, project_registry=self.registry
|
|
240
|
+
)
|
|
241
|
+
else:
|
|
242
|
+
dest_tm = TaskManager(
|
|
243
|
+
directory=project_dir,
|
|
244
|
+
category=selected_category,
|
|
245
|
+
is_global=False,
|
|
246
|
+
project_registry=self.registry,
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
dest_tm.add_task(task_text)
|
|
250
|
+
if task_done:
|
|
251
|
+
new_task_id = dest_tm.tasks[-1]['id']
|
|
252
|
+
dest_tm.set_task_status(new_task_id, 'done')
|
|
253
|
+
|
|
254
|
+
self.task_manager.remove_task(task_id)
|
|
255
|
+
|
|
256
|
+
dest_name = selected_category or 'default'
|
|
257
|
+
if selected_is_global:
|
|
258
|
+
print(f"✓ Transferred task to global:{dest_name}")
|
|
259
|
+
else:
|
|
260
|
+
print(f"✓ Transferred task to {dest_name}")
|
|
261
|
+
|
|
262
|
+
print("\nPress Enter to continue...", end='', flush=True)
|
|
263
|
+
input()
|
|
264
|
+
return True
|
|
@@ -61,8 +61,13 @@ class MetadataMixin:
|
|
|
61
61
|
self._save_tasks()
|
|
62
62
|
return True
|
|
63
63
|
|
|
64
|
-
def
|
|
65
|
-
"""
|
|
64
|
+
def set_highlight(self, task_id=None, color=None):
|
|
65
|
+
"""Set highlight color on a task, or clear it.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
task_id: Task to highlight (defaults to current task).
|
|
69
|
+
color: Color name from HIGHLIGHT_COLORS, or None to clear.
|
|
70
|
+
"""
|
|
66
71
|
if task_id is None:
|
|
67
72
|
current = self.get_current_task()
|
|
68
73
|
if not current:
|
|
@@ -71,7 +76,7 @@ class MetadataMixin:
|
|
|
71
76
|
|
|
72
77
|
for task in self.tasks:
|
|
73
78
|
if task['id'] == task_id:
|
|
74
|
-
task['highlight'] =
|
|
79
|
+
task['highlight'] = color if color else False
|
|
75
80
|
self._save_tasks()
|
|
76
81
|
return True
|
|
77
82
|
return False
|
|
@@ -16,7 +16,9 @@ from jot.ui.styles import (
|
|
|
16
16
|
RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE, GRAY, ORANGE, BLACK,
|
|
17
17
|
PRIORITY_COLORS, PRIORITY_SYMBOLS, STATUS_COLORS, STATUS_SYMBOLS,
|
|
18
18
|
ARCHIVE_SYMBOL, ARCHIVE_COLOR,
|
|
19
|
-
BG_YELLOW,
|
|
19
|
+
BG_YELLOW, BG_ORANGE, BG_MAGENTA, BG_GRAY,
|
|
20
|
+
HIGHLIGHT_COLORS, HIGHLIGHT_DEFAULT, HIGHLIGHT_FMT,
|
|
21
|
+
get_terminal_width, strip_ansi, visible_len,
|
|
20
22
|
)
|
|
21
23
|
|
|
22
24
|
__all__ = [
|
|
@@ -34,7 +36,8 @@ __all__ = [
|
|
|
34
36
|
'RED', 'GREEN', 'YELLOW', 'BLUE', 'MAGENTA', 'CYAN', 'WHITE', 'GRAY', 'ORANGE', 'BLACK',
|
|
35
37
|
'PRIORITY_COLORS', 'PRIORITY_SYMBOLS', 'STATUS_COLORS', 'STATUS_SYMBOLS',
|
|
36
38
|
'ARCHIVE_SYMBOL', 'ARCHIVE_COLOR',
|
|
37
|
-
'BG_YELLOW', 'HIGHLIGHT_FMT',
|
|
39
|
+
'BG_YELLOW', 'HIGHLIGHT_COLORS', 'HIGHLIGHT_DEFAULT', 'HIGHLIGHT_FMT',
|
|
40
|
+
'get_terminal_width', 'strip_ansi', 'visible_len',
|
|
38
41
|
# Picker
|
|
39
42
|
'PickerItem', 'PickerResult', 'fuzzy_picker', 'quick_picker',
|
|
40
43
|
]
|
|
@@ -5,6 +5,7 @@ from jot.categories.manager import CategoryManager
|
|
|
5
5
|
from jot.categories.config import CategoryConfig
|
|
6
6
|
from jot.ui.styles import (
|
|
7
7
|
RESET, BOLD, DIM, CYAN, YELLOW,
|
|
8
|
+
get_terminal_width,
|
|
8
9
|
)
|
|
9
10
|
|
|
10
11
|
|
|
@@ -15,7 +16,7 @@ def display_archive(project_dir, filter_category=None):
|
|
|
15
16
|
project_dir, cat_manager, filter_category)
|
|
16
17
|
|
|
17
18
|
print(f"\n{BOLD}Archived Tasks{RESET}")
|
|
18
|
-
print("=" *
|
|
19
|
+
print("=" * get_terminal_width())
|
|
19
20
|
|
|
20
21
|
total_archived = 0
|
|
21
22
|
for cat_name, tm in managers_to_check:
|
|
@@ -32,7 +33,7 @@ def display_archive(project_dir, filter_category=None):
|
|
|
32
33
|
|
|
33
34
|
if total_archived == 0:
|
|
34
35
|
print(f"\n{DIM}No archived tasks{RESET}")
|
|
35
|
-
print("=" *
|
|
36
|
+
print("=" * get_terminal_width() + "\n")
|
|
36
37
|
|
|
37
38
|
|
|
38
39
|
def _build_archive_managers(project_dir, cat_manager, filter_category):
|
|
@@ -74,7 +75,7 @@ def display_all_categories_view(
|
|
|
74
75
|
show_archived: Whether to include archived tasks in the view.
|
|
75
76
|
"""
|
|
76
77
|
print(f"\n{CYAN}{BOLD}ALL CATEGORIES VIEW{RESET}")
|
|
77
|
-
print("-" *
|
|
78
|
+
print("-" * get_terminal_width())
|
|
78
79
|
|
|
79
80
|
cat_config = CategoryConfig(project_dir=project_dir)
|
|
80
81
|
task_num = 1
|
|
@@ -110,7 +111,7 @@ def display_all_categories_view(
|
|
|
110
111
|
f"\n{DIM}Total: {total_tasks} tasks across "
|
|
111
112
|
f"{len(all_cat_data)} categories{RESET}"
|
|
112
113
|
)
|
|
113
|
-
print("-" *
|
|
114
|
+
print("-" * get_terminal_width())
|
|
114
115
|
print(
|
|
115
116
|
f"{CYAN}Press Shift+L or ESC to return to "
|
|
116
117
|
f"single category view{RESET}"
|
|
@@ -6,6 +6,7 @@ from jot.core.constants import (
|
|
|
6
6
|
)
|
|
7
7
|
from jot.ui.styles import (
|
|
8
8
|
RESET, BOLD, DIM, CYAN, GREEN, YELLOW,
|
|
9
|
+
get_terminal_width,
|
|
9
10
|
)
|
|
10
11
|
|
|
11
12
|
|
|
@@ -53,7 +54,7 @@ def _render_multiselect(selected_tasks):
|
|
|
53
54
|
f"{BOLD}Shift+↑/↓{RESET}: select while moving | "
|
|
54
55
|
f"{BOLD}A{RESET}: select all | {BOLD}N{RESET}: select none"
|
|
55
56
|
)
|
|
56
|
-
print("-" *
|
|
57
|
+
print("-" * get_terminal_width())
|
|
57
58
|
|
|
58
59
|
|
|
59
60
|
def _render_fuzzy_search(
|
|
@@ -88,7 +89,7 @@ def _render_fuzzy_search(
|
|
|
88
89
|
f"{BOLD}Ctrl+A{RESET}: toggle archives | "
|
|
89
90
|
f"{BOLD}ESC{RESET}: cancel"
|
|
90
91
|
)
|
|
91
|
-
print("-" *
|
|
92
|
+
print("-" * get_terminal_width())
|
|
92
93
|
|
|
93
94
|
|
|
94
95
|
def _render_command():
|
|
@@ -104,7 +105,7 @@ def _render_command():
|
|
|
104
105
|
f"{BOLD}(M){RESET}ove | {BOLD}(r){RESET}efresh | "
|
|
105
106
|
f"{BOLD}(h){RESET}elp | {BOLD}(q){RESET}uit"
|
|
106
107
|
)
|
|
107
|
-
print("-" *
|
|
108
|
+
print("-" * get_terminal_width())
|
|
108
109
|
print("Command: ", end='', flush=True)
|
|
109
110
|
|
|
110
111
|
|
|
@@ -130,4 +131,4 @@ def render_archived_section(archived_tasks):
|
|
|
130
131
|
f"{DIM} [{status}] {task['id']}. "
|
|
131
132
|
f"{day_prefix}{task['text']}{tally_suffix}{RESET}"
|
|
132
133
|
)
|
|
133
|
-
print(f"{DIM}-" *
|
|
134
|
+
print(f"{DIM}" + "-" * get_terminal_width() + RESET)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Display functions for help and keyboard shortcuts."""
|
|
2
2
|
|
|
3
|
-
from jot.ui.styles import RESET, BOLD, DIM, CYAN, GREEN, YELLOW
|
|
3
|
+
from jot.ui.styles import RESET, BOLD, DIM, CYAN, GREEN, YELLOW, get_terminal_width
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
def display_categorized_shortcuts():
|
|
@@ -42,7 +42,9 @@ def display_categorized_shortcuts():
|
|
|
42
42
|
("Shift+Y", "Toggle today filter"),
|
|
43
43
|
("Shift+D", "Mark current as done"),
|
|
44
44
|
("Shift+J", "Mark as agent task"),
|
|
45
|
-
("*", "
|
|
45
|
+
("*", "Highlight color picker"),
|
|
46
|
+
("~", "Quick highlight toggle"),
|
|
47
|
+
("Ctrl+L", "Assign parent (link subtask)"),
|
|
46
48
|
],
|
|
47
49
|
"Multi-Select Mode": [
|
|
48
50
|
("Shift+V", "Enter multi-select mode"),
|
|
@@ -79,22 +81,23 @@ def display_categorized_shortcuts():
|
|
|
79
81
|
],
|
|
80
82
|
}
|
|
81
83
|
|
|
84
|
+
w = get_terminal_width()
|
|
82
85
|
print(f"\n{BOLD}Keyboard Shortcuts{RESET}")
|
|
83
|
-
print("=" *
|
|
86
|
+
print("=" * w)
|
|
84
87
|
|
|
85
88
|
for category, items in shortcuts.items():
|
|
86
89
|
print(f"\n{CYAN}{category}:{RESET}")
|
|
87
90
|
for key, description in items:
|
|
88
91
|
print(f" {YELLOW}{key:15}{RESET} {description}")
|
|
89
92
|
|
|
90
|
-
print("\n" + "=" *
|
|
93
|
+
print("\n" + "=" * w + "\n")
|
|
91
94
|
|
|
92
95
|
|
|
93
96
|
def display_help():
|
|
94
97
|
"""Display comprehensive help information."""
|
|
95
98
|
help_text = f"""
|
|
96
99
|
{BOLD}Jott - Simple Interactive Task List{RESET}
|
|
97
|
-
{DIM}Version 0.5.
|
|
100
|
+
{DIM}Version 0.5.2{RESET}
|
|
98
101
|
|
|
99
102
|
{BOLD}BASIC USAGE:{RESET}
|
|
100
103
|
jott Start interactive task manager (current project)
|
|
@@ -146,7 +149,9 @@ def display_help():
|
|
|
146
149
|
{CYAN}Shift+Y{RESET} Toggle today filter
|
|
147
150
|
{CYAN}Shift+D{RESET} Mark current task as done
|
|
148
151
|
{CYAN}Shift+J{RESET} Mark task for agent/AI assistance
|
|
149
|
-
{CYAN}*{RESET}
|
|
152
|
+
{CYAN}*{RESET} Highlight color picker (6 colors)
|
|
153
|
+
{CYAN}~{RESET} Quick highlight toggle (default color)
|
|
154
|
+
{CYAN}Ctrl+L{RESET} Assign parent task (subtask link)
|
|
150
155
|
{CYAN}Shift+E{RESET} Execute keyword action for task
|
|
151
156
|
|
|
152
157
|
{BOLD}MULTI-SELECT MODE:{RESET}
|
|
@@ -4,6 +4,7 @@ from jot.core.task_manager import TaskManager
|
|
|
4
4
|
from jot.categories.manager import CategoryManager
|
|
5
5
|
from jot.ui.styles import (
|
|
6
6
|
RESET, BOLD, DIM, CYAN, GREEN, YELLOW,
|
|
7
|
+
get_terminal_width,
|
|
7
8
|
)
|
|
8
9
|
|
|
9
10
|
|
|
@@ -16,12 +17,13 @@ def display_all_projects(registry, show_categories=False):
|
|
|
16
17
|
"projects in ~/projects/")
|
|
17
18
|
return
|
|
18
19
|
|
|
19
|
-
|
|
20
|
+
w = get_terminal_width()
|
|
21
|
+
print("\n" + "=" * w)
|
|
20
22
|
if show_categories:
|
|
21
23
|
print(f"{BOLD}ALL PROJECTS OVERVIEW (with categories){RESET}")
|
|
22
24
|
else:
|
|
23
25
|
print(f"{BOLD}ALL PROJECTS OVERVIEW{RESET}")
|
|
24
|
-
print("=" *
|
|
26
|
+
print("=" * w)
|
|
25
27
|
|
|
26
28
|
for project_name in sorted(projects.keys()):
|
|
27
29
|
project_path = projects[project_name]
|
|
@@ -40,13 +42,14 @@ def display_all_projects(registry, show_categories=False):
|
|
|
40
42
|
f"{DIM}(error loading: {e}){RESET}")
|
|
41
43
|
print(f" {DIM}{project_path}{RESET}")
|
|
42
44
|
|
|
43
|
-
|
|
45
|
+
w = get_terminal_width()
|
|
46
|
+
print("\n" + "=" * w)
|
|
44
47
|
print(
|
|
45
48
|
f"Total: {len(projects)} projects | "
|
|
46
49
|
f"Use {CYAN}jot -p <project>{RESET} to switch | "
|
|
47
50
|
f"{CYAN}Shift+P{RESET} in jot UI"
|
|
48
51
|
)
|
|
49
|
-
print("=" *
|
|
52
|
+
print("=" * w + "\n")
|
|
50
53
|
|
|
51
54
|
|
|
52
55
|
def _display_project_with_categories(project_name, project_path):
|
|
@@ -116,8 +119,9 @@ def display_category_stats(project_dir):
|
|
|
116
119
|
local_categories = cat_manager.discover_categories()
|
|
117
120
|
global_categories = CategoryManager.discover_global_categories()
|
|
118
121
|
|
|
122
|
+
w = get_terminal_width()
|
|
119
123
|
print(f"\n{BOLD}Category Statistics{RESET}")
|
|
120
|
-
print("=" *
|
|
124
|
+
print("=" * w)
|
|
121
125
|
|
|
122
126
|
tm_default = TaskManager(directory=project_dir)
|
|
123
127
|
print(f"\n{CYAN}DEFAULT (uncategorized){RESET}")
|
|
@@ -148,4 +152,4 @@ def display_category_stats(project_dir):
|
|
|
148
152
|
f"{YELLOW}⚠ Global categories: "
|
|
149
153
|
f"{len(global_categories)}/{CategoryManager.MAX_CATEGORIES}{RESET}"
|
|
150
154
|
)
|
|
151
|
-
print("=" *
|
|
155
|
+
print("=" * w + "\n")
|