jott-cli 0.7.0__tar.gz → 0.7.1__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 (104) hide show
  1. {jott_cli-0.7.0/jott_cli.egg-info → jott_cli-0.7.1}/PKG-INFO +1 -1
  2. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/__init__.py +1 -1
  3. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/_dispatch_mixin.py +2 -1
  4. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/app.py +1 -1
  5. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/commands/_claude_mixin.py +10 -0
  6. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/commands/_core_mixin.py +3 -15
  7. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/commands/_notes_mixin.py +6 -1
  8. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/ui/display_footer.py +0 -3
  9. jott_cli-0.7.1/jot/ui/display_help.py +282 -0
  10. {jott_cli-0.7.0 → jott_cli-0.7.1/jott_cli.egg-info}/PKG-INFO +1 -1
  11. {jott_cli-0.7.0 → jott_cli-0.7.1}/pyproject.toml +1 -1
  12. {jott_cli-0.7.0 → jott_cli-0.7.1}/tests/test_command_handler.py +5 -3
  13. jott_cli-0.7.0/jot/ui/display_help.py +0 -233
  14. {jott_cli-0.7.0 → jott_cli-0.7.1}/LICENSE +0 -0
  15. {jott_cli-0.7.0 → jott_cli-0.7.1}/README.md +0 -0
  16. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/_app_navigation_mixin.py +0 -0
  17. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/categories/__init__.py +0 -0
  18. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/categories/config.py +0 -0
  19. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/categories/manager.py +0 -0
  20. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/categories/templates.py +0 -0
  21. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/cli/__init__.py +0 -0
  22. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/cli/archive.py +0 -0
  23. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/cli/config.py +0 -0
  24. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/cli/views.py +0 -0
  25. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/commands/__init__.py +0 -0
  26. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/commands/_ai_analysis_mixin.py +0 -0
  27. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/commands/_ai_suggest_mixin.py +0 -0
  28. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/commands/_audio_timer_mixin.py +0 -0
  29. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/commands/_bulk_mixin.py +0 -0
  30. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/commands/_context_mixin.py +0 -0
  31. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/commands/_gcal_mixin.py +0 -0
  32. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/commands/_github_mixin.py +0 -0
  33. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/commands/_metadata_mixin.py +0 -0
  34. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/commands/_transfer_mixin.py +0 -0
  35. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/commands/_web_clipboard_mixin.py +0 -0
  36. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/commands/handler.py +0 -0
  37. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/core/__init__.py +0 -0
  38. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/core/_age_backlog_mixin.py +0 -0
  39. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/core/_compress_mixin.py +0 -0
  40. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/core/_crud_mixin.py +0 -0
  41. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/core/_delete_mixin.py +0 -0
  42. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/core/_export_mixin.py +0 -0
  43. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/core/_id_migration_mixin.py +0 -0
  44. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/core/_metadata_mixin.py +0 -0
  45. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/core/_navigation_mixin.py +0 -0
  46. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/core/_persistence_mixin.py +0 -0
  47. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/core/_subtask_mixin.py +0 -0
  48. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/core/archive_manager.py +0 -0
  49. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/core/constants.py +0 -0
  50. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/core/id_manager.py +0 -0
  51. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/core/task_manager.py +0 -0
  52. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/integrations/__init__.py +0 -0
  53. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/integrations/gcal/__init__.py +0 -0
  54. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/integrations/gcal/account_manager.py +0 -0
  55. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/integrations/gcal/auth.py +0 -0
  56. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/integrations/gcal/events.py +0 -0
  57. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/integrations/github/__init__.py +0 -0
  58. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/integrations/github/issues.py +0 -0
  59. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/integrations/keywords/__init__.py +0 -0
  60. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/integrations/keywords/_config_mixin.py +0 -0
  61. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/integrations/keywords/_handlers_mixin.py +0 -0
  62. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/integrations/keywords/handler.py +0 -0
  63. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/mcp/__init__.py +0 -0
  64. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/mcp/handlers.py +0 -0
  65. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/mcp/schemas.py +0 -0
  66. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/mcp/server.py +0 -0
  67. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/projects/__init__.py +0 -0
  68. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/projects/backup.py +0 -0
  69. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/projects/registry.py +0 -0
  70. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/ui/__init__.py +0 -0
  71. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/ui/display.py +0 -0
  72. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/ui/display_archive.py +0 -0
  73. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/ui/display_projects.py +0 -0
  74. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/ui/display_tasks.py +0 -0
  75. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/ui/formatting.py +0 -0
  76. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/ui/input.py +0 -0
  77. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/ui/picker.py +0 -0
  78. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/ui/rendering.py +0 -0
  79. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/ui/styles.py +0 -0
  80. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/utils/__init__.py +0 -0
  81. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/utils/date_utils.py +0 -0
  82. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/utils/text_utils.py +0 -0
  83. {jott_cli-0.7.0 → jott_cli-0.7.1}/jot/utils/validation.py +0 -0
  84. {jott_cli-0.7.0 → jott_cli-0.7.1}/jott_cli.egg-info/SOURCES.txt +0 -0
  85. {jott_cli-0.7.0 → jott_cli-0.7.1}/jott_cli.egg-info/dependency_links.txt +0 -0
  86. {jott_cli-0.7.0 → jott_cli-0.7.1}/jott_cli.egg-info/entry_points.txt +0 -0
  87. {jott_cli-0.7.0 → jott_cli-0.7.1}/jott_cli.egg-info/requires.txt +0 -0
  88. {jott_cli-0.7.0 → jott_cli-0.7.1}/jott_cli.egg-info/top_level.txt +0 -0
  89. {jott_cli-0.7.0 → jott_cli-0.7.1}/setup.cfg +0 -0
  90. {jott_cli-0.7.0 → jott_cli-0.7.1}/setup.py +0 -0
  91. {jott_cli-0.7.0 → jott_cli-0.7.1}/tests/test_dispatch.py +0 -0
  92. {jott_cli-0.7.0 → jott_cli-0.7.1}/tests/test_edit_edge_cases.py +0 -0
  93. {jott_cli-0.7.0 → jott_cli-0.7.1}/tests/test_fuzzy_search.py +0 -0
  94. {jott_cli-0.7.0 → jott_cli-0.7.1}/tests/test_gcal_notes.py +0 -0
  95. {jott_cli-0.7.0 → jott_cli-0.7.1}/tests/test_github.py +0 -0
  96. {jott_cli-0.7.0 → jott_cli-0.7.1}/tests/test_highlight.py +0 -0
  97. {jott_cli-0.7.0 → jott_cli-0.7.1}/tests/test_input.py +0 -0
  98. {jott_cli-0.7.0 → jott_cli-0.7.1}/tests/test_jot.py +0 -0
  99. {jott_cli-0.7.0 → jott_cli-0.7.1}/tests/test_picker.py +0 -0
  100. {jott_cli-0.7.0 → jott_cli-0.7.1}/tests/test_styles.py +0 -0
  101. {jott_cli-0.7.0 → jott_cli-0.7.1}/tests/test_subtask_notes.py +0 -0
  102. {jott_cli-0.7.0 → jott_cli-0.7.1}/tests/test_terminal_wrap.py +0 -0
  103. {jott_cli-0.7.0 → jott_cli-0.7.1}/tests/test_today_filter.py +0 -0
  104. {jott_cli-0.7.0 → jott_cli-0.7.1}/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.7.0
3
+ Version: 0.7.1
4
4
  Summary: Feature-rich interactive CLI task manager with AI integration, calendar sync, and keyword automation
5
5
  Author-email: Scott Anderson <sonander@gmail.com>
6
6
  Maintainer-email: Scott Anderson <sonander@gmail.com>
@@ -7,7 +7,7 @@ import importlib.util
7
7
  import os
8
8
  from pathlib import Path
9
9
 
10
- __version__ = "0.7.0"
10
+ __version__ = "0.7.1"
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.
@@ -285,7 +285,8 @@ class DispatchMixin:
285
285
  st.input_buffer = ""
286
286
  return
287
287
  if second == '?':
288
- st.show_shortcuts = not st.show_shortcuts
288
+ from jot.ui.display_help import help_modal
289
+ help_modal()
289
290
  st.input_buffer = ""
290
291
  return
291
292
  if second == 'i':
@@ -37,7 +37,7 @@ class AppState:
37
37
  sort_by_day: bool = False
38
38
  show_today_only: bool = False
39
39
  include_archived_in_search: bool = True
40
- show_shortcuts: bool = False
40
+ show_shortcuts: bool = False # deprecated; kept for API compat
41
41
  show_notes_inline: bool = False
42
42
  running: bool = True
43
43
  selected_tasks: set = field(default_factory=set)
@@ -8,6 +8,15 @@ from datetime import datetime
8
8
 
9
9
  from jot.ui.styles import RESET, CYAN, DIM
10
10
 
11
+ _ORG_OUTPUT_INSTRUCTION = (
12
+ "\n\nIMPORTANT: Format your entire response as org-mode markup:\n"
13
+ "- Code blocks: #+begin_src <lang>\n...\n#+end_src\n"
14
+ "- Inline code: ~symbol~ or =literal=\n"
15
+ "- File references: [[relative/path/to/file][filename.ext]]\n"
16
+ "- Headings: * ** *** etc.\n"
17
+ "Do NOT use markdown backticks or markdown headings."
18
+ )
19
+
11
20
 
12
21
  class ClaudeMixin:
13
22
  """Launch Claude Code in a new tmux window with current task."""
@@ -209,6 +218,7 @@ class ClaudeMixin:
209
218
  f"Existing notes:\n{existing_notes}")
210
219
  else:
211
220
  prompt = task_text
221
+ prompt += _ORG_OUTPUT_INSTRUCTION
212
222
 
213
223
  cmd = [
214
224
  'claude', '-p', '--model', 'sonnet',
@@ -277,21 +277,9 @@ class CoreMixin:
277
277
 
278
278
  @staticmethod
279
279
  def help():
280
- """Show help message"""
281
- print("""
282
- Commands: a Add c Current d Delete D Delete-current e Edit E Edit-current
283
- M Move r Refresh h Help q Quit
284
-
285
- Navigation: ↑/Ctrl+p Up ↓/Ctrl+n Down Shift+↑ Move up Shift+↓ Move down
286
-
287
- Quick-Add: .? Shortcuts .f Notes .n Edit notes .j Agent task
288
- .z Project .l Categories .w Day .x Status
289
- Ctrl+R Register Ctrl+T Transfer .m Move Ctrl+X Quit
290
-
291
- Command Mode (ESC): r Refresh
292
-
293
- Tip: Press '.' to start a leader chord. Press '.?' for full shortcuts.
294
- Full CLI docs: jott --help""")
280
+ """Show help modal with all keyboard shortcuts."""
281
+ from jot.ui.display_help import help_modal
282
+ help_modal()
295
283
  return True
296
284
 
297
285
  def toggle_caps(self):
@@ -4,6 +4,7 @@ import os
4
4
  import shlex
5
5
  import subprocess
6
6
  import tempfile
7
+ from pathlib import Path
7
8
 
8
9
  from jot.ui.styles import RESET, DIM, CYAN, YELLOW
9
10
 
@@ -77,7 +78,11 @@ class NotesMixin:
77
78
  file_extension = self.get_notes_file_extension(editor)
78
79
  is_org_mode = file_extension == '.org'
79
80
 
80
- with tempfile.NamedTemporaryFile(mode='w+', suffix=file_extension, delete=False) as tf:
81
+ work_dir = getattr(self.task_manager, 'project_dir', None) or Path.cwd()
82
+ with tempfile.NamedTemporaryFile(
83
+ mode='w+', suffix=file_extension, delete=False,
84
+ dir=work_dir
85
+ ) as tf:
81
86
  temp_file = tf.name
82
87
  if is_org_mode:
83
88
  tf.write(f"#+TITLE: Notes for task: {task_text}\n")
@@ -35,9 +35,6 @@ def _render_quick_add(input_buffer, show_shortcuts):
35
35
  f"commands | {BOLD}Shift+V{RESET}: multi-select | "
36
36
  f"{BOLD}.?{RESET}: shortcuts"
37
37
  )
38
- if show_shortcuts:
39
- from jot.ui.display_help import display_categorized_shortcuts
40
- display_categorized_shortcuts()
41
38
  print(f"→ {input_buffer}█", end='')
42
39
 
43
40
 
@@ -0,0 +1,282 @@
1
+ """Display functions for help and keyboard shortcuts."""
2
+
3
+ import shutil
4
+ import sys
5
+
6
+ from jot.ui.input import get_key
7
+ from jot.ui.rendering import buffered_output
8
+ from jot.ui.styles import RESET, BOLD, DIM, CYAN, GREEN, YELLOW, get_terminal_width
9
+
10
+
11
+ # ── Shortcut data ──────────────────────────────────────────────────────
12
+
13
+ SHORTCUTS = {
14
+ "Navigation": [
15
+ ("↑/↓", "Navigate tasks"),
16
+ ("Tab", "Cycle through categories"),
17
+ ("Ctrl+F", "Fuzzy search tasks"),
18
+ (".z", "Switch project"),
19
+ ("Ctrl+C", "Switch category"),
20
+ (".l", "Toggle all categories view"),
21
+ ],
22
+ "Task Management": [
23
+ ("Enter", "Add task"),
24
+ ("Ctrl+E", "Edit current task"),
25
+ ("c", "Mark current task"),
26
+ ("d", "Delete task"),
27
+ (".d", "Duplicate task"),
28
+ ("t", "Toggle task done"),
29
+ ("a", "Archive task"),
30
+ (".a", "Toggle archive view"),
31
+ ("m", "Move to different category"),
32
+ (".m", "Move task to another project"),
33
+ (".k", "Copy task to another project"),
34
+ ("Ctrl+T", "Transfer to category"),
35
+ ("Ctrl+↑/↓", "Reorder tasks"),
36
+ ("=", "Sort by priority"),
37
+ ],
38
+ "Task Details": [
39
+ ("n", "Add/edit notes"),
40
+ (".n", "Edit task notes"),
41
+ (".f", "Toggle inline notes display"),
42
+ ("p", "Toggle priority cycle"),
43
+ (".h", "Set priority to high"),
44
+ (".x", "Set task status"),
45
+ (".w", "Assign day to task"),
46
+ (".o", "Toggle day sorting"),
47
+ (".y", "Toggle today filter"),
48
+ (".u", "Toggle ALL CAPS"),
49
+ ("Ctrl+D", "Mark current as done"),
50
+ (".j", "Mark as agent task"),
51
+ ("*", "Highlight color picker"),
52
+ ("~", "Quick highlight toggle"),
53
+ ("Ctrl+L", "Assign parent (link subtask)"),
54
+ ],
55
+ "Claude Code": [
56
+ (".A", "Ask Claude (read-only, writes to notes)"),
57
+ (".X", "Execute Claude (edit permissions)"),
58
+ (".C", "Launch Claude Code with task (tmux)"),
59
+ (".v", "Send task to local Claude session"),
60
+ (".V", "Send task to any Claude session"),
61
+ ],
62
+ "Export & Import": [
63
+ (".e", "Execute keyword action"),
64
+ (".b", "Priority timer"),
65
+ (".G", "Export to Google Calendar"),
66
+ (".i", "Import from Google Calendar"),
67
+ (".c", "Copy task to clipboard"),
68
+ ("Ctrl+U", "Open URLs in task"),
69
+ ("Shift+1", "Fix duplicate task IDs"),
70
+ ("Shift+4", "AI task suggestion"),
71
+ ],
72
+ "GitHub": [
73
+ (".I", "Import issues from GitHub"),
74
+ (".H", "Export task as GitHub issue"),
75
+ (".D", "Close linked GitHub issue"),
76
+ ],
77
+ "System": [
78
+ (".?", "Toggle this help"),
79
+ ("r", "Refresh display"),
80
+ ("Ctrl+R", "Register current project"),
81
+ ("q/Ctrl+X", "Quit"),
82
+ ("Esc", "Exit mode / Quit"),
83
+ ],
84
+ }
85
+
86
+
87
+ def _get_terminal_height():
88
+ """Return current terminal height, defaulting to 24."""
89
+ try:
90
+ return shutil.get_terminal_size().lines
91
+ except (ValueError, OSError):
92
+ return 24
93
+
94
+
95
+ # ── Build lines for the modal ──────────────────────────────────────────
96
+
97
+ def _build_help_lines():
98
+ """Build a list of (line_string, is_header) tuples for the modal body."""
99
+ lines = []
100
+ first = True
101
+ for category, items in SHORTCUTS.items():
102
+ if not first:
103
+ lines.append(("", False))
104
+ first = False
105
+ lines.append((category, True))
106
+ for key, desc in items:
107
+ lines.append((f" {YELLOW}{key:<12}{RESET} {desc}", False))
108
+ return lines
109
+
110
+
111
+ # ── Modal rendering ────────────────────────────────────────────────────
112
+
113
+ def help_modal():
114
+ """Show a centered, scrollable help modal. Blocks until dismissed.
115
+
116
+ Controls:
117
+ ↑/↓ or j/k Scroll one line
118
+ PgUp/PgDn Scroll one page
119
+ Home/End Jump to top/bottom
120
+ ESC/q/? Dismiss
121
+ """
122
+ lines = _build_help_lines()
123
+ scroll = 0
124
+
125
+ while True:
126
+ term_w = get_terminal_width()
127
+ term_h = _get_terminal_height()
128
+
129
+ # Box dimensions: 70% width, 85% height, centered
130
+ box_w = max(40, min(int(term_w * 0.70), term_w - 4))
131
+ box_h = max(10, min(int(term_h * 0.85), term_h - 2))
132
+ inner_w = box_w - 4 # 2 border + 2 padding
133
+ visible_rows = box_h - 4 # top border + title + bottom border + footer
134
+
135
+ max_scroll = max(0, len(lines) - visible_rows)
136
+ scroll = max(0, min(scroll, max_scroll))
137
+
138
+ # Centering offsets
139
+ pad_left = (term_w - box_w) // 2
140
+ pad_top = (term_h - box_h) // 2
141
+ margin = " " * pad_left
142
+
143
+ with buffered_output():
144
+ sys.stdout.write('\033[?25l\033[H\033[J') # hide cursor, clear
145
+
146
+ # Top padding
147
+ for _ in range(pad_top):
148
+ sys.stdout.write('\n')
149
+
150
+ # Top border
151
+ title = " Keybindings "
152
+ side = box_w - 2 - len(title)
153
+ left_bar = side // 2
154
+ right_bar = side - left_bar
155
+ sys.stdout.write(
156
+ f"{margin}{CYAN}┌{'─' * left_bar}{BOLD}{title}"
157
+ f"{RESET}{CYAN}{'─' * right_bar}┐{RESET}\n"
158
+ )
159
+
160
+ # Scroll indicator
161
+ if max_scroll > 0:
162
+ pct = int(scroll / max_scroll * 100) if max_scroll else 0
163
+ scroll_info = f" {scroll + 1}-{min(scroll + visible_rows, len(lines))}/{len(lines)} "
164
+ else:
165
+ scroll_info = ""
166
+
167
+ # Visible content rows
168
+ window = lines[scroll:scroll + visible_rows]
169
+ for i, (line_text, is_header) in enumerate(window):
170
+ if is_header:
171
+ content = f"{CYAN}{BOLD}{line_text}{RESET}"
172
+ else:
173
+ content = line_text
174
+
175
+ # Pad content to fill the box width (strip ANSI for measuring)
176
+ from jot.ui.styles import visible_len
177
+ vis = visible_len(content)
178
+ pad = max(0, inner_w - vis)
179
+ sys.stdout.write(
180
+ f"{margin}{CYAN}│{RESET} {content}{' ' * pad} "
181
+ f"{CYAN}│{RESET}\n"
182
+ )
183
+
184
+ # Fill remaining rows if content is shorter than visible area
185
+ for _ in range(visible_rows - len(window)):
186
+ sys.stdout.write(
187
+ f"{margin}{CYAN}│{RESET} {' ' * inner_w} "
188
+ f"{CYAN}│{RESET}\n"
189
+ )
190
+
191
+ # Footer row (scroll hints)
192
+ if max_scroll > 0:
193
+ hint = f"{DIM}j/k scroll q/ESC close{RESET} {DIM}{scroll_info}{RESET}"
194
+ else:
195
+ hint = f"{DIM}q/ESC close{RESET}"
196
+ hint_vis = visible_len(hint)
197
+ hint_pad = max(0, inner_w - hint_vis)
198
+ sys.stdout.write(
199
+ f"{margin}{CYAN}│{RESET} {hint}{' ' * hint_pad} "
200
+ f"{CYAN}│{RESET}\n"
201
+ )
202
+
203
+ # Bottom border
204
+ sys.stdout.write(
205
+ f"{margin}{CYAN}└{'─' * (box_w - 2)}┘{RESET}\n"
206
+ )
207
+
208
+ # Wait for input
209
+ key = get_key()
210
+ if key is None:
211
+ continue
212
+
213
+ # Scroll keys first — must precede dismiss check so arrow
214
+ # keys scroll instead of being swallowed by ESC handling.
215
+ if key in ('UP', 'k'):
216
+ scroll = max(0, scroll - 1)
217
+ continue
218
+ if key in ('DOWN', 'j'):
219
+ scroll = min(max_scroll, scroll + 1)
220
+ continue
221
+
222
+ # Dismiss keys
223
+ if key in ('\x1b', 'q', '?', '\r', '\n'):
224
+ break
225
+ if key == 'SHIFT_UP':
226
+ scroll = max(0, scroll - visible_rows)
227
+ continue
228
+ if key == 'SHIFT_DOWN':
229
+ scroll = min(max_scroll, scroll + visible_rows)
230
+ continue
231
+ if key == 'g':
232
+ scroll = 0
233
+ continue
234
+ if key == 'G':
235
+ scroll = max_scroll
236
+ continue
237
+
238
+ sys.stdout.write('\033[?25h')
239
+ sys.stdout.flush()
240
+
241
+
242
+ # ── Legacy functions (kept for CLI --help and tests) ───────────────────
243
+
244
+ def display_categorized_shortcuts():
245
+ """Display keyboard shortcuts organized by category (non-modal)."""
246
+ w = get_terminal_width()
247
+ print(f"\n{BOLD}Keyboard Shortcuts{RESET}")
248
+ print("=" * w)
249
+
250
+ for category, items in SHORTCUTS.items():
251
+ print(f"\n{CYAN}{category}:{RESET}")
252
+ for key, description in items:
253
+ print(f" {YELLOW}{key:15}{RESET} {description}")
254
+
255
+ print("\n" + "=" * w + "\n")
256
+
257
+
258
+ def display_help():
259
+ """Display comprehensive help information."""
260
+ help_text = f"""
261
+ {BOLD}Jott - Simple Interactive Task List{RESET}
262
+ {DIM}Version 0.7.1{RESET}
263
+
264
+ {BOLD}BASIC USAGE:{RESET}
265
+ jott Start interactive task manager (current project)
266
+ jott -c <category> Start in specific category
267
+ jott --category <cat> Start in specific category (long form)
268
+ jott --global Start with global tasks
269
+ jott -h / --help Show this help message
270
+
271
+ {BOLD}PROJECT MANAGEMENT:{RESET}
272
+ jott --all-projects Show tasks from all registered projects
273
+ jott --list-projects List all registered projects
274
+ jott --register <name> <path> Register a new project
275
+ jott --unregister <name> Remove project from registry
276
+ jott --refresh Refresh project registry (auto-discover)
277
+ jott --fix-duplicates Fix duplicate task IDs in current directory
278
+
279
+ For keyboard shortcuts, press {CYAN}.?{RESET} inside the interactive TUI.
280
+
281
+ {DIM}For more info: https://github.com/son1112/jot{RESET}"""
282
+ print(help_text)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jott-cli
3
- Version: 0.7.0
3
+ Version: 0.7.1
4
4
  Summary: Feature-rich interactive CLI task manager with AI integration, calendar sync, and keyword automation
5
5
  Author-email: Scott Anderson <sonander@gmail.com>
6
6
  Maintainer-email: Scott Anderson <sonander@gmail.com>
@@ -7,7 +7,7 @@ include = ["jot", "jot.*"]
7
7
 
8
8
  [project]
9
9
  name = "jott-cli"
10
- version = "0.7.0"
10
+ version = "0.7.1"
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"
@@ -46,13 +46,15 @@ class TestCommandHandlerBasics:
46
46
  assert len(tm.tasks) == initial_count + 1
47
47
 
48
48
  def test_help(self):
49
- """Test help displays command list"""
49
+ """Test help opens modal and returns True"""
50
50
  with tempfile.TemporaryDirectory() as tmpdir:
51
51
  tm = TaskManager(directory=Path(tmpdir))
52
52
  handler = CommandHandler(tm)
53
53
 
54
- # Should return True and not crash
55
- result = handler.help()
54
+ # Mock help_modal to skip the interactive modal
55
+ help_mod = sys.modules['jot.ui.display_help']
56
+ with patch.object(help_mod, 'help_modal'):
57
+ result = handler.help()
56
58
 
57
59
  assert result is True
58
60
 
@@ -1,233 +0,0 @@
1
- """Display functions for help and keyboard shortcuts."""
2
-
3
- from jot.ui.styles import RESET, BOLD, DIM, CYAN, GREEN, YELLOW, get_terminal_width
4
-
5
-
6
- def display_categorized_shortcuts():
7
- """Display keyboard shortcuts organized by category."""
8
- shortcuts = {
9
- "Navigation": [
10
- ("↑/↓", "Navigate tasks"),
11
- ("Tab", "Cycle through categories"),
12
- ("Ctrl+F", "Fuzzy search tasks"),
13
- (".z", "Switch project"),
14
- ("Ctrl+C", "Switch category"),
15
- (".l", "Toggle all categories view"),
16
- ],
17
- "Task Management": [
18
- ("Enter", "Add task"),
19
- ("Ctrl+E", "Edit current task"),
20
- ("c", "Mark current task"),
21
- ("d", "Delete task"),
22
- (".d", "Duplicate task"),
23
- ("t", "Toggle task done"),
24
- ("a", "Archive task"),
25
- (".a", "Toggle archive view"),
26
- ("m", "Move to different category"),
27
- (".m", "Move task to another project"),
28
- (".k", "Copy task to another project"),
29
- ("Ctrl+T", "Transfer to category"),
30
- ("Ctrl+↑/↓", "Reorder tasks"),
31
- ("=", "Sort by priority"),
32
- ],
33
- "Task Details": [
34
- ("n", "Add/edit notes"),
35
- (".n", "Edit task notes"),
36
- (".f", "Toggle inline notes display"),
37
- ("p", "Toggle priority cycle"),
38
- (".h", "Set priority to high"),
39
- ("=", "Sort by priority"),
40
- (".x", "Set task status"),
41
- (".w", "Assign day to task"),
42
- (".o", "Toggle day sorting"),
43
- (".y", "Toggle today filter"),
44
- (".u", "Toggle ALL CAPS"),
45
- ("Ctrl+D", "Mark current as done"),
46
- (".j", "Mark as agent task"),
47
- ("*", "Highlight color picker"),
48
- ("~", "Quick highlight toggle"),
49
- ("Ctrl+L", "Assign parent (link subtask)"),
50
- ],
51
- "Analysis & Export": [
52
- ("Shift+1", "Fix duplicate task IDs"),
53
- ("Shift+4", "AI task suggestion"),
54
- (".A", "Ask Claude (read-only, writes to notes)"),
55
- (".X", "Execute Claude (edit permissions, uses notes)"),
56
- (".C", "Launch Claude Code with task"),
57
- (".v", "Send task to local Claude session"),
58
- (".V", "Send task to any Claude session"),
59
- (".e", "Execute keyword action"),
60
- (".b", "Priority timer"),
61
- (".G", "Export to Google Calendar"),
62
- (".i", "Import from Google Calendar"),
63
- (".c", "Copy task to clipboard"),
64
- ("Ctrl+U", "Open URLs in task"),
65
- ],
66
- "GitHub": [
67
- (".I", "Import issues from GitHub"),
68
- (".H", "Export task as GitHub issue"),
69
- (".D", "Close linked GitHub issue"),
70
- ],
71
- "System": [
72
- ("h/.?", "Show this help"),
73
- ("r", "Refresh display"),
74
- ("Ctrl+R", "Register current project"),
75
- ("q", "Quit"),
76
- ("Ctrl+X", "Quit"),
77
- ("Esc", "Exit mode / Quit"),
78
- ],
79
- "Fuzzy Search": [
80
- ("Ctrl+A", "Toggle archived tasks in search"),
81
- ],
82
- }
83
-
84
- w = get_terminal_width()
85
- print(f"\n{BOLD}Keyboard Shortcuts{RESET}")
86
- print("=" * w)
87
-
88
- for category, items in shortcuts.items():
89
- print(f"\n{CYAN}{category}:{RESET}")
90
- for key, description in items:
91
- print(f" {YELLOW}{key:15}{RESET} {description}")
92
-
93
- print("\n" + "=" * w + "\n")
94
-
95
-
96
- def display_help():
97
- """Display comprehensive help information."""
98
- help_text = f"""
99
- {BOLD}Jott - Simple Interactive Task List{RESET}
100
- {DIM}Version 0.7.0{RESET}
101
-
102
- {BOLD}BASIC USAGE:{RESET}
103
- jott Start interactive task manager (current project)
104
- jott -c <category> Start in specific category
105
- jott --category <cat> Start in specific category (long form)
106
- jott --global Start with global tasks
107
- jott -h / --help Show this help message
108
-
109
- {BOLD}PROJECT MANAGEMENT:{RESET}
110
- jott --all-projects Show tasks from all registered projects
111
- jott --list-projects List all registered projects
112
- jott --register <name> <path> Register a new project
113
- jott --unregister <name> Remove project from registry
114
- jott --refresh Refresh project registry (auto-discover)
115
- jott --fix-duplicates Fix duplicate task IDs in current directory
116
-
117
- {BOLD}NAVIGATION:{RESET}
118
- {CYAN}↑/↓{RESET} Navigate through tasks
119
- {CYAN}Tab{RESET} Cycle through categories
120
- {CYAN}Ctrl+F{RESET} Fuzzy search tasks
121
- {CYAN}.z{RESET} Switch between projects
122
- {CYAN}Ctrl+C{RESET} Switch between categories
123
- {CYAN}.l{RESET} Toggle all categories view
124
-
125
- {BOLD}TASK MANAGEMENT:{RESET}
126
- {CYAN}Type + Enter{RESET} Add new task
127
- {CYAN}Ctrl+E{RESET} Edit current task text
128
- {CYAN}c{RESET} Mark task as current
129
- {CYAN}d{RESET} Delete selected task
130
- {CYAN}.d{RESET} Duplicate current task
131
- {CYAN}t{RESET} Toggle task done/undone
132
- {CYAN}a{RESET} Archive task (mark as done/canceled)
133
- {CYAN}.a{RESET} Toggle archive view
134
- {CYAN}m{RESET} Move task to different category
135
- {CYAN}.m{RESET} Move task to another project
136
- {CYAN}.k{RESET} Copy task to another project
137
- {CYAN}Ctrl+T{RESET} Transfer task to category
138
- {CYAN}Ctrl+↑/↓{RESET} Reorder tasks
139
- {CYAN}={RESET} Sort by priority
140
-
141
- {BOLD}TASK DETAILS:{RESET}
142
- {CYAN}n{RESET} Add/edit task notes (opens editor)
143
- {CYAN}.n{RESET} Edit task notes
144
- {CYAN}.f{RESET} Toggle inline notes display
145
- {CYAN}p{RESET} Toggle task priority cycle
146
- {CYAN}.h{RESET} Set priority to high
147
- {CYAN}.x{RESET} Set task status (todo/in-progress/blocked/done)
148
- {CYAN}.w{RESET} Assign day to task
149
- {CYAN}.o{RESET} Toggle day sorting
150
- {CYAN}.y{RESET} Toggle today filter
151
- {CYAN}.u{RESET} Toggle ALL CAPS
152
- {CYAN}Ctrl+D{RESET} Mark current task as done
153
- {CYAN}.j{RESET} Mark task for agent/AI assistance
154
- {CYAN}*{RESET} Highlight color picker (6 colors)
155
- {CYAN}~{RESET} Quick highlight toggle (default color)
156
- {CYAN}Ctrl+L{RESET} Assign parent task (subtask link)
157
-
158
- {BOLD}KEYWORDS:{RESET}
159
- Keywords trigger automatic actions when added to task text:
160
-
161
- {YELLOW}bullet:{RESET} task Start bullet priority timer
162
- {YELLOW}gcal:{RESET} task Export to Google Calendar
163
- {YELLOW}ai:{RESET} task Mark for AI/agent assistance
164
- {YELLOW}analyze:{RESET} task Create analysis plan with Claude Code
165
- {YELLOW}remind:{RESET} task Set system notification (.e)
166
-
167
- Configure keywords in ~/.jot-keywords.json
168
-
169
- {BOLD}CATEGORIES:{RESET}
170
- Categories organize tasks within projects:
171
- • Local categories: Project-specific (max 10 per project)
172
- • Global categories: Cross-project tasks (max 10 global)
173
- • Default: Tasks without a category
174
-
175
- {CYAN}jott -c backend{RESET} Start in "backend" category
176
- {CYAN}Tab{RESET} Cycle through categories
177
- {CYAN}Ctrl+C{RESET} Create/switch categories interactively
178
- {CYAN}m{RESET} Move task between categories
179
-
180
- {BOLD}PROJECTS:{RESET}
181
- Multi-project support with auto-discovery:
182
- • Auto-discovers projects in ~/projects/
183
- • Each project has its own .jot.json
184
- • Switch projects with {CYAN}.z{RESET} or {CYAN}jott -p <name>{RESET}
185
-
186
- {BOLD}ANALYSIS & EXPORT:{RESET}
187
- {CYAN}Shift+1{RESET} Fix duplicate task IDs
188
- {CYAN}Shift+4{RESET} AI task suggestion
189
- {CYAN}.A{RESET} Ask Claude (read-only, writes to task notes)
190
- {CYAN}.X{RESET} Execute Claude (edit permissions, uses notes)
191
- {CYAN}.C{RESET} Launch Claude Code with current task (tmux)
192
- {CYAN}.v{RESET} Send task to local Claude session
193
- {CYAN}.V{RESET} Send task to any Claude session (picker)
194
- {CYAN}.e{RESET} Execute keyword action for task
195
- {CYAN}.b{RESET} Priority timer
196
- {CYAN}.G{RESET} Export to Google Calendar
197
- {CYAN}.i{RESET} Import tasks from Google Calendar
198
- {CYAN}.c{RESET} Copy task text to clipboard
199
- {CYAN}Ctrl+U{RESET} Open URLs found in task text
200
-
201
- {BOLD}GITHUB:{RESET}
202
- {CYAN}.I{RESET} Import issues from GitHub
203
- {CYAN}.H{RESET} Export current task as GitHub issue
204
- {CYAN}.D{RESET} Close linked GitHub issue
205
-
206
- {BOLD}FUZZY SEARCH:{RESET}
207
- {CYAN}Ctrl+F{RESET} Enter fuzzy search mode
208
- {CYAN}Ctrl+A{RESET} Toggle archived tasks in search results
209
- {CYAN}↑/↓{RESET} Navigate results
210
- {CYAN}Enter{RESET} Select task
211
- {CYAN}Esc{RESET} Exit search
212
-
213
- {BOLD}SYSTEM:{RESET}
214
- {CYAN}h or .?{RESET} Show keyboard shortcuts
215
- {CYAN}r{RESET} Refresh display
216
- {CYAN}Ctrl+R{RESET} Register current project in registry
217
- {CYAN}q{RESET} Quit
218
- {CYAN}Ctrl+X{RESET} Quit
219
- {CYAN}Esc{RESET} Exit current mode / Quit
220
-
221
- {BOLD}FILES:{RESET}
222
- {GREEN}.jot.json{RESET} Task storage (per project)
223
- {GREEN}~/.jot-categories/<name>.json{RESET} Global category tasks
224
- {GREEN}~/.jot-keywords.json{RESET} Keyword configuration
225
- {GREEN}~/.jot-project-registry.json{RESET} Project discovery cache
226
-
227
- {BOLD}CONFIGURATION:{RESET}
228
- • Edit ~/.jot-keywords.json to customize keyword actions
229
- • Category colors: Edit category config (Ctrl+C)
230
- • Global vs local: Use -g flag or create global categories
231
-
232
- {DIM}For more info: https://github.com/your-repo/jot{RESET}"""
233
- print(help_text)
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