jott-cli 0.5.5__tar.gz → 0.6.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.
- {jott_cli-0.5.5/jott_cli.egg-info → jott_cli-0.6.0}/PKG-INFO +21 -20
- {jott_cli-0.5.5 → jott_cli-0.6.0}/README.md +20 -19
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/__init__.py +1 -1
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/_app_navigation_mixin.py +0 -36
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/_dispatch_mixin.py +90 -27
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/app.py +0 -13
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/commands/_ai_analysis_mixin.py +2 -2
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/commands/_core_mixin.py +28 -4
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/commands/_metadata_mixin.py +7 -3
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/core/_crud_mixin.py +12 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/ui/display_help.py +35 -58
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/ui/display_projects.py +1 -1
- {jott_cli-0.5.5 → jott_cli-0.6.0/jott_cli.egg-info}/PKG-INFO +21 -20
- {jott_cli-0.5.5 → jott_cli-0.6.0}/pyproject.toml +1 -1
- {jott_cli-0.5.5 → jott_cli-0.6.0}/tests/test_command_handler.py +64 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/tests/test_dispatch.py +2 -8
- {jott_cli-0.5.5 → jott_cli-0.6.0}/LICENSE +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/categories/__init__.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/categories/config.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/categories/manager.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/categories/templates.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/cli/__init__.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/cli/archive.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/cli/config.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/cli/views.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/commands/__init__.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/commands/_ai_suggest_mixin.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/commands/_audio_timer_mixin.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/commands/_bulk_mixin.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/commands/_context_mixin.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/commands/_gcal_mixin.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/commands/_notes_mixin.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/commands/_transfer_mixin.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/commands/_web_clipboard_mixin.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/commands/handler.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/core/__init__.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/core/_age_backlog_mixin.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/core/_compress_mixin.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/core/_delete_mixin.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/core/_export_mixin.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/core/_id_migration_mixin.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/core/_metadata_mixin.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/core/_navigation_mixin.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/core/_persistence_mixin.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/core/_subtask_mixin.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/core/archive_manager.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/core/constants.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/core/id_manager.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/core/task_manager.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/integrations/__init__.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/integrations/gcal/__init__.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/integrations/gcal/account_manager.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/integrations/gcal/auth.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/integrations/gcal/events.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/integrations/keywords/__init__.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/integrations/keywords/_config_mixin.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/integrations/keywords/_handlers_mixin.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/integrations/keywords/handler.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/mcp/__init__.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/mcp/handlers.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/mcp/schemas.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/mcp/server.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/projects/__init__.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/projects/backup.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/projects/registry.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/ui/__init__.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/ui/display.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/ui/display_archive.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/ui/display_footer.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/ui/display_tasks.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/ui/formatting.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/ui/input.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/ui/picker.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/ui/rendering.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/ui/styles.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/utils/__init__.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/utils/date_utils.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/utils/text_utils.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/utils/validation.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jott_cli.egg-info/SOURCES.txt +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jott_cli.egg-info/dependency_links.txt +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jott_cli.egg-info/entry_points.txt +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jott_cli.egg-info/requires.txt +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/jott_cli.egg-info/top_level.txt +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/setup.cfg +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/setup.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/tests/test_edit_edge_cases.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/tests/test_fuzzy_search.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/tests/test_gcal_notes.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/tests/test_highlight.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/tests/test_input.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/tests/test_jot.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/tests/test_picker.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/tests/test_styles.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/tests/test_subtask_notes.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/tests/test_terminal_wrap.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/tests/test_today_filter.py +0 -0
- {jott_cli-0.5.5 → jott_cli-0.6.0}/tests/test_transfer_subtasks.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: jott-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
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>
|
|
@@ -62,12 +62,12 @@ Dynamic: license-file
|
|
|
62
62
|
- **Dual-mode interface**: Quick-add mode ↔ Command mode (ESC to switch)
|
|
63
63
|
- **Cross-project task routing**: `jott myproject "task"` from anywhere
|
|
64
64
|
- **Keyword automation**: Tasks starting with `bullet:`, `gcal:`, `ai:`, `analyze:` trigger actions
|
|
65
|
-
- **AI integration**: Claude Code integration
|
|
65
|
+
- **AI integration**: Claude Code integration via the `analyze:` keyword for task analysis
|
|
66
66
|
- **Google Calendar sync**: Bidirectional sync (export tasks, import events)
|
|
67
67
|
- **Text-to-speech**: Read all tasks aloud with Shift+2
|
|
68
68
|
- **Fuzzy search**: Filter tasks quickly with `Ctrl+/`
|
|
69
69
|
- **Fuzzy project picker**: Interactive search when moving tasks between projects
|
|
70
|
-
- **
|
|
70
|
+
- **Leader chord system**: `.` prefix for 20+ shortcuts (e.g., `.m` move, `.n` notes)
|
|
71
71
|
- **Category system**: Up to 12 categories per project
|
|
72
72
|
- **Task metadata**: Priority, status, day-of-week assignment, notes
|
|
73
73
|
- **Backup system**: Organized monthly backups
|
|
@@ -121,34 +121,35 @@ Just type and press Enter:
|
|
|
121
121
|
### Keyboard Shortcuts
|
|
122
122
|
|
|
123
123
|
**Task Management:**
|
|
124
|
-
- `
|
|
125
|
-
- `
|
|
126
|
-
- `
|
|
127
|
-
- `
|
|
124
|
+
- `.n` - Edit task notes
|
|
125
|
+
- `Ctrl+D` - Delete current task
|
|
126
|
+
- `.a` - Toggle archived tasks
|
|
127
|
+
- `.f` - Toggle inline notes display
|
|
128
|
+
- `.u` - Toggle ALL CAPS
|
|
128
129
|
- `Shift+4` - AI-powered task suggestion
|
|
129
130
|
|
|
130
131
|
**Scheduling:**
|
|
131
|
-
- `
|
|
132
|
-
- `
|
|
133
|
-
- `
|
|
132
|
+
- `.w` - Assign day of week
|
|
133
|
+
- `.p` - Set priority
|
|
134
|
+
- `.x` - Set status
|
|
134
135
|
|
|
135
136
|
**AI Integration:**
|
|
136
|
-
- `
|
|
137
|
-
- `
|
|
138
|
-
- `Shift+J` - Mark as agent task
|
|
137
|
+
- `analyze:` keyword - Analyze task with Claude Code (plan mode)
|
|
138
|
+
- `.j` - Mark as agent task
|
|
139
139
|
|
|
140
140
|
**Integrations:**
|
|
141
141
|
- `Shift+2` - Read tasks aloud (TTS)
|
|
142
142
|
- `Shift+3` - Re-authenticate Google Calendar
|
|
143
|
-
-
|
|
144
|
-
- `
|
|
145
|
-
- `
|
|
143
|
+
- `.G` - Export to Google Calendar (single or bulk with time tags)
|
|
144
|
+
- `.i` - Import from Google Calendar
|
|
145
|
+
- `.b` - Start priority timer (12-minute focus session)
|
|
146
146
|
|
|
147
147
|
**Navigation & System:**
|
|
148
148
|
- `↑/↓` or `Ctrl+N/P` - Navigate tasks
|
|
149
149
|
- `Shift+↑/↓` - Reorder tasks
|
|
150
|
-
- `Ctrl
|
|
151
|
-
- `
|
|
150
|
+
- `Ctrl+F` - Fuzzy search mode
|
|
151
|
+
- `.z` - Switch project
|
|
152
|
+
- `.l` - Toggle all categories view
|
|
152
153
|
- `Tab` - Cycle through categories
|
|
153
154
|
- `Ctrl+R` - Register current project
|
|
154
155
|
- `Ctrl+U` - Open URLs from task text
|
|
@@ -170,7 +171,7 @@ Organize tasks with categories (up to 12 per project):
|
|
|
170
171
|
|
|
171
172
|
```bash
|
|
172
173
|
# Switch to category
|
|
173
|
-
|
|
174
|
+
Ctrl+C
|
|
174
175
|
|
|
175
176
|
# Create category-specific tasks
|
|
176
177
|
jott --category=bugs "Fix login redirect"
|
|
@@ -202,7 +203,7 @@ jott myproject "task text"
|
|
|
202
203
|
1. Enable Google Calendar API
|
|
203
204
|
2. Download credentials
|
|
204
205
|
3. Save as `~/.jot/gcal-accounts/default/credentials.json`
|
|
205
|
-
4. Use
|
|
206
|
+
4. Use `.G` to export tasks
|
|
206
207
|
|
|
207
208
|
**Re-authentication:**
|
|
208
209
|
- Press `Shift+3` to force re-authentication (if token expires or switching accounts)
|
|
@@ -12,12 +12,12 @@
|
|
|
12
12
|
- **Dual-mode interface**: Quick-add mode ↔ Command mode (ESC to switch)
|
|
13
13
|
- **Cross-project task routing**: `jott myproject "task"` from anywhere
|
|
14
14
|
- **Keyword automation**: Tasks starting with `bullet:`, `gcal:`, `ai:`, `analyze:` trigger actions
|
|
15
|
-
- **AI integration**: Claude Code integration
|
|
15
|
+
- **AI integration**: Claude Code integration via the `analyze:` keyword for task analysis
|
|
16
16
|
- **Google Calendar sync**: Bidirectional sync (export tasks, import events)
|
|
17
17
|
- **Text-to-speech**: Read all tasks aloud with Shift+2
|
|
18
18
|
- **Fuzzy search**: Filter tasks quickly with `Ctrl+/`
|
|
19
19
|
- **Fuzzy project picker**: Interactive search when moving tasks between projects
|
|
20
|
-
- **
|
|
20
|
+
- **Leader chord system**: `.` prefix for 20+ shortcuts (e.g., `.m` move, `.n` notes)
|
|
21
21
|
- **Category system**: Up to 12 categories per project
|
|
22
22
|
- **Task metadata**: Priority, status, day-of-week assignment, notes
|
|
23
23
|
- **Backup system**: Organized monthly backups
|
|
@@ -71,34 +71,35 @@ Just type and press Enter:
|
|
|
71
71
|
### Keyboard Shortcuts
|
|
72
72
|
|
|
73
73
|
**Task Management:**
|
|
74
|
-
- `
|
|
75
|
-
- `
|
|
76
|
-
- `
|
|
77
|
-
- `
|
|
74
|
+
- `.n` - Edit task notes
|
|
75
|
+
- `Ctrl+D` - Delete current task
|
|
76
|
+
- `.a` - Toggle archived tasks
|
|
77
|
+
- `.f` - Toggle inline notes display
|
|
78
|
+
- `.u` - Toggle ALL CAPS
|
|
78
79
|
- `Shift+4` - AI-powered task suggestion
|
|
79
80
|
|
|
80
81
|
**Scheduling:**
|
|
81
|
-
- `
|
|
82
|
-
- `
|
|
83
|
-
- `
|
|
82
|
+
- `.w` - Assign day of week
|
|
83
|
+
- `.p` - Set priority
|
|
84
|
+
- `.x` - Set status
|
|
84
85
|
|
|
85
86
|
**AI Integration:**
|
|
86
|
-
- `
|
|
87
|
-
- `
|
|
88
|
-
- `Shift+J` - Mark as agent task
|
|
87
|
+
- `analyze:` keyword - Analyze task with Claude Code (plan mode)
|
|
88
|
+
- `.j` - Mark as agent task
|
|
89
89
|
|
|
90
90
|
**Integrations:**
|
|
91
91
|
- `Shift+2` - Read tasks aloud (TTS)
|
|
92
92
|
- `Shift+3` - Re-authenticate Google Calendar
|
|
93
|
-
-
|
|
94
|
-
- `
|
|
95
|
-
- `
|
|
93
|
+
- `.G` - Export to Google Calendar (single or bulk with time tags)
|
|
94
|
+
- `.i` - Import from Google Calendar
|
|
95
|
+
- `.b` - Start priority timer (12-minute focus session)
|
|
96
96
|
|
|
97
97
|
**Navigation & System:**
|
|
98
98
|
- `↑/↓` or `Ctrl+N/P` - Navigate tasks
|
|
99
99
|
- `Shift+↑/↓` - Reorder tasks
|
|
100
|
-
- `Ctrl
|
|
101
|
-
- `
|
|
100
|
+
- `Ctrl+F` - Fuzzy search mode
|
|
101
|
+
- `.z` - Switch project
|
|
102
|
+
- `.l` - Toggle all categories view
|
|
102
103
|
- `Tab` - Cycle through categories
|
|
103
104
|
- `Ctrl+R` - Register current project
|
|
104
105
|
- `Ctrl+U` - Open URLs from task text
|
|
@@ -120,7 +121,7 @@ Organize tasks with categories (up to 12 per project):
|
|
|
120
121
|
|
|
121
122
|
```bash
|
|
122
123
|
# Switch to category
|
|
123
|
-
|
|
124
|
+
Ctrl+C
|
|
124
125
|
|
|
125
126
|
# Create category-specific tasks
|
|
126
127
|
jott --category=bugs "Fix login redirect"
|
|
@@ -152,7 +153,7 @@ jott myproject "task text"
|
|
|
152
153
|
1. Enable Google Calendar API
|
|
153
154
|
2. Download credentials
|
|
154
155
|
3. Save as `~/.jot/gcal-accounts/default/credentials.json`
|
|
155
|
-
4. Use
|
|
156
|
+
4. Use `.G` to export tasks
|
|
156
157
|
|
|
157
158
|
**Re-authentication:**
|
|
158
159
|
- Press `Shift+3` to force re-authentication (if token expires or switching accounts)
|
|
@@ -7,7 +7,7 @@ import importlib.util
|
|
|
7
7
|
import os
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
|
|
10
|
-
__version__ = "0.
|
|
10
|
+
__version__ = "0.6.0"
|
|
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.
|
|
@@ -8,7 +8,6 @@ from jot.commands import CommandHandler
|
|
|
8
8
|
from jot.core.constants import MODE_QUICK_ADD
|
|
9
9
|
from jot.core.task_manager import TaskManager
|
|
10
10
|
from jot.ui.styles import RESET, CYAN
|
|
11
|
-
from jot.utils.date_utils import get_today_day_name
|
|
12
11
|
|
|
13
12
|
|
|
14
13
|
class AppNavigationMixin:
|
|
@@ -37,41 +36,6 @@ class AppNavigationMixin:
|
|
|
37
36
|
return True
|
|
38
37
|
return False
|
|
39
38
|
|
|
40
|
-
def _handle_toggle(self, key):
|
|
41
|
-
"""Process shared toggle keys O/Y/F. Returns True if
|
|
42
|
-
the key was a toggle key."""
|
|
43
|
-
st = self.state
|
|
44
|
-
|
|
45
|
-
toggle_map = {
|
|
46
|
-
'O': ('sort_by_day', None),
|
|
47
|
-
'Y': ('show_today_only', None),
|
|
48
|
-
'F': ('show_notes_inline', 'inline notes'),
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if key not in toggle_map:
|
|
52
|
-
return False
|
|
53
|
-
|
|
54
|
-
attr, label = toggle_map[key]
|
|
55
|
-
|
|
56
|
-
if key == 'O':
|
|
57
|
-
st.sort_by_day = not st.sort_by_day
|
|
58
|
-
msg = "Sorting by day" if st.sort_by_day else "Normal order"
|
|
59
|
-
elif key == 'Y':
|
|
60
|
-
st.show_today_only = not st.show_today_only
|
|
61
|
-
today = get_today_day_name()
|
|
62
|
-
msg = (f"Showing only {today} tasks"
|
|
63
|
-
if st.show_today_only else "Showing all tasks")
|
|
64
|
-
else:
|
|
65
|
-
current = getattr(st, attr)
|
|
66
|
-
setattr(st, attr, not current)
|
|
67
|
-
new_val = not current
|
|
68
|
-
status = "Showing" if new_val else "Hiding"
|
|
69
|
-
msg = f"{status} {label}"
|
|
70
|
-
|
|
71
|
-
print(f"\n{CYAN}\u2713 {msg}{RESET}")
|
|
72
|
-
time.sleep(0.3)
|
|
73
|
-
st.input_buffer = ""
|
|
74
|
-
return True
|
|
75
39
|
|
|
76
40
|
def _reload_task_manager(self, task_manager):
|
|
77
41
|
"""Replace the active TaskManager and CommandHandler."""
|
|
@@ -8,6 +8,7 @@ from jot.core.constants import (
|
|
|
8
8
|
)
|
|
9
9
|
from jot.ui.input import get_key
|
|
10
10
|
from jot.ui.styles import RESET, CYAN
|
|
11
|
+
from jot.utils.date_utils import get_today_day_name
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
class DispatchMixin:
|
|
@@ -34,17 +35,7 @@ class DispatchMixin:
|
|
|
34
35
|
self._handle_switch_category()
|
|
35
36
|
return
|
|
36
37
|
|
|
37
|
-
if key == 'Z':
|
|
38
|
-
self._handle_switch_project()
|
|
39
|
-
return
|
|
40
38
|
|
|
41
|
-
if key == 'L':
|
|
42
|
-
should_toggle, new_mode = (
|
|
43
|
-
self.command_handler.toggle_all_categories_view(st.mode))
|
|
44
|
-
if should_toggle:
|
|
45
|
-
st.mode = new_mode
|
|
46
|
-
st.input_buffer = ""
|
|
47
|
-
return
|
|
48
39
|
|
|
49
40
|
if key == '=':
|
|
50
41
|
self.task_manager.sort_by_priority()
|
|
@@ -57,15 +48,6 @@ class DispatchMixin:
|
|
|
57
48
|
st.input_buffer = ""
|
|
58
49
|
return
|
|
59
50
|
|
|
60
|
-
if self._handle_toggle(key):
|
|
61
|
-
return
|
|
62
|
-
|
|
63
|
-
if key == 'V':
|
|
64
|
-
st.mode = MODE_MULTISELECT
|
|
65
|
-
st.selected_tasks.clear()
|
|
66
|
-
st.input_buffer = ""
|
|
67
|
-
return
|
|
68
|
-
|
|
69
51
|
if key == '\x06': # Ctrl+F
|
|
70
52
|
st.mode = MODE_FUZZY_SEARCH
|
|
71
53
|
st.search_buffer = ""
|
|
@@ -153,10 +135,6 @@ class DispatchMixin:
|
|
|
153
135
|
time.sleep(0.3)
|
|
154
136
|
return
|
|
155
137
|
|
|
156
|
-
if key in ('O', 'Y'):
|
|
157
|
-
self._handle_toggle(key)
|
|
158
|
-
return
|
|
159
|
-
|
|
160
138
|
if key:
|
|
161
139
|
st.running = self.command_handler.handle(key)
|
|
162
140
|
|
|
@@ -213,9 +191,6 @@ class DispatchMixin:
|
|
|
213
191
|
st.selected_tasks.add(current_task['id'])
|
|
214
192
|
return
|
|
215
193
|
|
|
216
|
-
if key == 'B':
|
|
217
|
-
self._multiselect_bulk_action()
|
|
218
|
-
return
|
|
219
194
|
|
|
220
195
|
def _multiselect_bulk_action(self):
|
|
221
196
|
"""Process bulk action menu in multiselect mode."""
|
|
@@ -324,6 +299,88 @@ class DispatchMixin:
|
|
|
324
299
|
time.sleep(0.3)
|
|
325
300
|
st.input_buffer = ""
|
|
326
301
|
return
|
|
302
|
+
if second == 'G':
|
|
303
|
+
self.command_handler.export_to_gcal()
|
|
304
|
+
st.input_buffer = ""
|
|
305
|
+
return
|
|
306
|
+
if second == 'f':
|
|
307
|
+
st.show_notes_inline = not st.show_notes_inline
|
|
308
|
+
status = "Showing" if st.show_notes_inline else "Hiding"
|
|
309
|
+
print(f"\n{CYAN}\u2713 {status} inline notes{RESET}")
|
|
310
|
+
time.sleep(0.3)
|
|
311
|
+
st.input_buffer = ""
|
|
312
|
+
return
|
|
313
|
+
if second == 'k':
|
|
314
|
+
self.command_handler.copy_task_to_project()
|
|
315
|
+
st.input_buffer = ""
|
|
316
|
+
return
|
|
317
|
+
if second == 'h':
|
|
318
|
+
self.command_handler.set_priority_high()
|
|
319
|
+
st.input_buffer = ""
|
|
320
|
+
return
|
|
321
|
+
if second == 'p':
|
|
322
|
+
self.command_handler.set_priority()
|
|
323
|
+
st.input_buffer = ""
|
|
324
|
+
return
|
|
325
|
+
if second == 'm':
|
|
326
|
+
self.command_handler.move_task_to_project()
|
|
327
|
+
st.input_buffer = ""
|
|
328
|
+
return
|
|
329
|
+
if second == 'u':
|
|
330
|
+
self.command_handler.toggle_caps()
|
|
331
|
+
st.input_buffer = ""
|
|
332
|
+
return
|
|
333
|
+
if second == 'e':
|
|
334
|
+
self.command_handler.trigger_keyword_action()
|
|
335
|
+
st.input_buffer = ""
|
|
336
|
+
return
|
|
337
|
+
if second == 'b':
|
|
338
|
+
self.command_handler.start_priority_timer()
|
|
339
|
+
st.input_buffer = ""
|
|
340
|
+
return
|
|
341
|
+
if second == 'j':
|
|
342
|
+
self.command_handler.set_agent_task()
|
|
343
|
+
st.input_buffer = ""
|
|
344
|
+
return
|
|
345
|
+
if second == 'l':
|
|
346
|
+
should_toggle, new_mode = (
|
|
347
|
+
self.command_handler.toggle_all_categories_view(st.mode))
|
|
348
|
+
if should_toggle:
|
|
349
|
+
st.mode = new_mode
|
|
350
|
+
st.input_buffer = ""
|
|
351
|
+
return
|
|
352
|
+
if second == 'n':
|
|
353
|
+
self.command_handler.edit_task_notes()
|
|
354
|
+
st.input_buffer = ""
|
|
355
|
+
return
|
|
356
|
+
if second == 'o':
|
|
357
|
+
st.sort_by_day = not st.sort_by_day
|
|
358
|
+
msg = "Sorting by day" if st.sort_by_day else "Normal order"
|
|
359
|
+
print(f"\n{CYAN}\u2713 {msg}{RESET}")
|
|
360
|
+
time.sleep(0.3)
|
|
361
|
+
st.input_buffer = ""
|
|
362
|
+
return
|
|
363
|
+
if second == 'w':
|
|
364
|
+
self.command_handler.assign_day()
|
|
365
|
+
st.input_buffer = ""
|
|
366
|
+
return
|
|
367
|
+
if second == 'x':
|
|
368
|
+
self.command_handler.set_status()
|
|
369
|
+
st.input_buffer = ""
|
|
370
|
+
return
|
|
371
|
+
if second == 'y':
|
|
372
|
+
st.show_today_only = not st.show_today_only
|
|
373
|
+
today = get_today_day_name()
|
|
374
|
+
msg = (f"Showing only {today} tasks"
|
|
375
|
+
if st.show_today_only else "Showing all tasks")
|
|
376
|
+
print(f"\n{CYAN}\u2713 {msg}{RESET}")
|
|
377
|
+
time.sleep(0.3)
|
|
378
|
+
st.input_buffer = ""
|
|
379
|
+
return
|
|
380
|
+
if second == 'z':
|
|
381
|
+
self._handle_switch_project()
|
|
382
|
+
st.input_buffer = ""
|
|
383
|
+
return
|
|
327
384
|
# Not a chord — treat '.' as normal input
|
|
328
385
|
st.input_buffer += '.'
|
|
329
386
|
if second and second != '.':
|
|
@@ -398,10 +455,16 @@ class DispatchMixin:
|
|
|
398
455
|
"""Handle a keypress in ALL_CATEGORIES mode."""
|
|
399
456
|
st = self.state
|
|
400
457
|
|
|
401
|
-
if key == '\x1b'
|
|
458
|
+
if key == '\x1b':
|
|
402
459
|
st.mode = MODE_QUICK_ADD
|
|
403
460
|
return
|
|
404
461
|
|
|
462
|
+
if key == '.':
|
|
463
|
+
second = get_key(timeout=0.5)
|
|
464
|
+
if second == 'l':
|
|
465
|
+
st.mode = MODE_QUICK_ADD
|
|
466
|
+
return
|
|
467
|
+
|
|
405
468
|
filtered = (
|
|
406
469
|
tasks_to_display
|
|
407
470
|
if st.show_today_only or st.collapsed_parents
|
|
@@ -56,24 +56,11 @@ class App(DispatchMixin, AppNavigationMixin):
|
|
|
56
56
|
"""Interactive event loop for the jot TUI."""
|
|
57
57
|
|
|
58
58
|
_QUICK_ADD_SIMPLE = {
|
|
59
|
-
'M': 'move_task_to_project',
|
|
60
|
-
'\x0b': 'copy_task_to_project', # Ctrl+K
|
|
61
59
|
'\x14': 'transfer_task_to_category', # Ctrl+T
|
|
62
60
|
'\x13': 'sync_subtasks', # Ctrl+S
|
|
63
61
|
'\x04': 'delete_current', # Ctrl+D
|
|
64
|
-
'W': 'assign_day',
|
|
65
|
-
'P': 'set_priority',
|
|
66
|
-
'H': 'set_priority_high',
|
|
67
|
-
'X': 'set_status',
|
|
68
|
-
'G': 'export_to_gcal',
|
|
69
|
-
'E': 'trigger_keyword_action',
|
|
70
|
-
'N': 'edit_task_notes',
|
|
71
62
|
'\x15': 'open_url', # Ctrl+U
|
|
72
|
-
'(': 'ultrathink_task',
|
|
73
|
-
')': 'execute_analysis_task',
|
|
74
63
|
'$': 'suggest_task',
|
|
75
|
-
'J': 'set_agent_task',
|
|
76
|
-
'B': 'start_priority_timer',
|
|
77
64
|
'!': 'fix_duplicate_ids_interactive',
|
|
78
65
|
'@': 'read_tasks_aloud',
|
|
79
66
|
'#': 'reauthenticate_google_calendar',
|
|
@@ -177,7 +177,7 @@ class AiAnalysisMixin:
|
|
|
177
177
|
return True
|
|
178
178
|
|
|
179
179
|
def execute_analysis_task(self):
|
|
180
|
-
"""Execute analysis plan with Claude Code
|
|
180
|
+
"""Execute analysis plan with Claude Code."""
|
|
181
181
|
current_task = self.task_manager.get_current_task()
|
|
182
182
|
if not current_task:
|
|
183
183
|
print("\n✗ No current task selected")
|
|
@@ -191,7 +191,7 @@ class AiAnalysisMixin:
|
|
|
191
191
|
analysis_file = Path.cwd() / f".jot.analysis.{task_id}.org"
|
|
192
192
|
if not analysis_file.exists():
|
|
193
193
|
print(f"\n{YELLOW}⚠️ No analysis found for this task{RESET}")
|
|
194
|
-
print(f"\n{DIM}Tip:
|
|
194
|
+
print(f"\n{DIM}Tip: Use the ~analyze:~ keyword to create an analysis plan{RESET}")
|
|
195
195
|
print("\nPress Enter to continue...", end='', flush=True)
|
|
196
196
|
input()
|
|
197
197
|
return True
|
|
@@ -268,12 +268,36 @@ Commands: a Add c Current d Delete D Delete-current e Edit E Edit-current
|
|
|
268
268
|
|
|
269
269
|
Navigation: ↑/Ctrl+p Up ↓/Ctrl+n Down Shift+↑ Move up Shift+↓ Move down
|
|
270
270
|
|
|
271
|
-
Quick-Add:
|
|
272
|
-
|
|
273
|
-
Ctrl+R Register
|
|
271
|
+
Quick-Add: .? Shortcuts .f Notes .n Edit notes .j Agent task
|
|
272
|
+
.z Project .l Categories .w Day .x Status
|
|
273
|
+
Ctrl+R Register Ctrl+T Transfer .m Move Ctrl+X Quit
|
|
274
274
|
|
|
275
275
|
Command Mode (ESC): r Refresh
|
|
276
276
|
|
|
277
|
-
Tip: Press '
|
|
277
|
+
Tip: Press '.' to start a leader chord. Press '.?' for full shortcuts.
|
|
278
278
|
Full CLI docs: jott --help""")
|
|
279
279
|
return True
|
|
280
|
+
|
|
281
|
+
def toggle_caps(self):
|
|
282
|
+
"""Toggle current task text between ALL CAPS and original case."""
|
|
283
|
+
current_task = self.task_manager.get_current_task()
|
|
284
|
+
if not current_task:
|
|
285
|
+
print("\n✗ No current task selected")
|
|
286
|
+
return True
|
|
287
|
+
|
|
288
|
+
task_id = current_task['id']
|
|
289
|
+
text = current_task['text']
|
|
290
|
+
original = current_task.get('_original_text')
|
|
291
|
+
|
|
292
|
+
if original is not None:
|
|
293
|
+
# Revert to original case
|
|
294
|
+
self.task_manager.edit_task(task_id, original)
|
|
295
|
+
self.task_manager.set_task_field(task_id, '_original_text', None)
|
|
296
|
+
print(f"\n{CYAN}✓ Reverted to original case{RESET}")
|
|
297
|
+
else:
|
|
298
|
+
# Convert to ALL CAPS, stash original
|
|
299
|
+
self.task_manager.set_task_field(task_id, '_original_text', text)
|
|
300
|
+
self.task_manager.edit_task(task_id, text.upper())
|
|
301
|
+
print(f"\n{CYAN}✓ ALL CAPS{RESET}")
|
|
302
|
+
|
|
303
|
+
return True
|
|
@@ -174,13 +174,17 @@ class MetadataMixin:
|
|
|
174
174
|
return True
|
|
175
175
|
|
|
176
176
|
def set_priority_high(self):
|
|
177
|
-
"""
|
|
177
|
+
"""Toggle current task priority between high and none."""
|
|
178
178
|
current_task = self.task_manager.get_current_task()
|
|
179
179
|
if not current_task:
|
|
180
180
|
return True
|
|
181
181
|
|
|
182
|
-
|
|
183
|
-
|
|
182
|
+
if current_task.get('priority') == 'high':
|
|
183
|
+
self.task_manager.set_task_priority(current_task['id'], 'none')
|
|
184
|
+
print(f"\n{GREEN}✓{RESET} Priority removed")
|
|
185
|
+
else:
|
|
186
|
+
self.task_manager.set_task_priority(current_task['id'], 'high')
|
|
187
|
+
print(f"\n{GREEN}✓{RESET} Priority set to {RED}● high{RESET}")
|
|
184
188
|
return True
|
|
185
189
|
|
|
186
190
|
def set_status(self):
|
|
@@ -148,6 +148,18 @@ class CrudMixin:
|
|
|
148
148
|
return True
|
|
149
149
|
return False
|
|
150
150
|
|
|
151
|
+
def set_task_field(self, task_id, field, value):
|
|
152
|
+
"""Set an arbitrary field on a task. Removes the field if value is None."""
|
|
153
|
+
for task in self.tasks:
|
|
154
|
+
if task['id'] == task_id:
|
|
155
|
+
if value is None:
|
|
156
|
+
task.pop(field, None)
|
|
157
|
+
else:
|
|
158
|
+
task[field] = value
|
|
159
|
+
self._save_tasks()
|
|
160
|
+
return True
|
|
161
|
+
return False
|
|
162
|
+
|
|
151
163
|
def set_task_notes(self, task_id, notes_text):
|
|
152
164
|
"""Set notes for a task"""
|
|
153
165
|
for task in self.tasks:
|
|
@@ -10,9 +10,9 @@ def display_categorized_shortcuts():
|
|
|
10
10
|
("↑/↓", "Navigate tasks"),
|
|
11
11
|
("Tab", "Cycle through categories"),
|
|
12
12
|
("Ctrl+F", "Fuzzy search tasks"),
|
|
13
|
-
("
|
|
13
|
+
(".z", "Switch project"),
|
|
14
14
|
("Ctrl+C", "Switch category"),
|
|
15
|
-
("
|
|
15
|
+
(".l", "Toggle all categories view"),
|
|
16
16
|
],
|
|
17
17
|
"Task Management": [
|
|
18
18
|
("Enter", "Add task"),
|
|
@@ -23,47 +23,36 @@ def display_categorized_shortcuts():
|
|
|
23
23
|
("a", "Archive task"),
|
|
24
24
|
(".a", "Toggle archive view"),
|
|
25
25
|
("m", "Move to different category"),
|
|
26
|
-
("
|
|
27
|
-
("
|
|
26
|
+
(".m", "Move task to another project"),
|
|
27
|
+
(".k", "Copy task to another project"),
|
|
28
28
|
("Ctrl+T", "Transfer to category"),
|
|
29
29
|
("Ctrl+↑/↓", "Reorder tasks"),
|
|
30
30
|
("=", "Sort by priority"),
|
|
31
31
|
],
|
|
32
32
|
"Task Details": [
|
|
33
33
|
("n", "Add/edit notes"),
|
|
34
|
-
("
|
|
35
|
-
("
|
|
34
|
+
(".n", "Edit task notes"),
|
|
35
|
+
(".f", "Toggle inline notes display"),
|
|
36
36
|
("p", "Toggle priority cycle"),
|
|
37
|
-
("
|
|
37
|
+
(".h", "Set priority to high"),
|
|
38
38
|
("=", "Sort by priority"),
|
|
39
|
-
("
|
|
40
|
-
("
|
|
41
|
-
("
|
|
42
|
-
("
|
|
39
|
+
(".x", "Set task status"),
|
|
40
|
+
(".w", "Assign day to task"),
|
|
41
|
+
(".o", "Toggle day sorting"),
|
|
42
|
+
(".y", "Toggle today filter"),
|
|
43
|
+
(".u", "Toggle ALL CAPS"),
|
|
43
44
|
("Ctrl+D", "Mark current as done"),
|
|
44
|
-
("
|
|
45
|
+
(".j", "Mark as agent task"),
|
|
45
46
|
("*", "Highlight color picker"),
|
|
46
47
|
("~", "Quick highlight toggle"),
|
|
47
48
|
("Ctrl+L", "Assign parent (link subtask)"),
|
|
48
49
|
],
|
|
49
|
-
"Multi-Select Mode": [
|
|
50
|
-
("Shift+V", "Enter multi-select mode"),
|
|
51
|
-
("Space", "Toggle task selection"),
|
|
52
|
-
("Shift+↑/↓", "Select while moving"),
|
|
53
|
-
("A", "Select all tasks"),
|
|
54
|
-
("N", "Select none"),
|
|
55
|
-
("B", "Bulk actions menu"),
|
|
56
|
-
("Esc", "Exit multi-select"),
|
|
57
|
-
],
|
|
58
|
-
"Bulk Operations": [
|
|
59
|
-
("Shift+B", "Bulk actions / Priority timer"),
|
|
60
|
-
],
|
|
61
50
|
"Analysis & Export": [
|
|
62
|
-
("Shift+0", "Execute analysis plan"),
|
|
63
51
|
("Shift+1", "Fix duplicate task IDs"),
|
|
64
52
|
("Shift+4", "AI task suggestion"),
|
|
65
|
-
("
|
|
66
|
-
("
|
|
53
|
+
(".e", "Execute keyword action"),
|
|
54
|
+
(".b", "Priority timer"),
|
|
55
|
+
(".G", "Export to Google Calendar"),
|
|
67
56
|
(".i", "Import from Google Calendar"),
|
|
68
57
|
(".c", "Copy task to clipboard"),
|
|
69
58
|
("Ctrl+U", "Open URLs in task"),
|
|
@@ -97,7 +86,7 @@ def display_help():
|
|
|
97
86
|
"""Display comprehensive help information."""
|
|
98
87
|
help_text = f"""
|
|
99
88
|
{BOLD}Jott - Simple Interactive Task List{RESET}
|
|
100
|
-
{DIM}Version 0.
|
|
89
|
+
{DIM}Version 0.6.0{RESET}
|
|
101
90
|
|
|
102
91
|
{BOLD}BASIC USAGE:{RESET}
|
|
103
92
|
jott Start interactive task manager (current project)
|
|
@@ -118,9 +107,9 @@ def display_help():
|
|
|
118
107
|
{CYAN}↑/↓{RESET} Navigate through tasks
|
|
119
108
|
{CYAN}Tab{RESET} Cycle through categories
|
|
120
109
|
{CYAN}Ctrl+F{RESET} Fuzzy search tasks
|
|
121
|
-
{CYAN}
|
|
110
|
+
{CYAN}.z{RESET} Switch between projects
|
|
122
111
|
{CYAN}Ctrl+C{RESET} Switch between categories
|
|
123
|
-
{CYAN}
|
|
112
|
+
{CYAN}.l{RESET} Toggle all categories view
|
|
124
113
|
|
|
125
114
|
{BOLD}TASK MANAGEMENT:{RESET}
|
|
126
115
|
{CYAN}Type + Enter{RESET} Add new task
|
|
@@ -131,40 +120,28 @@ def display_help():
|
|
|
131
120
|
{CYAN}a{RESET} Archive task (mark as done/canceled)
|
|
132
121
|
{CYAN}.a{RESET} Toggle archive view
|
|
133
122
|
{CYAN}m{RESET} Move task to different category
|
|
134
|
-
{CYAN}
|
|
135
|
-
{CYAN}
|
|
123
|
+
{CYAN}.m{RESET} Move task to another project
|
|
124
|
+
{CYAN}.k{RESET} Copy task to another project
|
|
136
125
|
{CYAN}Ctrl+T{RESET} Transfer task to category
|
|
137
126
|
{CYAN}Ctrl+↑/↓{RESET} Reorder tasks
|
|
138
127
|
{CYAN}={RESET} Sort by priority
|
|
139
128
|
|
|
140
129
|
{BOLD}TASK DETAILS:{RESET}
|
|
141
130
|
{CYAN}n{RESET} Add/edit task notes (opens editor)
|
|
142
|
-
{CYAN}
|
|
143
|
-
{CYAN}
|
|
131
|
+
{CYAN}.n{RESET} Edit task notes
|
|
132
|
+
{CYAN}.f{RESET} Toggle inline notes display
|
|
144
133
|
{CYAN}p{RESET} Toggle task priority cycle
|
|
145
|
-
{CYAN}
|
|
146
|
-
{CYAN}
|
|
147
|
-
{CYAN}
|
|
148
|
-
{CYAN}
|
|
149
|
-
{CYAN}
|
|
134
|
+
{CYAN}.h{RESET} Set priority to high
|
|
135
|
+
{CYAN}.x{RESET} Set task status (todo/in-progress/blocked/done)
|
|
136
|
+
{CYAN}.w{RESET} Assign day to task
|
|
137
|
+
{CYAN}.o{RESET} Toggle day sorting
|
|
138
|
+
{CYAN}.y{RESET} Toggle today filter
|
|
139
|
+
{CYAN}.u{RESET} Toggle ALL CAPS
|
|
150
140
|
{CYAN}Ctrl+D{RESET} Mark current task as done
|
|
151
|
-
{CYAN}
|
|
141
|
+
{CYAN}.j{RESET} Mark task for agent/AI assistance
|
|
152
142
|
{CYAN}*{RESET} Highlight color picker (6 colors)
|
|
153
143
|
{CYAN}~{RESET} Quick highlight toggle (default color)
|
|
154
144
|
{CYAN}Ctrl+L{RESET} Assign parent task (subtask link)
|
|
155
|
-
{CYAN}Shift+E{RESET} Execute keyword action for task
|
|
156
|
-
|
|
157
|
-
{BOLD}MULTI-SELECT MODE:{RESET}
|
|
158
|
-
{CYAN}Shift+V{RESET} Enter multi-select mode
|
|
159
|
-
{CYAN}Space{RESET} Toggle task selection
|
|
160
|
-
{CYAN}Shift+↑/↓{RESET} Select while moving
|
|
161
|
-
{CYAN}A{RESET} Select all tasks
|
|
162
|
-
{CYAN}N{RESET} Select none
|
|
163
|
-
{CYAN}B{RESET} Bulk actions menu
|
|
164
|
-
{CYAN}Esc{RESET} Exit multi-select mode
|
|
165
|
-
|
|
166
|
-
{BOLD}BULK OPERATIONS:{RESET}
|
|
167
|
-
{CYAN}Shift+B{RESET} Bulk actions menu / Priority timer
|
|
168
145
|
|
|
169
146
|
{BOLD}KEYWORDS:{RESET}
|
|
170
147
|
Keywords trigger automatic actions when added to task text:
|
|
@@ -173,7 +150,7 @@ def display_help():
|
|
|
173
150
|
{YELLOW}gcal:{RESET} task Export to Google Calendar
|
|
174
151
|
{YELLOW}ai:{RESET} task Mark for AI/agent assistance
|
|
175
152
|
{YELLOW}analyze:{RESET} task Create analysis plan with Claude Code
|
|
176
|
-
{YELLOW}remind:{RESET} task Set system notification (
|
|
153
|
+
{YELLOW}remind:{RESET} task Set system notification (.e)
|
|
177
154
|
|
|
178
155
|
Configure keywords in ~/.jot-keywords.json
|
|
179
156
|
|
|
@@ -192,14 +169,14 @@ def display_help():
|
|
|
192
169
|
Multi-project support with auto-discovery:
|
|
193
170
|
• Auto-discovers projects in ~/projects/
|
|
194
171
|
• Each project has its own .jot.json
|
|
195
|
-
• Switch projects with {CYAN}
|
|
172
|
+
• Switch projects with {CYAN}.z{RESET} or {CYAN}jott -p <name>{RESET}
|
|
196
173
|
|
|
197
174
|
{BOLD}ANALYSIS & EXPORT:{RESET}
|
|
198
|
-
{CYAN}Shift+0{RESET} Execute analysis plan (if exists)
|
|
199
175
|
{CYAN}Shift+1{RESET} Fix duplicate task IDs
|
|
200
176
|
{CYAN}Shift+4{RESET} AI task suggestion
|
|
201
|
-
{CYAN}
|
|
202
|
-
{CYAN}
|
|
177
|
+
{CYAN}.e{RESET} Execute keyword action for task
|
|
178
|
+
{CYAN}.b{RESET} Priority timer
|
|
179
|
+
{CYAN}.G{RESET} Export to Google Calendar
|
|
203
180
|
{CYAN}.i{RESET} Import tasks from Google Calendar
|
|
204
181
|
{CYAN}.c{RESET} Copy task text to clipboard
|
|
205
182
|
{CYAN}Ctrl+U{RESET} Open URLs found in task text
|
|
@@ -47,7 +47,7 @@ def display_all_projects(registry, show_categories=False):
|
|
|
47
47
|
print(
|
|
48
48
|
f"Total: {len(projects)} projects | "
|
|
49
49
|
f"Use {CYAN}jot -p <project>{RESET} to switch | "
|
|
50
|
-
f"{CYAN}
|
|
50
|
+
f"{CYAN}.p{RESET} in jot UI"
|
|
51
51
|
)
|
|
52
52
|
print("=" * w + "\n")
|
|
53
53
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: jott-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
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>
|
|
@@ -62,12 +62,12 @@ Dynamic: license-file
|
|
|
62
62
|
- **Dual-mode interface**: Quick-add mode ↔ Command mode (ESC to switch)
|
|
63
63
|
- **Cross-project task routing**: `jott myproject "task"` from anywhere
|
|
64
64
|
- **Keyword automation**: Tasks starting with `bullet:`, `gcal:`, `ai:`, `analyze:` trigger actions
|
|
65
|
-
- **AI integration**: Claude Code integration
|
|
65
|
+
- **AI integration**: Claude Code integration via the `analyze:` keyword for task analysis
|
|
66
66
|
- **Google Calendar sync**: Bidirectional sync (export tasks, import events)
|
|
67
67
|
- **Text-to-speech**: Read all tasks aloud with Shift+2
|
|
68
68
|
- **Fuzzy search**: Filter tasks quickly with `Ctrl+/`
|
|
69
69
|
- **Fuzzy project picker**: Interactive search when moving tasks between projects
|
|
70
|
-
- **
|
|
70
|
+
- **Leader chord system**: `.` prefix for 20+ shortcuts (e.g., `.m` move, `.n` notes)
|
|
71
71
|
- **Category system**: Up to 12 categories per project
|
|
72
72
|
- **Task metadata**: Priority, status, day-of-week assignment, notes
|
|
73
73
|
- **Backup system**: Organized monthly backups
|
|
@@ -121,34 +121,35 @@ Just type and press Enter:
|
|
|
121
121
|
### Keyboard Shortcuts
|
|
122
122
|
|
|
123
123
|
**Task Management:**
|
|
124
|
-
- `
|
|
125
|
-
- `
|
|
126
|
-
- `
|
|
127
|
-
- `
|
|
124
|
+
- `.n` - Edit task notes
|
|
125
|
+
- `Ctrl+D` - Delete current task
|
|
126
|
+
- `.a` - Toggle archived tasks
|
|
127
|
+
- `.f` - Toggle inline notes display
|
|
128
|
+
- `.u` - Toggle ALL CAPS
|
|
128
129
|
- `Shift+4` - AI-powered task suggestion
|
|
129
130
|
|
|
130
131
|
**Scheduling:**
|
|
131
|
-
- `
|
|
132
|
-
- `
|
|
133
|
-
- `
|
|
132
|
+
- `.w` - Assign day of week
|
|
133
|
+
- `.p` - Set priority
|
|
134
|
+
- `.x` - Set status
|
|
134
135
|
|
|
135
136
|
**AI Integration:**
|
|
136
|
-
- `
|
|
137
|
-
- `
|
|
138
|
-
- `Shift+J` - Mark as agent task
|
|
137
|
+
- `analyze:` keyword - Analyze task with Claude Code (plan mode)
|
|
138
|
+
- `.j` - Mark as agent task
|
|
139
139
|
|
|
140
140
|
**Integrations:**
|
|
141
141
|
- `Shift+2` - Read tasks aloud (TTS)
|
|
142
142
|
- `Shift+3` - Re-authenticate Google Calendar
|
|
143
|
-
-
|
|
144
|
-
- `
|
|
145
|
-
- `
|
|
143
|
+
- `.G` - Export to Google Calendar (single or bulk with time tags)
|
|
144
|
+
- `.i` - Import from Google Calendar
|
|
145
|
+
- `.b` - Start priority timer (12-minute focus session)
|
|
146
146
|
|
|
147
147
|
**Navigation & System:**
|
|
148
148
|
- `↑/↓` or `Ctrl+N/P` - Navigate tasks
|
|
149
149
|
- `Shift+↑/↓` - Reorder tasks
|
|
150
|
-
- `Ctrl
|
|
151
|
-
- `
|
|
150
|
+
- `Ctrl+F` - Fuzzy search mode
|
|
151
|
+
- `.z` - Switch project
|
|
152
|
+
- `.l` - Toggle all categories view
|
|
152
153
|
- `Tab` - Cycle through categories
|
|
153
154
|
- `Ctrl+R` - Register current project
|
|
154
155
|
- `Ctrl+U` - Open URLs from task text
|
|
@@ -170,7 +171,7 @@ Organize tasks with categories (up to 12 per project):
|
|
|
170
171
|
|
|
171
172
|
```bash
|
|
172
173
|
# Switch to category
|
|
173
|
-
|
|
174
|
+
Ctrl+C
|
|
174
175
|
|
|
175
176
|
# Create category-specific tasks
|
|
176
177
|
jott --category=bugs "Fix login redirect"
|
|
@@ -202,7 +203,7 @@ jott myproject "task text"
|
|
|
202
203
|
1. Enable Google Calendar API
|
|
203
204
|
2. Download credentials
|
|
204
205
|
3. Save as `~/.jot/gcal-accounts/default/credentials.json`
|
|
205
|
-
4. Use
|
|
206
|
+
4. Use `.G` to export tasks
|
|
206
207
|
|
|
207
208
|
**Re-authentication:**
|
|
208
209
|
- Press `Shift+3` to force re-authentication (if token expires or switching accounts)
|
|
@@ -7,7 +7,7 @@ include = ["jot", "jot.*"]
|
|
|
7
7
|
|
|
8
8
|
[project]
|
|
9
9
|
name = "jott-cli"
|
|
10
|
-
version = "0.
|
|
10
|
+
version = "0.6.0"
|
|
11
11
|
description = "Feature-rich interactive CLI task manager with AI integration, calendar sync, and keyword automation"
|
|
12
12
|
readme = {file = "README.md", content-type = "text/markdown"}
|
|
13
13
|
requires-python = ">=3.6"
|
|
@@ -411,6 +411,70 @@ class TestSuggestTask:
|
|
|
411
411
|
assert 'testing' in suggestion['labels']
|
|
412
412
|
|
|
413
413
|
|
|
414
|
+
class TestToggleCaps:
|
|
415
|
+
"""Test toggle_caps command — ALL CAPS and revert."""
|
|
416
|
+
|
|
417
|
+
def test_toggle_caps_no_task(self):
|
|
418
|
+
"""No current task selected — returns True, no crash."""
|
|
419
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
420
|
+
tm = TaskManager(directory=Path(tmpdir))
|
|
421
|
+
handler = CommandHandler(tm)
|
|
422
|
+
|
|
423
|
+
result = handler.toggle_caps()
|
|
424
|
+
assert result is True
|
|
425
|
+
|
|
426
|
+
def test_toggle_caps_to_upper(self):
|
|
427
|
+
"""First press converts text to ALL CAPS."""
|
|
428
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
429
|
+
tm = TaskManager(directory=Path(tmpdir))
|
|
430
|
+
handler = CommandHandler(tm)
|
|
431
|
+
|
|
432
|
+
tm.add_task("buy groceries")
|
|
433
|
+
task_id = tm.tasks[0]['id']
|
|
434
|
+
tm.set_current(task_id)
|
|
435
|
+
|
|
436
|
+
handler.toggle_caps()
|
|
437
|
+
|
|
438
|
+
assert tm.tasks[0]['text'] == "BUY GROCERIES"
|
|
439
|
+
assert tm.tasks[0]['_original_text'] == "buy groceries"
|
|
440
|
+
|
|
441
|
+
def test_toggle_caps_revert(self):
|
|
442
|
+
"""Second press reverts to original case."""
|
|
443
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
444
|
+
tm = TaskManager(directory=Path(tmpdir))
|
|
445
|
+
handler = CommandHandler(tm)
|
|
446
|
+
|
|
447
|
+
tm.add_task("Buy Groceries")
|
|
448
|
+
task_id = tm.tasks[0]['id']
|
|
449
|
+
tm.set_current(task_id)
|
|
450
|
+
|
|
451
|
+
handler.toggle_caps()
|
|
452
|
+
assert tm.tasks[0]['text'] == "BUY GROCERIES"
|
|
453
|
+
|
|
454
|
+
handler.toggle_caps()
|
|
455
|
+
assert tm.tasks[0]['text'] == "Buy Groceries"
|
|
456
|
+
assert '_original_text' not in tm.tasks[0]
|
|
457
|
+
|
|
458
|
+
def test_toggle_caps_already_upper(self):
|
|
459
|
+
"""Text already uppercase still stores original and toggles."""
|
|
460
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
461
|
+
tm = TaskManager(directory=Path(tmpdir))
|
|
462
|
+
handler = CommandHandler(tm)
|
|
463
|
+
|
|
464
|
+
tm.add_task("ALREADY CAPS")
|
|
465
|
+
task_id = tm.tasks[0]['id']
|
|
466
|
+
tm.set_current(task_id)
|
|
467
|
+
|
|
468
|
+
handler.toggle_caps()
|
|
469
|
+
assert tm.tasks[0]['text'] == "ALREADY CAPS"
|
|
470
|
+
assert tm.tasks[0]['_original_text'] == "ALREADY CAPS"
|
|
471
|
+
|
|
472
|
+
# Revert restores the same text
|
|
473
|
+
handler.toggle_caps()
|
|
474
|
+
assert tm.tasks[0]['text'] == "ALREADY CAPS"
|
|
475
|
+
assert '_original_text' not in tm.tasks[0]
|
|
476
|
+
|
|
477
|
+
|
|
414
478
|
if __name__ == '__main__':
|
|
415
479
|
import pytest
|
|
416
480
|
import sys
|
|
@@ -75,19 +75,14 @@ class TestQuickAddDispatchTable:
|
|
|
75
75
|
)
|
|
76
76
|
|
|
77
77
|
def test_table_is_not_empty(self):
|
|
78
|
-
assert len(App._QUICK_ADD_SIMPLE) >=
|
|
79
|
-
"Dispatch table should have at least
|
|
78
|
+
assert len(App._QUICK_ADD_SIMPLE) >= 10, (
|
|
79
|
+
"Dispatch table should have at least 10 entries"
|
|
80
80
|
)
|
|
81
81
|
|
|
82
82
|
def test_critical_keys_present(self):
|
|
83
83
|
"""Verify that the most important shortcuts are wired up."""
|
|
84
84
|
critical = {
|
|
85
85
|
'\x04': 'delete_current', # Ctrl+D
|
|
86
|
-
'W': 'assign_day',
|
|
87
|
-
'P': 'set_priority',
|
|
88
|
-
'X': 'set_status',
|
|
89
|
-
'M': 'move_task_to_project',
|
|
90
|
-
'N': 'edit_task_notes',
|
|
91
86
|
'\x14': 'transfer_task_to_category', # Ctrl+T
|
|
92
87
|
}
|
|
93
88
|
for key, expected_method in critical.items():
|
|
@@ -114,7 +109,6 @@ class TestAppDispatcherStructure:
|
|
|
114
109
|
def test_shared_helpers_exist(self):
|
|
115
110
|
helpers = [
|
|
116
111
|
'_handle_navigation',
|
|
117
|
-
'_handle_toggle',
|
|
118
112
|
'_handle_auto_refresh',
|
|
119
113
|
'_handle_paste',
|
|
120
114
|
'_reload_task_manager',
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
File without changes
|