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.
Files changed (98) hide show
  1. {jott_cli-0.5.5/jott_cli.egg-info → jott_cli-0.6.0}/PKG-INFO +21 -20
  2. {jott_cli-0.5.5 → jott_cli-0.6.0}/README.md +20 -19
  3. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/__init__.py +1 -1
  4. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/_app_navigation_mixin.py +0 -36
  5. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/_dispatch_mixin.py +90 -27
  6. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/app.py +0 -13
  7. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/commands/_ai_analysis_mixin.py +2 -2
  8. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/commands/_core_mixin.py +28 -4
  9. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/commands/_metadata_mixin.py +7 -3
  10. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/core/_crud_mixin.py +12 -0
  11. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/ui/display_help.py +35 -58
  12. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/ui/display_projects.py +1 -1
  13. {jott_cli-0.5.5 → jott_cli-0.6.0/jott_cli.egg-info}/PKG-INFO +21 -20
  14. {jott_cli-0.5.5 → jott_cli-0.6.0}/pyproject.toml +1 -1
  15. {jott_cli-0.5.5 → jott_cli-0.6.0}/tests/test_command_handler.py +64 -0
  16. {jott_cli-0.5.5 → jott_cli-0.6.0}/tests/test_dispatch.py +2 -8
  17. {jott_cli-0.5.5 → jott_cli-0.6.0}/LICENSE +0 -0
  18. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/categories/__init__.py +0 -0
  19. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/categories/config.py +0 -0
  20. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/categories/manager.py +0 -0
  21. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/categories/templates.py +0 -0
  22. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/cli/__init__.py +0 -0
  23. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/cli/archive.py +0 -0
  24. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/cli/config.py +0 -0
  25. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/cli/views.py +0 -0
  26. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/commands/__init__.py +0 -0
  27. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/commands/_ai_suggest_mixin.py +0 -0
  28. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/commands/_audio_timer_mixin.py +0 -0
  29. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/commands/_bulk_mixin.py +0 -0
  30. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/commands/_context_mixin.py +0 -0
  31. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/commands/_gcal_mixin.py +0 -0
  32. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/commands/_notes_mixin.py +0 -0
  33. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/commands/_transfer_mixin.py +0 -0
  34. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/commands/_web_clipboard_mixin.py +0 -0
  35. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/commands/handler.py +0 -0
  36. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/core/__init__.py +0 -0
  37. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/core/_age_backlog_mixin.py +0 -0
  38. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/core/_compress_mixin.py +0 -0
  39. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/core/_delete_mixin.py +0 -0
  40. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/core/_export_mixin.py +0 -0
  41. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/core/_id_migration_mixin.py +0 -0
  42. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/core/_metadata_mixin.py +0 -0
  43. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/core/_navigation_mixin.py +0 -0
  44. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/core/_persistence_mixin.py +0 -0
  45. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/core/_subtask_mixin.py +0 -0
  46. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/core/archive_manager.py +0 -0
  47. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/core/constants.py +0 -0
  48. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/core/id_manager.py +0 -0
  49. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/core/task_manager.py +0 -0
  50. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/integrations/__init__.py +0 -0
  51. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/integrations/gcal/__init__.py +0 -0
  52. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/integrations/gcal/account_manager.py +0 -0
  53. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/integrations/gcal/auth.py +0 -0
  54. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/integrations/gcal/events.py +0 -0
  55. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/integrations/keywords/__init__.py +0 -0
  56. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/integrations/keywords/_config_mixin.py +0 -0
  57. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/integrations/keywords/_handlers_mixin.py +0 -0
  58. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/integrations/keywords/handler.py +0 -0
  59. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/mcp/__init__.py +0 -0
  60. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/mcp/handlers.py +0 -0
  61. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/mcp/schemas.py +0 -0
  62. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/mcp/server.py +0 -0
  63. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/projects/__init__.py +0 -0
  64. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/projects/backup.py +0 -0
  65. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/projects/registry.py +0 -0
  66. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/ui/__init__.py +0 -0
  67. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/ui/display.py +0 -0
  68. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/ui/display_archive.py +0 -0
  69. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/ui/display_footer.py +0 -0
  70. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/ui/display_tasks.py +0 -0
  71. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/ui/formatting.py +0 -0
  72. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/ui/input.py +0 -0
  73. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/ui/picker.py +0 -0
  74. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/ui/rendering.py +0 -0
  75. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/ui/styles.py +0 -0
  76. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/utils/__init__.py +0 -0
  77. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/utils/date_utils.py +0 -0
  78. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/utils/text_utils.py +0 -0
  79. {jott_cli-0.5.5 → jott_cli-0.6.0}/jot/utils/validation.py +0 -0
  80. {jott_cli-0.5.5 → jott_cli-0.6.0}/jott_cli.egg-info/SOURCES.txt +0 -0
  81. {jott_cli-0.5.5 → jott_cli-0.6.0}/jott_cli.egg-info/dependency_links.txt +0 -0
  82. {jott_cli-0.5.5 → jott_cli-0.6.0}/jott_cli.egg-info/entry_points.txt +0 -0
  83. {jott_cli-0.5.5 → jott_cli-0.6.0}/jott_cli.egg-info/requires.txt +0 -0
  84. {jott_cli-0.5.5 → jott_cli-0.6.0}/jott_cli.egg-info/top_level.txt +0 -0
  85. {jott_cli-0.5.5 → jott_cli-0.6.0}/setup.cfg +0 -0
  86. {jott_cli-0.5.5 → jott_cli-0.6.0}/setup.py +0 -0
  87. {jott_cli-0.5.5 → jott_cli-0.6.0}/tests/test_edit_edge_cases.py +0 -0
  88. {jott_cli-0.5.5 → jott_cli-0.6.0}/tests/test_fuzzy_search.py +0 -0
  89. {jott_cli-0.5.5 → jott_cli-0.6.0}/tests/test_gcal_notes.py +0 -0
  90. {jott_cli-0.5.5 → jott_cli-0.6.0}/tests/test_highlight.py +0 -0
  91. {jott_cli-0.5.5 → jott_cli-0.6.0}/tests/test_input.py +0 -0
  92. {jott_cli-0.5.5 → jott_cli-0.6.0}/tests/test_jot.py +0 -0
  93. {jott_cli-0.5.5 → jott_cli-0.6.0}/tests/test_picker.py +0 -0
  94. {jott_cli-0.5.5 → jott_cli-0.6.0}/tests/test_styles.py +0 -0
  95. {jott_cli-0.5.5 → jott_cli-0.6.0}/tests/test_subtask_notes.py +0 -0
  96. {jott_cli-0.5.5 → jott_cli-0.6.0}/tests/test_terminal_wrap.py +0 -0
  97. {jott_cli-0.5.5 → jott_cli-0.6.0}/tests/test_today_filter.py +0 -0
  98. {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.5.5
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 for task analysis (Shift+9) and execution (Shift+0)
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
- - **Multi-select mode**: Bulk operations with Shift+V
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
- - `Shift+N` - Edit task notes
125
- - `Shift+D` - Delete current task
126
- - `Shift+A` - Toggle archived tasks
127
- - `Shift+F` - Toggle inline notes display
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
- - `Shift+W` - Assign day of week
132
- - `Shift+P` - Set priority
133
- - `Shift+X` - Set status
132
+ - `.w` - Assign day of week
133
+ - `.p` - Set priority
134
+ - `.x` - Set status
134
135
 
135
136
  **AI Integration:**
136
- - `Shift+9` - Analyze task with Claude Code (plan mode)
137
- - `Shift+0` - Execute analysis plan
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
- - `Shift+G` - Export to Google Calendar (single or bulk with time tags)
144
- - `Shift+I` - Import from Google Calendar
145
- - `Shift+B` - Start priority timer (12-minute focus session)
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+/` - Fuzzy search mode
151
- - `Shift+V` - Multi-select mode
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
- Shift+C
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 `Shift+G` to export tasks
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 for task analysis (Shift+9) and execution (Shift+0)
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
- - **Multi-select mode**: Bulk operations with Shift+V
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
- - `Shift+N` - Edit task notes
75
- - `Shift+D` - Delete current task
76
- - `Shift+A` - Toggle archived tasks
77
- - `Shift+F` - Toggle inline notes display
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
- - `Shift+W` - Assign day of week
82
- - `Shift+P` - Set priority
83
- - `Shift+X` - Set status
82
+ - `.w` - Assign day of week
83
+ - `.p` - Set priority
84
+ - `.x` - Set status
84
85
 
85
86
  **AI Integration:**
86
- - `Shift+9` - Analyze task with Claude Code (plan mode)
87
- - `Shift+0` - Execute analysis plan
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
- - `Shift+G` - Export to Google Calendar (single or bulk with time tags)
94
- - `Shift+I` - Import from Google Calendar
95
- - `Shift+B` - Start priority timer (12-minute focus session)
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+/` - Fuzzy search mode
101
- - `Shift+V` - Multi-select mode
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
- Shift+C
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 `Shift+G` to export tasks
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.5.5"
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' or key == 'L':
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 (Shift+0)"""
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: Press Shift+9 to analyze first, then Shift+0 to execute{RESET}")
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: ? Shortcuts Shift+F Notes display Shift+N Edit notes
272
- Shift+J Agent task Shift+C Category Shift+Z Project
273
- Ctrl+R Register Shift+T Transfer Shift+M Move Ctrl+X Quit
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 '?' in Quick-Add mode for full categorized shortcuts.
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
- """Quickly set current task priority to high (shortcut)"""
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
- self.task_manager.set_task_priority(current_task['id'], 'high')
183
- print(f"\n{GREEN}✓{RESET} Priority set to {RED}● high{RESET}")
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
- ("Shift+Z", "Switch project"),
13
+ (".z", "Switch project"),
14
14
  ("Ctrl+C", "Switch category"),
15
- ("Shift+L", "Toggle all categories view"),
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
- ("Shift+M", "Move task to another project"),
27
- ("Ctrl+K", "Copy task to another project"),
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
- ("Shift+N", "Edit task notes"),
35
- ("Shift+F", "Toggle inline notes display"),
34
+ (".n", "Edit task notes"),
35
+ (".f", "Toggle inline notes display"),
36
36
  ("p", "Toggle priority cycle"),
37
- ("Shift+H", "Set priority to high"),
37
+ (".h", "Set priority to high"),
38
38
  ("=", "Sort by priority"),
39
- ("Shift+X", "Set task status"),
40
- ("Shift+W", "Assign day to task"),
41
- ("Shift+O", "Toggle day sorting"),
42
- ("Shift+Y", "Toggle today filter"),
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
- ("Shift+J", "Mark as agent task"),
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
- ("Shift+E", "Execute keyword action"),
66
- ("Shift+G", "Google Calendar setup"),
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.5.5{RESET}
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}Shift+Z{RESET} Switch between projects
110
+ {CYAN}.z{RESET} Switch between projects
122
111
  {CYAN}Ctrl+C{RESET} Switch between categories
123
- {CYAN}Shift+L{RESET} Toggle all categories view
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}Shift+M{RESET} Move task to another project
135
- {CYAN}Ctrl+K{RESET} Copy task to another project
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}Shift+N{RESET} Edit task notes (multi-select: select none)
143
- {CYAN}Shift+F{RESET} Toggle inline notes display
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}Shift+H{RESET} Set priority to high
146
- {CYAN}Shift+X{RESET} Set task status (todo/in-progress/blocked/done)
147
- {CYAN}Shift+W{RESET} Assign day to task
148
- {CYAN}Shift+O{RESET} Toggle day sorting
149
- {CYAN}Shift+Y{RESET} Toggle today filter
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}Shift+J{RESET} Mark task for agent/AI assistance
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 (Shift+E)
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}Shift+Z{RESET} or {CYAN}jott -p <name>{RESET}
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}Shift+E{RESET} Execute keyword action for task
202
- {CYAN}Shift+G{RESET} Set up Google Calendar integration
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}Shift+P{RESET} in jot UI"
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.5.5
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 for task analysis (Shift+9) and execution (Shift+0)
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
- - **Multi-select mode**: Bulk operations with Shift+V
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
- - `Shift+N` - Edit task notes
125
- - `Shift+D` - Delete current task
126
- - `Shift+A` - Toggle archived tasks
127
- - `Shift+F` - Toggle inline notes display
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
- - `Shift+W` - Assign day of week
132
- - `Shift+P` - Set priority
133
- - `Shift+X` - Set status
132
+ - `.w` - Assign day of week
133
+ - `.p` - Set priority
134
+ - `.x` - Set status
134
135
 
135
136
  **AI Integration:**
136
- - `Shift+9` - Analyze task with Claude Code (plan mode)
137
- - `Shift+0` - Execute analysis plan
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
- - `Shift+G` - Export to Google Calendar (single or bulk with time tags)
144
- - `Shift+I` - Import from Google Calendar
145
- - `Shift+B` - Start priority timer (12-minute focus session)
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+/` - Fuzzy search mode
151
- - `Shift+V` - Multi-select mode
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
- Shift+C
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 `Shift+G` to export tasks
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.5.5"
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) >= 20, (
79
- "Dispatch table should have at least 20 entries"
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