jott-cli 0.5.6__tar.gz → 0.7.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 (103) hide show
  1. {jott_cli-0.5.6/jott_cli.egg-info → jott_cli-0.7.0}/PKG-INFO +21 -20
  2. {jott_cli-0.5.6 → jott_cli-0.7.0}/README.md +20 -19
  3. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/__init__.py +1 -1
  4. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/_app_navigation_mixin.py +0 -36
  5. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/_dispatch_mixin.py +122 -27
  6. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/app.py +8 -12
  7. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/commands/_ai_analysis_mixin.py +2 -2
  8. jott_cli-0.7.0/jot/commands/_claude_mixin.py +390 -0
  9. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/commands/_core_mixin.py +44 -4
  10. jott_cli-0.7.0/jot/commands/_github_mixin.py +296 -0
  11. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/commands/_metadata_mixin.py +7 -3
  12. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/commands/_notes_mixin.py +18 -1
  13. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/commands/handler.py +4 -0
  14. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/core/_crud_mixin.py +40 -0
  15. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/core/constants.py +6 -0
  16. jott_cli-0.7.0/jot/integrations/github/issues.py +105 -0
  17. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/ui/display_help.py +55 -56
  18. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/ui/display_projects.py +1 -1
  19. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/ui/display_tasks.py +18 -0
  20. jott_cli-0.7.0/jot/utils/__init__.py +0 -0
  21. {jott_cli-0.5.6 → jott_cli-0.7.0/jott_cli.egg-info}/PKG-INFO +21 -20
  22. {jott_cli-0.5.6 → jott_cli-0.7.0}/jott_cli.egg-info/SOURCES.txt +5 -0
  23. {jott_cli-0.5.6 → jott_cli-0.7.0}/pyproject.toml +1 -1
  24. {jott_cli-0.5.6 → jott_cli-0.7.0}/tests/test_command_handler.py +64 -0
  25. {jott_cli-0.5.6 → jott_cli-0.7.0}/tests/test_dispatch.py +2 -8
  26. jott_cli-0.7.0/tests/test_github.py +234 -0
  27. {jott_cli-0.5.6 → jott_cli-0.7.0}/LICENSE +0 -0
  28. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/categories/__init__.py +0 -0
  29. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/categories/config.py +0 -0
  30. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/categories/manager.py +0 -0
  31. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/categories/templates.py +0 -0
  32. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/cli/__init__.py +0 -0
  33. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/cli/archive.py +0 -0
  34. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/cli/config.py +0 -0
  35. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/cli/views.py +0 -0
  36. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/commands/__init__.py +0 -0
  37. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/commands/_ai_suggest_mixin.py +0 -0
  38. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/commands/_audio_timer_mixin.py +0 -0
  39. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/commands/_bulk_mixin.py +0 -0
  40. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/commands/_context_mixin.py +0 -0
  41. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/commands/_gcal_mixin.py +0 -0
  42. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/commands/_transfer_mixin.py +0 -0
  43. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/commands/_web_clipboard_mixin.py +0 -0
  44. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/core/__init__.py +0 -0
  45. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/core/_age_backlog_mixin.py +0 -0
  46. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/core/_compress_mixin.py +0 -0
  47. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/core/_delete_mixin.py +0 -0
  48. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/core/_export_mixin.py +0 -0
  49. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/core/_id_migration_mixin.py +0 -0
  50. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/core/_metadata_mixin.py +0 -0
  51. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/core/_navigation_mixin.py +0 -0
  52. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/core/_persistence_mixin.py +0 -0
  53. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/core/_subtask_mixin.py +0 -0
  54. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/core/archive_manager.py +0 -0
  55. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/core/id_manager.py +0 -0
  56. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/core/task_manager.py +0 -0
  57. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/integrations/__init__.py +0 -0
  58. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/integrations/gcal/__init__.py +0 -0
  59. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/integrations/gcal/account_manager.py +0 -0
  60. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/integrations/gcal/auth.py +0 -0
  61. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/integrations/gcal/events.py +0 -0
  62. {jott_cli-0.5.6/jot/integrations/keywords → jott_cli-0.7.0/jot/integrations/github}/__init__.py +0 -0
  63. {jott_cli-0.5.6/jot/projects → jott_cli-0.7.0/jot/integrations/keywords}/__init__.py +0 -0
  64. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/integrations/keywords/_config_mixin.py +0 -0
  65. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/integrations/keywords/_handlers_mixin.py +0 -0
  66. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/integrations/keywords/handler.py +0 -0
  67. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/mcp/__init__.py +0 -0
  68. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/mcp/handlers.py +0 -0
  69. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/mcp/schemas.py +0 -0
  70. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/mcp/server.py +0 -0
  71. {jott_cli-0.5.6/jot/utils → jott_cli-0.7.0/jot/projects}/__init__.py +0 -0
  72. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/projects/backup.py +0 -0
  73. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/projects/registry.py +0 -0
  74. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/ui/__init__.py +0 -0
  75. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/ui/display.py +0 -0
  76. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/ui/display_archive.py +0 -0
  77. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/ui/display_footer.py +0 -0
  78. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/ui/formatting.py +0 -0
  79. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/ui/input.py +0 -0
  80. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/ui/picker.py +0 -0
  81. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/ui/rendering.py +0 -0
  82. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/ui/styles.py +0 -0
  83. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/utils/date_utils.py +0 -0
  84. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/utils/text_utils.py +0 -0
  85. {jott_cli-0.5.6 → jott_cli-0.7.0}/jot/utils/validation.py +0 -0
  86. {jott_cli-0.5.6 → jott_cli-0.7.0}/jott_cli.egg-info/dependency_links.txt +0 -0
  87. {jott_cli-0.5.6 → jott_cli-0.7.0}/jott_cli.egg-info/entry_points.txt +0 -0
  88. {jott_cli-0.5.6 → jott_cli-0.7.0}/jott_cli.egg-info/requires.txt +0 -0
  89. {jott_cli-0.5.6 → jott_cli-0.7.0}/jott_cli.egg-info/top_level.txt +0 -0
  90. {jott_cli-0.5.6 → jott_cli-0.7.0}/setup.cfg +0 -0
  91. {jott_cli-0.5.6 → jott_cli-0.7.0}/setup.py +0 -0
  92. {jott_cli-0.5.6 → jott_cli-0.7.0}/tests/test_edit_edge_cases.py +0 -0
  93. {jott_cli-0.5.6 → jott_cli-0.7.0}/tests/test_fuzzy_search.py +0 -0
  94. {jott_cli-0.5.6 → jott_cli-0.7.0}/tests/test_gcal_notes.py +0 -0
  95. {jott_cli-0.5.6 → jott_cli-0.7.0}/tests/test_highlight.py +0 -0
  96. {jott_cli-0.5.6 → jott_cli-0.7.0}/tests/test_input.py +0 -0
  97. {jott_cli-0.5.6 → jott_cli-0.7.0}/tests/test_jot.py +0 -0
  98. {jott_cli-0.5.6 → jott_cli-0.7.0}/tests/test_picker.py +0 -0
  99. {jott_cli-0.5.6 → jott_cli-0.7.0}/tests/test_styles.py +0 -0
  100. {jott_cli-0.5.6 → jott_cli-0.7.0}/tests/test_subtask_notes.py +0 -0
  101. {jott_cli-0.5.6 → jott_cli-0.7.0}/tests/test_terminal_wrap.py +0 -0
  102. {jott_cli-0.5.6 → jott_cli-0.7.0}/tests/test_today_filter.py +0 -0
  103. {jott_cli-0.5.6 → jott_cli-0.7.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.6
3
+ Version: 0.7.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.6"
10
+ __version__ = "0.7.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."""
@@ -301,6 +276,10 @@ class DispatchMixin:
301
276
  self._toggle_collapse()
302
277
  st.input_buffer = ""
303
278
  return
279
+ if second == 'd':
280
+ self.command_handler.duplicate_task()
281
+ st.input_buffer = ""
282
+ return
304
283
  if second == 'c':
305
284
  self.command_handler.copy_to_clipboard()
306
285
  st.input_buffer = ""
@@ -328,6 +307,116 @@ class DispatchMixin:
328
307
  self.command_handler.export_to_gcal()
329
308
  st.input_buffer = ""
330
309
  return
310
+ if second == 'f':
311
+ st.show_notes_inline = not st.show_notes_inline
312
+ status = "Showing" if st.show_notes_inline else "Hiding"
313
+ print(f"\n{CYAN}\u2713 {status} inline notes{RESET}")
314
+ time.sleep(0.3)
315
+ st.input_buffer = ""
316
+ return
317
+ if second == 'k':
318
+ self.command_handler.copy_task_to_project()
319
+ st.input_buffer = ""
320
+ return
321
+ if second == 'h':
322
+ self.command_handler.set_priority_high()
323
+ st.input_buffer = ""
324
+ return
325
+ if second == 'p':
326
+ self.command_handler.set_priority()
327
+ st.input_buffer = ""
328
+ return
329
+ if second == 'm':
330
+ self.command_handler.move_task_to_project()
331
+ st.input_buffer = ""
332
+ return
333
+ if second == 'u':
334
+ self.command_handler.toggle_caps()
335
+ st.input_buffer = ""
336
+ return
337
+ if second == 'e':
338
+ self.command_handler.trigger_keyword_action()
339
+ st.input_buffer = ""
340
+ return
341
+ if second == 'b':
342
+ self.command_handler.start_priority_timer()
343
+ st.input_buffer = ""
344
+ return
345
+ if second == 'j':
346
+ self.command_handler.set_agent_task()
347
+ st.input_buffer = ""
348
+ return
349
+ if second == 'l':
350
+ should_toggle, new_mode = (
351
+ self.command_handler.toggle_all_categories_view(st.mode))
352
+ if should_toggle:
353
+ st.mode = new_mode
354
+ st.input_buffer = ""
355
+ return
356
+ if second == 'n':
357
+ self.command_handler.edit_task_notes()
358
+ st.input_buffer = ""
359
+ return
360
+ if second == 'o':
361
+ st.sort_by_day = not st.sort_by_day
362
+ msg = "Sorting by day" if st.sort_by_day else "Normal order"
363
+ print(f"\n{CYAN}\u2713 {msg}{RESET}")
364
+ time.sleep(0.3)
365
+ st.input_buffer = ""
366
+ return
367
+ if second == 'w':
368
+ self.command_handler.assign_day()
369
+ st.input_buffer = ""
370
+ return
371
+ if second == 'x':
372
+ self.command_handler.set_status()
373
+ st.input_buffer = ""
374
+ return
375
+ if second == 'y':
376
+ st.show_today_only = not st.show_today_only
377
+ today = get_today_day_name()
378
+ msg = (f"Showing only {today} tasks"
379
+ if st.show_today_only else "Showing all tasks")
380
+ print(f"\n{CYAN}\u2713 {msg}{RESET}")
381
+ time.sleep(0.3)
382
+ st.input_buffer = ""
383
+ return
384
+ if second == 'z':
385
+ self._handle_switch_project()
386
+ st.input_buffer = ""
387
+ return
388
+ if second == 'A':
389
+ self.command_handler.ask_claude()
390
+ st.input_buffer = ""
391
+ return
392
+ if second == 'C':
393
+ self.command_handler.launch_claude()
394
+ st.input_buffer = ""
395
+ return
396
+ if second == 'v':
397
+ self.command_handler.send_to_local_claude()
398
+ st.input_buffer = ""
399
+ return
400
+ if second == 'V':
401
+ self.command_handler.send_to_claude()
402
+ st.input_buffer = ""
403
+ return
404
+ if second == 'X':
405
+ self.command_handler.execute_claude()
406
+ st.input_buffer = ""
407
+ return
408
+ if second == 'I':
409
+ self.command_handler.import_from_github()
410
+ st.input_buffer = ""
411
+ return
412
+ if second == 'H':
413
+ self.command_handler.export_to_github()
414
+ st.input_buffer = ""
415
+ return
416
+ if second == 'D':
417
+ self.command_handler.close_github_issue()
418
+ st.input_buffer = ""
419
+ return
331
420
  # Not a chord — treat '.' as normal input
332
421
  st.input_buffer += '.'
333
422
  if second and second != '.':
@@ -402,10 +491,16 @@ class DispatchMixin:
402
491
  """Handle a keypress in ALL_CATEGORIES mode."""
403
492
  st = self.state
404
493
 
405
- if key == '\x1b' or key == 'L':
494
+ if key == '\x1b':
406
495
  st.mode = MODE_QUICK_ADD
407
496
  return
408
497
 
498
+ if key == '.':
499
+ second = get_key(timeout=0.5)
500
+ if second == 'l':
501
+ st.mode = MODE_QUICK_ADD
502
+ return
503
+
409
504
  filtered = (
410
505
  tasks_to_display
411
506
  if st.show_today_only or st.collapsed_parents
@@ -56,23 +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
- 'E': 'trigger_keyword_action',
69
- 'N': 'edit_task_notes',
70
62
  '\x15': 'open_url', # Ctrl+U
71
- '(': 'ultrathink_task',
72
- ')': 'execute_analysis_task',
73
63
  '$': 'suggest_task',
74
- 'J': 'set_agent_task',
75
- 'B': 'start_priority_timer',
76
64
  '!': 'fix_duplicate_ids_interactive',
77
65
  '@': 'read_tasks_aloud',
78
66
  '#': 'reauthenticate_google_calendar',
@@ -116,6 +104,7 @@ class App(DispatchMixin, AppNavigationMixin):
116
104
  tasks_to_display = self._prepare_tasks()
117
105
  self._render(tasks_to_display)
118
106
  self._needs_render = False
107
+ self._clear_terminal_claude_status()
119
108
  else:
120
109
  tasks_to_display = self._prepare_tasks()
121
110
 
@@ -279,6 +268,7 @@ class App(DispatchMixin, AppNavigationMixin):
279
268
  match_positions=st.match_positions,
280
269
  collapsed_parents=st.collapsed_parents,
281
270
  all_tasks=all_tasks,
271
+ claude_status=self.command_handler._claude_status,
282
272
  )
283
273
 
284
274
  sys.stdout.write('\033[?25h')
@@ -298,6 +288,12 @@ class App(DispatchMixin, AppNavigationMixin):
298
288
  # Auto-refresh & paste
299
289
  # ------------------------------------------------------------------
300
290
 
291
+ def _clear_terminal_claude_status(self):
292
+ """Clear 'done'/'error' statuses after one render cycle."""
293
+ status = self.command_handler._claude_status
294
+ if status and status not in ("thinking...", "editing..."):
295
+ self.command_handler._claude_status = ""
296
+
301
297
  def _handle_auto_refresh(self, key):
302
298
  """Detect external file changes on timeout."""
303
299
  if key is not None:
@@ -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