jott-cli 0.8.1__tar.gz → 0.8.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {jott_cli-0.8.1/jott_cli.egg-info → jott_cli-0.8.2}/PKG-INFO +1 -1
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/__init__.py +3 -13
- jott_cli-0.8.2/jot/main.py +227 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/ui/display_help.py +2 -1
- {jott_cli-0.8.1 → jott_cli-0.8.2/jott_cli.egg-info}/PKG-INFO +1 -1
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jott_cli.egg-info/SOURCES.txt +2 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/pyproject.toml +1 -1
- {jott_cli-0.8.1 → jott_cli-0.8.2}/tests/test_jot.py +1 -8
- jott_cli-0.8.2/tests/test_main_entry_point.py +84 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/LICENSE +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/README.md +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/_app_navigation_mixin.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/_dispatch_mixin.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/app.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/categories/__init__.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/categories/config.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/categories/manager.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/categories/templates.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/cli/__init__.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/cli/archive.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/cli/config.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/cli/views.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/commands/__init__.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/commands/_ai_analysis_mixin.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/commands/_ai_suggest_mixin.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/commands/_audio_timer_mixin.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/commands/_bulk_mixin.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/commands/_claude_mixin.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/commands/_context_mixin.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/commands/_core_mixin.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/commands/_gcal_mixin.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/commands/_github_mixin.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/commands/_metadata_mixin.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/commands/_notes_mixin.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/commands/_transfer_mixin.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/commands/_web_clipboard_mixin.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/commands/handler.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/core/__init__.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/core/_age_backlog_mixin.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/core/_compress_mixin.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/core/_crud_mixin.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/core/_delete_mixin.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/core/_export_mixin.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/core/_id_migration_mixin.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/core/_metadata_mixin.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/core/_navigation_mixin.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/core/_persistence_mixin.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/core/_subtask_mixin.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/core/archive_manager.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/core/constants.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/core/id_manager.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/core/task_manager.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/integrations/__init__.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/integrations/gcal/__init__.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/integrations/gcal/account_manager.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/integrations/gcal/auth.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/integrations/gcal/events.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/integrations/github/__init__.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/integrations/github/issues.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/integrations/keywords/__init__.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/integrations/keywords/_config_mixin.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/integrations/keywords/_handlers_mixin.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/integrations/keywords/handler.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/mcp/__init__.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/mcp/handlers.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/mcp/schemas.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/mcp/server.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/projects/__init__.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/projects/backup.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/projects/registry.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/ui/__init__.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/ui/display.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/ui/display_archive.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/ui/display_footer.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/ui/display_projects.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/ui/display_tasks.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/ui/formatting.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/ui/input.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/ui/picker.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/ui/rendering.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/ui/styles.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/utils/__init__.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/utils/date_utils.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/utils/text_utils.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jot/utils/validation.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jott_cli.egg-info/dependency_links.txt +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jott_cli.egg-info/entry_points.txt +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jott_cli.egg-info/requires.txt +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/jott_cli.egg-info/top_level.txt +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/setup.cfg +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/setup.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/tests/test_claude_org_prompt.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/tests/test_command_handler.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/tests/test_deleted_notes_dir.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/tests/test_dispatch.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/tests/test_edit_edge_cases.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/tests/test_fuzzy_search.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/tests/test_gcal_notes.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/tests/test_github.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/tests/test_highlight.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/tests/test_input.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/tests/test_notes_tempfile_location.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/tests/test_picker.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/tests/test_state_palette.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/tests/test_styles.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/tests/test_subtask_notes.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/tests/test_terminal_wrap.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/tests/test_today_filter.py +0 -0
- {jott_cli-0.8.1 → jott_cli-0.8.2}/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.8.
|
|
3
|
+
Version: 0.8.2
|
|
4
4
|
Summary: Feature-rich interactive CLI task manager with AI integration, calendar sync, and keyword automation
|
|
5
5
|
Author-email: Scott Anderson <sonander@gmail.com>
|
|
6
6
|
Maintainer-email: Scott Anderson <sonander@gmail.com>
|
|
@@ -3,18 +3,15 @@
|
|
|
3
3
|
This package provides modular components for the jott task management system.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
-
import importlib.util
|
|
7
6
|
import os
|
|
8
|
-
from pathlib import Path
|
|
9
7
|
|
|
10
|
-
__version__ = "0.8.
|
|
8
|
+
__version__ = "0.8.2"
|
|
11
9
|
|
|
12
|
-
# When building Sphinx docs, skip the
|
|
10
|
+
# When building Sphinx docs, skip the heavy runtime imports.
|
|
13
11
|
# Set JOT_SPHINX_BUILD=1 in docs/sphinx/conf.py before importing jot.
|
|
14
12
|
if os.environ.get('JOT_SPHINX_BUILD'):
|
|
15
13
|
main = None
|
|
16
14
|
else:
|
|
17
|
-
# Import extracted classes from their new modules
|
|
18
15
|
from jot.core.id_manager import IDManager
|
|
19
16
|
from jot.core.task_manager import TaskManager
|
|
20
17
|
from jot.projects.registry import ProjectRegistry
|
|
@@ -27,14 +24,7 @@ else:
|
|
|
27
24
|
from jot.integrations.gcal.events import create_gcal_event, fetch_gcal_events
|
|
28
25
|
from jot.integrations.keywords.handler import KeywordHandler
|
|
29
26
|
|
|
30
|
-
|
|
31
|
-
_jot_script_path = Path(__file__).parent.parent / 'jot.py'
|
|
32
|
-
_spec = importlib.util.spec_from_file_location("_jot_script", _jot_script_path)
|
|
33
|
-
_jot_script = importlib.util.module_from_spec(_spec)
|
|
34
|
-
_spec.loader.exec_module(_jot_script)
|
|
35
|
-
|
|
36
|
-
# Export main function from jot.py
|
|
37
|
-
main = _jot_script.main
|
|
27
|
+
from jot.main import main
|
|
38
28
|
|
|
39
29
|
__all__ = [
|
|
40
30
|
'main',
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"""jott entry point.
|
|
2
|
+
|
|
3
|
+
Hosts main() and the CLI argument plumbing previously in the
|
|
4
|
+
repo-root jot.py script. The console-script entry point in
|
|
5
|
+
pyproject.toml resolves through jot.__init__, which re-exports
|
|
6
|
+
main from this module.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import sys
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
from jot.core.task_manager import TaskManager
|
|
14
|
+
from jot.projects.registry import ProjectRegistry
|
|
15
|
+
from jot.categories.manager import CategoryManager
|
|
16
|
+
from jot.commands import CommandHandler
|
|
17
|
+
from jot.cli import handle_cli_args
|
|
18
|
+
from jot.ui import display_help
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def load_auto_backlog_config():
|
|
22
|
+
"""Load auto-backlog configuration from ~/.jot-config.json"""
|
|
23
|
+
config_file = Path.home() / '.jot-config.json'
|
|
24
|
+
default_config = {
|
|
25
|
+
'enabled': True,
|
|
26
|
+
'age_threshold_days': 30,
|
|
27
|
+
'backlog_category': 'backlog',
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if not config_file.exists():
|
|
31
|
+
return default_config
|
|
32
|
+
|
|
33
|
+
try:
|
|
34
|
+
with open(config_file, 'r') as f:
|
|
35
|
+
config = json.load(f)
|
|
36
|
+
result = default_config.copy()
|
|
37
|
+
result.update(config.get('auto_backlog', {}))
|
|
38
|
+
return result
|
|
39
|
+
except (json.JSONDecodeError, IOError):
|
|
40
|
+
return default_config
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _parse_flags(args):
|
|
44
|
+
"""Extract --all, --global, --category/-c flags from args.
|
|
45
|
+
|
|
46
|
+
Returns (args, broadcast_all, explicit_global, category) with
|
|
47
|
+
consumed flags removed from args.
|
|
48
|
+
"""
|
|
49
|
+
broadcast_all = '--all' in args
|
|
50
|
+
if broadcast_all:
|
|
51
|
+
args.remove('--all')
|
|
52
|
+
|
|
53
|
+
explicit_global = '--global' in args
|
|
54
|
+
if explicit_global:
|
|
55
|
+
args.remove('--global')
|
|
56
|
+
|
|
57
|
+
category = None
|
|
58
|
+
for flag in ('--category', '-c'):
|
|
59
|
+
if flag in args:
|
|
60
|
+
idx = args.index(flag)
|
|
61
|
+
if idx + 1 < len(args):
|
|
62
|
+
category = args[idx + 1]
|
|
63
|
+
args = args[:idx] + args[idx + 2 :]
|
|
64
|
+
break
|
|
65
|
+
|
|
66
|
+
return args, broadcast_all, explicit_global, category
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _resolve_global(category, explicit_global, target_dir):
|
|
70
|
+
"""Determine whether a category should be global or local."""
|
|
71
|
+
if not category:
|
|
72
|
+
return False
|
|
73
|
+
|
|
74
|
+
if explicit_global:
|
|
75
|
+
return True
|
|
76
|
+
|
|
77
|
+
cat_manager = CategoryManager(project_dir=target_dir or Path.cwd())
|
|
78
|
+
location = cat_manager.resolve_category_location(category)
|
|
79
|
+
|
|
80
|
+
if location == 'global':
|
|
81
|
+
return True
|
|
82
|
+
if location == 'local':
|
|
83
|
+
return False
|
|
84
|
+
|
|
85
|
+
# New category — create locally by default
|
|
86
|
+
can_create, error_msg = cat_manager.can_create_category(category)
|
|
87
|
+
if not can_create:
|
|
88
|
+
print(f"✗ {error_msg}")
|
|
89
|
+
sys.exit(1)
|
|
90
|
+
|
|
91
|
+
warning_msg = cat_manager.get_warning_message(category)
|
|
92
|
+
if warning_msg:
|
|
93
|
+
print(warning_msg)
|
|
94
|
+
|
|
95
|
+
return False
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _resolve_project_and_task(args, registry):
|
|
99
|
+
"""Parse remaining args into (target_dir, project_name, initial_task)."""
|
|
100
|
+
target_dir = None
|
|
101
|
+
project_name = None
|
|
102
|
+
initial_task = None
|
|
103
|
+
|
|
104
|
+
if args:
|
|
105
|
+
project_path = registry.get_project_path(args[0])
|
|
106
|
+
if project_path:
|
|
107
|
+
target_dir = project_path
|
|
108
|
+
project_name = args[0]
|
|
109
|
+
if len(args) > 1:
|
|
110
|
+
initial_task = ' '.join(args[1:])
|
|
111
|
+
else:
|
|
112
|
+
initial_task = ' '.join(args)
|
|
113
|
+
|
|
114
|
+
return target_dir, project_name, initial_task
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _handle_initial_task(
|
|
118
|
+
task_manager, initial_task, broadcast_all, registry, project_name, is_global, category
|
|
119
|
+
):
|
|
120
|
+
"""Add a task supplied on the command line and exit."""
|
|
121
|
+
if initial_task and broadcast_all:
|
|
122
|
+
projects = registry.list_projects()
|
|
123
|
+
if not projects:
|
|
124
|
+
print("✗ No registered projects found. Use --register to add projects.")
|
|
125
|
+
return
|
|
126
|
+
|
|
127
|
+
n = len(projects)
|
|
128
|
+
confirm = input(f"⚠ Broadcast \"{initial_task}\" to {n} projects? (y/N): ").strip().lower()
|
|
129
|
+
if confirm != 'y':
|
|
130
|
+
print("Cancelled.")
|
|
131
|
+
return
|
|
132
|
+
|
|
133
|
+
added = 0
|
|
134
|
+
seen_paths = set()
|
|
135
|
+
for _, proj_path in projects.items():
|
|
136
|
+
resolved = str(Path(proj_path).resolve())
|
|
137
|
+
if resolved in seen_paths:
|
|
138
|
+
continue
|
|
139
|
+
seen_paths.add(resolved)
|
|
140
|
+
tm = TaskManager(directory=proj_path, project_registry=registry)
|
|
141
|
+
all_texts = [t['text'] for t in tm.tasks + tm.archived]
|
|
142
|
+
if initial_task in all_texts:
|
|
143
|
+
continue
|
|
144
|
+
tm.add_task(initial_task)
|
|
145
|
+
added += 1
|
|
146
|
+
print(f"✓ Added task to {added}/{len(projects)} projects: {initial_task}")
|
|
147
|
+
return
|
|
148
|
+
|
|
149
|
+
if initial_task:
|
|
150
|
+
task_manager.add_task(initial_task)
|
|
151
|
+
location = project_name or Path.cwd().name
|
|
152
|
+
|
|
153
|
+
if is_global and category:
|
|
154
|
+
print(f"✓ Added task to global ({category}): {initial_task}")
|
|
155
|
+
elif category:
|
|
156
|
+
print(f"✓ Added task to {location} ({category}): {initial_task}")
|
|
157
|
+
else:
|
|
158
|
+
print(f"✓ Added task to {location}: {initial_task}")
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def main():
|
|
162
|
+
"""Entry point: parse flags, dispatch CLI commands, or start TUI."""
|
|
163
|
+
registry = ProjectRegistry()
|
|
164
|
+
args = sys.argv[1:]
|
|
165
|
+
|
|
166
|
+
# --help is always checked first
|
|
167
|
+
if '--help' in args or '-h' in args:
|
|
168
|
+
display_help()
|
|
169
|
+
return
|
|
170
|
+
|
|
171
|
+
args, broadcast_all, explicit_global, category = _parse_flags(args)
|
|
172
|
+
|
|
173
|
+
# One-shot CLI commands (--list-projects, --archive, --today, etc.)
|
|
174
|
+
if handle_cli_args(args, registry, category, explicit_global):
|
|
175
|
+
return
|
|
176
|
+
|
|
177
|
+
# Resolve project routing and initial task text
|
|
178
|
+
target_dir, project_name, initial_task = _resolve_project_and_task(args, registry)
|
|
179
|
+
|
|
180
|
+
is_global = _resolve_global(category, explicit_global, target_dir)
|
|
181
|
+
|
|
182
|
+
# Set up TaskManager and CommandHandler
|
|
183
|
+
task_manager = TaskManager(
|
|
184
|
+
directory=target_dir,
|
|
185
|
+
category=category,
|
|
186
|
+
is_global=is_global,
|
|
187
|
+
project_registry=registry,
|
|
188
|
+
)
|
|
189
|
+
command_handler = CommandHandler(task_manager, registry)
|
|
190
|
+
|
|
191
|
+
# Auto-register project if opened with .jot.json but not in registry
|
|
192
|
+
proj_dir = task_manager.project_dir
|
|
193
|
+
if (
|
|
194
|
+
not is_global
|
|
195
|
+
and (proj_dir / '.jot.json').exists()
|
|
196
|
+
and not registry.is_path_registered(proj_dir)
|
|
197
|
+
):
|
|
198
|
+
registry.register_project(proj_dir.name, str(proj_dir))
|
|
199
|
+
|
|
200
|
+
# Auto-transfer stale tasks to backlog
|
|
201
|
+
auto_cfg = load_auto_backlog_config()
|
|
202
|
+
if auto_cfg.get('enabled', True) and category != auto_cfg.get('backlog_category', 'backlog'):
|
|
203
|
+
task_manager.transfer_old_tasks_to_backlog(
|
|
204
|
+
age_threshold_days=auto_cfg.get('age_threshold_days', 30),
|
|
205
|
+
backlog_category=auto_cfg.get('backlog_category', 'backlog'),
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
task_manager.ensure_default_task()
|
|
209
|
+
|
|
210
|
+
# If a task was supplied on the command line, add it and exit
|
|
211
|
+
if initial_task or broadcast_all:
|
|
212
|
+
_handle_initial_task(
|
|
213
|
+
task_manager,
|
|
214
|
+
initial_task,
|
|
215
|
+
broadcast_all,
|
|
216
|
+
registry,
|
|
217
|
+
project_name,
|
|
218
|
+
is_global,
|
|
219
|
+
category,
|
|
220
|
+
)
|
|
221
|
+
return
|
|
222
|
+
|
|
223
|
+
# Launch the interactive TUI
|
|
224
|
+
from jot.app import App
|
|
225
|
+
|
|
226
|
+
app = App(task_manager, command_handler, registry, target_dir, project_name)
|
|
227
|
+
app.run()
|
|
@@ -257,9 +257,10 @@ def display_categorized_shortcuts():
|
|
|
257
257
|
|
|
258
258
|
def display_help():
|
|
259
259
|
"""Display comprehensive help information."""
|
|
260
|
+
from jot import __version__
|
|
260
261
|
help_text = f"""
|
|
261
262
|
{BOLD}Jott - Simple Interactive Task List{RESET}
|
|
262
|
-
{DIM}Version
|
|
263
|
+
{DIM}Version {__version__}{RESET}
|
|
263
264
|
|
|
264
265
|
{BOLD}BASIC USAGE:{RESET}
|
|
265
266
|
jott Start interactive task manager (current project)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: jott-cli
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.2
|
|
4
4
|
Summary: Feature-rich interactive CLI task manager with AI integration, calendar sync, and keyword automation
|
|
5
5
|
Author-email: Scott Anderson <sonander@gmail.com>
|
|
6
6
|
Maintainer-email: Scott Anderson <sonander@gmail.com>
|
|
@@ -6,6 +6,7 @@ jot/__init__.py
|
|
|
6
6
|
jot/_app_navigation_mixin.py
|
|
7
7
|
jot/_dispatch_mixin.py
|
|
8
8
|
jot/app.py
|
|
9
|
+
jot/main.py
|
|
9
10
|
jot/categories/__init__.py
|
|
10
11
|
jot/categories/config.py
|
|
11
12
|
jot/categories/manager.py
|
|
@@ -95,6 +96,7 @@ tests/test_github.py
|
|
|
95
96
|
tests/test_highlight.py
|
|
96
97
|
tests/test_input.py
|
|
97
98
|
tests/test_jot.py
|
|
99
|
+
tests/test_main_entry_point.py
|
|
98
100
|
tests/test_notes_tempfile_location.py
|
|
99
101
|
tests/test_picker.py
|
|
100
102
|
tests/test_state_palette.py
|
|
@@ -7,7 +7,7 @@ include = ["jot", "jot.*"]
|
|
|
7
7
|
|
|
8
8
|
[project]
|
|
9
9
|
name = "jott-cli"
|
|
10
|
-
version = "0.8.
|
|
10
|
+
version = "0.8.2"
|
|
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"
|
|
@@ -3390,14 +3390,7 @@ class TestImportIntegrity:
|
|
|
3390
3390
|
def test_cli_broadcast_requires_confirmation(self):
|
|
3391
3391
|
"""CLI --all broadcast requires y confirmation, N cancels"""
|
|
3392
3392
|
from unittest.mock import patch
|
|
3393
|
-
import
|
|
3394
|
-
|
|
3395
|
-
spec = importlib.util.spec_from_file_location(
|
|
3396
|
-
"jot_main", str(Path(__file__).parent.parent / "jot.py")
|
|
3397
|
-
)
|
|
3398
|
-
jot_main = importlib.util.module_from_spec(spec)
|
|
3399
|
-
spec.loader.exec_module(jot_main)
|
|
3400
|
-
_handle_initial_task = jot_main._handle_initial_task
|
|
3393
|
+
from jot.main import _handle_initial_task
|
|
3401
3394
|
|
|
3402
3395
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
3403
3396
|
tmpdir = Path(tmpdir)
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Regression tests for the package entry point.
|
|
3
|
+
|
|
4
|
+
Earlier versions used a dynamic-import bridge that loaded main() from
|
|
5
|
+
the repo-root jot.py script via spec_from_file_location. That worked
|
|
6
|
+
locally (where jot.py sat next to the jot/ package) but failed under
|
|
7
|
+
pip install because jot.py was not part of the wheel:
|
|
8
|
+
|
|
9
|
+
FileNotFoundError: '.../site-packages/jot.py'
|
|
10
|
+
|
|
11
|
+
The fix moved main() into jot/main.py so the console script resolves
|
|
12
|
+
through a real package submodule.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import importlib
|
|
16
|
+
import subprocess
|
|
17
|
+
import sys
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class TestMainEntryPoint:
|
|
22
|
+
def test_main_is_a_real_package_submodule(self):
|
|
23
|
+
"""jot.main must exist as a real importable submodule, not a
|
|
24
|
+
path-loaded shim. Importing it must not depend on any file
|
|
25
|
+
outside site-packages.
|
|
26
|
+
"""
|
|
27
|
+
import importlib
|
|
28
|
+
main_module = importlib.import_module('jot.main')
|
|
29
|
+
main_path = Path(main_module.__file__).resolve()
|
|
30
|
+
assert main_path.name == 'main.py'
|
|
31
|
+
assert main_path.parent.name == 'jot', (
|
|
32
|
+
f"jot.main should live inside the jot/ package, "
|
|
33
|
+
f"got {main_path}"
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
def test_main_is_exported_from_package(self):
|
|
37
|
+
"""`from jot import main` must yield a callable."""
|
|
38
|
+
# Reload to ensure we exercise the real import path.
|
|
39
|
+
if 'jot' in sys.modules:
|
|
40
|
+
importlib.reload(sys.modules['jot'])
|
|
41
|
+
from jot import main
|
|
42
|
+
assert callable(main)
|
|
43
|
+
|
|
44
|
+
def test_init_does_not_use_path_based_loader(self):
|
|
45
|
+
"""Guard against re-introduction of the dynamic bridge."""
|
|
46
|
+
import jot
|
|
47
|
+
init_src = Path(jot.__file__).read_text()
|
|
48
|
+
assert 'spec_from_file_location' not in init_src, (
|
|
49
|
+
"jot/__init__.py must not load main from a file path; "
|
|
50
|
+
"it should import jot.main as a normal submodule"
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
def test_console_entry_point_resolves(self):
|
|
54
|
+
"""Simulate what `jott` does: `import jot; jot.main` is the
|
|
55
|
+
target referenced by pyproject.toml's console_scripts entry.
|
|
56
|
+
"""
|
|
57
|
+
import jot
|
|
58
|
+
assert hasattr(jot, 'main')
|
|
59
|
+
assert callable(jot.main)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class TestInstalledLayout:
|
|
63
|
+
"""Verify the layout that pip install produces. Skips silently if
|
|
64
|
+
we can't find a wheel to inspect (e.g., fresh checkout pre-build).
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
def test_built_wheel_contains_main_module(self):
|
|
68
|
+
dist = Path(__file__).resolve().parents[1] / 'dist'
|
|
69
|
+
if not dist.exists():
|
|
70
|
+
return # no build artifacts; skip
|
|
71
|
+
wheels = sorted(dist.glob('jott_cli-*.whl'))
|
|
72
|
+
if not wheels:
|
|
73
|
+
return
|
|
74
|
+
latest = wheels[-1]
|
|
75
|
+
|
|
76
|
+
result = subprocess.run(
|
|
77
|
+
['unzip', '-l', str(latest)],
|
|
78
|
+
capture_output=True, text=True,
|
|
79
|
+
)
|
|
80
|
+
listing = result.stdout
|
|
81
|
+
assert 'jot/main.py' in listing, (
|
|
82
|
+
f"wheel {latest.name} is missing jot/main.py; the entry "
|
|
83
|
+
f"point will fail on install. Listing:\n{listing}"
|
|
84
|
+
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
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
|