jott-cli 0.2.2__tar.gz → 0.3.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 (44) hide show
  1. {jott_cli-0.2.2/jott_cli.egg-info → jott_cli-0.3.0}/PKG-INFO +1 -1
  2. jott_cli-0.3.0/jot/__init__.py +47 -0
  3. jott_cli-0.3.0/jot/categories/__init__.py +0 -0
  4. jott_cli-0.3.0/jot/categories/config.py +133 -0
  5. jott_cli-0.3.0/jot/categories/manager.py +146 -0
  6. jott_cli-0.3.0/jot/categories/templates.py +121 -0
  7. jott_cli-0.3.0/jot/commands/__init__.py +5 -0
  8. jott_cli-0.3.0/jot/commands/handler.py +3398 -0
  9. jott_cli-0.3.0/jot/core/__init__.py +0 -0
  10. jott_cli-0.3.0/jot/core/constants.py +35 -0
  11. jott_cli-0.3.0/jot/core/id_manager.py +139 -0
  12. jott_cli-0.3.0/jot/core/task_manager.py +1155 -0
  13. jott_cli-0.3.0/jot/integrations/__init__.py +0 -0
  14. jott_cli-0.3.0/jot/integrations/gcal/__init__.py +0 -0
  15. jott_cli-0.3.0/jot/integrations/gcal/account_manager.py +66 -0
  16. jott_cli-0.3.0/jot/integrations/gcal/auth.py +151 -0
  17. jott_cli-0.3.0/jot/integrations/gcal/events.py +115 -0
  18. jott_cli-0.3.0/jot/integrations/keywords/__init__.py +0 -0
  19. jott_cli-0.3.0/jot/integrations/keywords/handler.py +443 -0
  20. jott_cli-0.3.0/jot/projects/__init__.py +0 -0
  21. jott_cli-0.3.0/jot/projects/backup.py +98 -0
  22. jott_cli-0.3.0/jot/projects/registry.py +79 -0
  23. jott_cli-0.3.0/jot/ui/__init__.py +22 -0
  24. jott_cli-0.3.0/jot/ui/display.py +819 -0
  25. jott_cli-0.3.0/jot/ui/formatting.py +72 -0
  26. jott_cli-0.3.0/jot/utils/__init__.py +0 -0
  27. jott_cli-0.3.0/jot/utils/date_utils.py +71 -0
  28. jott_cli-0.3.0/jot/utils/text_utils.py +97 -0
  29. jott_cli-0.3.0/jot/utils/validation.py +58 -0
  30. jott_cli-0.3.0/jot.py +1929 -0
  31. {jott_cli-0.2.2 → jott_cli-0.3.0/jott_cli.egg-info}/PKG-INFO +1 -1
  32. jott_cli-0.3.0/jott_cli.egg-info/SOURCES.txt +40 -0
  33. {jott_cli-0.2.2 → jott_cli-0.3.0}/pyproject.toml +5 -2
  34. jott_cli-0.2.2/jot.py +0 -8161
  35. jott_cli-0.2.2/jott_cli.egg-info/SOURCES.txt +0 -12
  36. {jott_cli-0.2.2 → jott_cli-0.3.0}/LICENSE +0 -0
  37. {jott_cli-0.2.2 → jott_cli-0.3.0}/README.md +0 -0
  38. {jott_cli-0.2.2 → jott_cli-0.3.0}/jott_cli.egg-info/dependency_links.txt +0 -0
  39. {jott_cli-0.2.2 → jott_cli-0.3.0}/jott_cli.egg-info/entry_points.txt +0 -0
  40. {jott_cli-0.2.2 → jott_cli-0.3.0}/jott_cli.egg-info/requires.txt +0 -0
  41. {jott_cli-0.2.2 → jott_cli-0.3.0}/jott_cli.egg-info/top_level.txt +0 -0
  42. {jott_cli-0.2.2 → jott_cli-0.3.0}/mcp_server.py +0 -0
  43. {jott_cli-0.2.2 → jott_cli-0.3.0}/setup.cfg +0 -0
  44. {jott_cli-0.2.2 → jott_cli-0.3.0}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jott-cli
3
- Version: 0.2.2
3
+ Version: 0.3.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>
@@ -0,0 +1,47 @@
1
+ """jot package - Simple, extensible interactive CLI task list
2
+
3
+ This package provides modular components for the jot task management system.
4
+ """
5
+
6
+ import importlib.util
7
+ from pathlib import Path
8
+
9
+ # Import extracted classes from their new modules
10
+ from jot.core.id_manager import IDManager
11
+ from jot.core.task_manager import TaskManager
12
+ from jot.projects.registry import ProjectRegistry
13
+ from jot.projects.backup import ProjectBackup
14
+ from jot.categories.manager import CategoryManager
15
+ from jot.categories.config import CategoryConfig
16
+ from jot.categories.templates import CategoryTemplates
17
+ from jot.integrations.gcal.account_manager import GoogleCalendarAccountManager
18
+ from jot.integrations.gcal.auth import GoogleCalendarAuth
19
+ from jot.integrations.gcal.events import create_gcal_event, fetch_gcal_events
20
+ from jot.integrations.keywords.handler import KeywordHandler
21
+
22
+ # Load jot.py for main() and other not-yet-extracted components
23
+ _jot_script_path = Path(__file__).parent.parent / 'jot.py'
24
+ _spec = importlib.util.spec_from_file_location("_jot_script", _jot_script_path)
25
+ _jot_script = importlib.util.module_from_spec(_spec)
26
+ _spec.loader.exec_module(_jot_script)
27
+
28
+ # Export main function from jot.py
29
+ main = _jot_script.main
30
+
31
+ __version__ = "0.3.0"
32
+
33
+ __all__ = [
34
+ 'main',
35
+ 'TaskManager',
36
+ 'ProjectRegistry',
37
+ 'ProjectBackup',
38
+ 'IDManager',
39
+ 'CategoryManager',
40
+ 'CategoryConfig',
41
+ 'CategoryTemplates',
42
+ 'KeywordHandler',
43
+ 'GoogleCalendarAccountManager',
44
+ 'GoogleCalendarAuth',
45
+ 'create_gcal_event',
46
+ 'fetch_gcal_events',
47
+ ]
File without changes
@@ -0,0 +1,133 @@
1
+ """Category configuration and color management"""
2
+
3
+ import json
4
+ from pathlib import Path
5
+
6
+
7
+ class CategoryConfig:
8
+ """Manages category colors and configuration"""
9
+
10
+ # ANSI color codes
11
+ COLORS = {
12
+ 'red': '\033[91m',
13
+ 'green': '\033[92m',
14
+ 'yellow': '\033[93m',
15
+ 'blue': '\033[94m',
16
+ 'magenta': '\033[95m',
17
+ 'cyan': '\033[96m',
18
+ 'white': '\033[97m',
19
+ 'orange': '\033[38;5;208m',
20
+ 'purple': '\033[38;5;141m',
21
+ 'pink': '\033[38;5;213m',
22
+ 'teal': '\033[38;5;44m',
23
+ 'lime': '\033[38;5;154m',
24
+ }
25
+
26
+ # Default color assignments for common categories
27
+ DEFAULT_COLORS = {
28
+ 'default': 'white',
29
+ 'features': 'green',
30
+ 'bugs': 'red',
31
+ 'testing': 'yellow',
32
+ 'docs': 'blue',
33
+ 'backlog': 'cyan',
34
+ 'ideas': 'purple',
35
+ 'work': 'teal',
36
+ 'personal': 'pink',
37
+ 'bills': 'orange',
38
+ 'health': 'lime',
39
+ }
40
+
41
+ def __init__(self, project_dir=None):
42
+ """Initialize category config"""
43
+ self.project_dir = Path(project_dir) if project_dir else Path.cwd()
44
+ self.config_file = self.project_dir / '.jot.config.json'
45
+ self.config = self._load_config()
46
+
47
+ def _load_config(self):
48
+ """Load config from .jot.config.json"""
49
+ if self.config_file.exists():
50
+ try:
51
+ with open(self.config_file, 'r') as f:
52
+ return json.load(f)
53
+ except json.JSONDecodeError:
54
+ return {}
55
+ return {}
56
+
57
+ def _save_config(self):
58
+ """Save config to .jot.config.json"""
59
+ self.config_file.parent.mkdir(parents=True, exist_ok=True)
60
+ with open(self.config_file, 'w') as f:
61
+ json.dump(self.config, indent=2, fp=f)
62
+
63
+ def get_color(self, category_name, for_global=False):
64
+ """Get ANSI color code for a category"""
65
+ # Check custom colors first
66
+ if 'colors' in self.config and category_name in self.config['colors']:
67
+ color_name = self.config['colors'][category_name]
68
+ return self.COLORS.get(color_name, '\033[96m') # Default to cyan
69
+
70
+ # Use default color for known categories
71
+ if category_name in self.DEFAULT_COLORS:
72
+ return self.COLORS[self.DEFAULT_COLORS[category_name]]
73
+
74
+ # Global categories get magenta by default
75
+ if for_global:
76
+ return self.COLORS['magenta']
77
+
78
+ # Unknown local categories get cyan
79
+ return self.COLORS['cyan']
80
+
81
+ def set_color(self, category_name, color_name):
82
+ """Set color for a category"""
83
+ if color_name not in self.COLORS:
84
+ return False, f"Invalid color. Available: {', '.join(sorted(self.COLORS.keys()))}"
85
+
86
+ if 'colors' not in self.config:
87
+ self.config['colors'] = {}
88
+
89
+ self.config['colors'][category_name] = color_name
90
+ self._save_config()
91
+ return True, f"Set {category_name} color to {color_name}"
92
+
93
+ def get_all_colors(self):
94
+ """Get all configured colors"""
95
+ return self.config.get('colors', {})
96
+
97
+ def reset_colors(self):
98
+ """Reset all colors to defaults"""
99
+ if 'colors' in self.config:
100
+ del self.config['colors']
101
+ self._save_config()
102
+ return True
103
+
104
+ def list_available_colors(self):
105
+ """List all available color names"""
106
+ return sorted(self.COLORS.keys())
107
+
108
+ def get_tts_config(self):
109
+ """Get TTS configuration settings"""
110
+ default_tts = {
111
+ 'enabled': True,
112
+ 'rate': 150, # Words per minute
113
+ 'volume': 0.9, # 0.0 to 1.0
114
+ 'voice': None, # None = system default
115
+ }
116
+ return self.config.get('tts', default_tts)
117
+
118
+ def set_tts_config(self, **kwargs):
119
+ """Set TTS configuration settings"""
120
+ if 'tts' not in self.config:
121
+ self.config['tts'] = {
122
+ 'enabled': True,
123
+ 'rate': 150,
124
+ 'volume': 0.9,
125
+ 'voice': None,
126
+ }
127
+
128
+ for key, value in kwargs.items():
129
+ if key in ['enabled', 'rate', 'volume', 'voice']:
130
+ self.config['tts'][key] = value
131
+
132
+ self._save_config()
133
+ return True
@@ -0,0 +1,146 @@
1
+ """Category management for jot projects"""
2
+
3
+ from pathlib import Path
4
+
5
+
6
+ class CategoryManager:
7
+ """Manages categories within a project directory and globally"""
8
+
9
+ MAX_CATEGORIES = 12
10
+ GLOBAL_CATEGORIES_DIR = Path.home() / '.jot-categories'
11
+
12
+ def __init__(self, project_dir=None):
13
+ """Initialize category manager for a project directory"""
14
+ self.project_dir = Path(project_dir) if project_dir else Path.cwd()
15
+
16
+ def discover_categories(self):
17
+ """
18
+ Find all category files in .jot-categories/ directory.
19
+ Returns list of category names (without .json suffix).
20
+ """
21
+ categories = []
22
+
23
+ # Check if .jot-categories directory exists
24
+ categories_dir = self.project_dir / '.jot-categories'
25
+ if not categories_dir.exists():
26
+ return categories
27
+
28
+ # Find all .json files in .jot-categories/
29
+ for file in categories_dir.glob('*.json'):
30
+ # Extract category name from filename
31
+ # features.json -> features
32
+ category = file.stem # Get filename without extension
33
+ categories.append(category)
34
+
35
+ return sorted(categories)
36
+
37
+ def count_categories(self):
38
+ """Count existing categories in the project"""
39
+ return len(self.discover_categories())
40
+
41
+ def can_create_category(self, category_name):
42
+ """
43
+ Check if a new category can be created.
44
+ Returns (can_create: bool, message: str)
45
+ """
46
+ existing = self.discover_categories()
47
+
48
+ # Category already exists
49
+ if category_name in existing:
50
+ return True, None
51
+
52
+ # Check limit
53
+ if len(existing) >= self.MAX_CATEGORIES:
54
+ return (
55
+ False,
56
+ f"Category limit reached ({self.MAX_CATEGORIES} max). Existing: {', '.join(existing)}",
57
+ )
58
+
59
+ return True, None
60
+
61
+ def get_warning_message(self, category_name):
62
+ """
63
+ Get warning message if approaching category limit.
64
+ Returns None if no warning needed.
65
+ """
66
+ existing = self.discover_categories()
67
+
68
+ # No warning if category already exists
69
+ if category_name in existing:
70
+ return None
71
+
72
+ count = len(existing)
73
+
74
+ if count == 10:
75
+ return f"Warning: Approaching category limit ({count + 1}/12 categories)"
76
+ elif count == 11:
77
+ return f"Warning: Near category limit ({count + 1}/12 categories)"
78
+ elif count >= 12:
79
+ return f"Error: Category limit reached ({self.MAX_CATEGORIES} max)"
80
+
81
+ return None
82
+
83
+ def discover_global_categories(self):
84
+ """
85
+ Find all global category files in ~/.jot-categories/
86
+ Returns list of category names (without .json suffix).
87
+ Excludes special files like ids, archive, etc.
88
+ """
89
+ # Return empty list if global directory doesn't exist
90
+ if not self.GLOBAL_CATEGORIES_DIR.exists():
91
+ return []
92
+
93
+ categories = []
94
+ reserved_names = {'ids', 'archive', 'index', 'config'}
95
+
96
+ # Find all *.json files in global categories directory
97
+ for file in self.GLOBAL_CATEGORIES_DIR.glob('*.json'):
98
+ category = file.stem # filename without .json
99
+
100
+ # Skip reserved names and special files
101
+ if category not in reserved_names and not category.endswith('.ids'):
102
+ categories.append(category)
103
+
104
+ return sorted(categories)
105
+
106
+ def category_exists_locally(self, category_name):
107
+ """Check if category exists locally in project"""
108
+ categories_dir = self.project_dir / '.jot-categories'
109
+ local_file = categories_dir / f'{category_name}.json'
110
+ return local_file.exists()
111
+
112
+ def category_exists_globally(self, category_name):
113
+ """Check if category exists globally"""
114
+ global_file = self.GLOBAL_CATEGORIES_DIR / f'{category_name}.json'
115
+ return global_file.exists()
116
+
117
+ def resolve_category_location(self, category_name):
118
+ """
119
+ Determine if category is local, global, or new.
120
+ Returns: 'local', 'global', or 'new'
121
+ """
122
+ if self.category_exists_locally(category_name):
123
+ return 'local'
124
+ elif self.category_exists_globally(category_name):
125
+ return 'global'
126
+ else:
127
+ return 'new'
128
+
129
+ def get_category_file_path(self, category_name, is_global=False):
130
+ """
131
+ Get the full path for a category file.
132
+
133
+ Args:
134
+ category_name: Name of the category
135
+ is_global: If True, return global path; if False, return local path
136
+
137
+ Returns:
138
+ Path object for the category file
139
+ """
140
+ if is_global:
141
+ return self.GLOBAL_CATEGORIES_DIR / f'{category_name}.json'
142
+ else:
143
+ # Local categories are stored in .jot-categories/ directory
144
+ categories_dir = self.project_dir / '.jot-categories'
145
+ categories_dir.mkdir(exist_ok=True)
146
+ return categories_dir / f'{category_name}.json'
@@ -0,0 +1,121 @@
1
+ """Category templates for quick project setup"""
2
+
3
+ from pathlib import Path
4
+
5
+ from jot.categories.config import CategoryConfig
6
+ from jot.categories.manager import CategoryManager
7
+ from jot.core.task_manager import TaskManager
8
+
9
+
10
+ class CategoryTemplates:
11
+ """Manages category templates for quick project setup"""
12
+
13
+ TEMPLATES = {
14
+ 'software-dev': {
15
+ 'name': 'Software Development',
16
+ 'description': 'Standard software project categories',
17
+ 'categories': ['features', 'bugs', 'testing', 'docs'],
18
+ 'sample_tasks': {
19
+ 'features': ['Plan next sprint', 'Review backlog'],
20
+ 'bugs': ['Triage bug reports'],
21
+ 'testing': ['Run test suite', 'Update test coverage'],
22
+ 'docs': ['Update README', 'Write API docs'],
23
+ },
24
+ },
25
+ 'personal': {
26
+ 'name': 'Personal Tasks',
27
+ 'description': 'Personal life management categories',
28
+ 'categories': ['work', 'home', 'bills', 'health'],
29
+ 'sample_tasks': {
30
+ 'work': ['Check email', 'Weekly planning'],
31
+ 'home': ['Grocery shopping', 'Clean house'],
32
+ 'bills': ['Pay rent', 'Review budget'],
33
+ 'health': ['Exercise', 'Doctor appointment'],
34
+ },
35
+ },
36
+ 'creative': {
37
+ 'name': 'Creative Project',
38
+ 'description': 'Creative and content creation categories',
39
+ 'categories': ['ideas', 'drafts', 'editing', 'publishing'],
40
+ 'sample_tasks': {
41
+ 'ideas': ['Brainstorm topics', 'Research inspiration'],
42
+ 'drafts': ['Write first draft'],
43
+ 'editing': ['Review and revise'],
44
+ 'publishing': ['Format and publish'],
45
+ },
46
+ },
47
+ 'business': {
48
+ 'name': 'Business Management',
49
+ 'description': 'Business and client work categories',
50
+ 'categories': ['leads', 'clients', 'invoicing', 'marketing'],
51
+ 'sample_tasks': {
52
+ 'leads': ['Follow up prospects', 'Update CRM'],
53
+ 'clients': ['Client meetings', 'Project deliverables'],
54
+ 'invoicing': ['Send invoices', 'Track payments'],
55
+ 'marketing': ['Social media posts', 'Email campaign'],
56
+ },
57
+ },
58
+ 'academic': {
59
+ 'name': 'Academic/Research',
60
+ 'description': 'Academic and research project categories',
61
+ 'categories': ['reading', 'research', 'writing', 'teaching'],
62
+ 'sample_tasks': {
63
+ 'reading': ['Literature review', 'Paper analysis'],
64
+ 'research': ['Experiment design', 'Data collection'],
65
+ 'writing': ['Draft paper', 'Revise manuscript'],
66
+ 'teaching': ['Prepare lecture', 'Grade assignments'],
67
+ },
68
+ },
69
+ }
70
+
71
+ def __init__(self, project_dir=None):
72
+ """Initialize templates manager"""
73
+ self.project_dir = Path(project_dir) if project_dir else Path.cwd()
74
+ self.cat_manager = CategoryManager(project_dir=self.project_dir)
75
+ self.cat_config = CategoryConfig(project_dir=self.project_dir)
76
+
77
+ def list_templates(self):
78
+ """List all available templates"""
79
+ return self.TEMPLATES
80
+
81
+ def apply_template(self, template_name, with_samples=False):
82
+ """Apply a template to create categories"""
83
+ if template_name not in self.TEMPLATES:
84
+ return False, f"Template '{template_name}' not found"
85
+
86
+ template = self.TEMPLATES[template_name]
87
+ categories_to_create = template['categories']
88
+
89
+ existing_categories = self.cat_manager.discover_categories()
90
+ total_after = len(existing_categories) + len(categories_to_create)
91
+
92
+ if total_after > CategoryManager.MAX_CATEGORIES:
93
+ return (
94
+ False,
95
+ f"Cannot create {len(categories_to_create)} categories. "
96
+ f"Would exceed limit ({CategoryManager.MAX_CATEGORIES} max)",
97
+ )
98
+
99
+ conflicts = [c for c in categories_to_create if c in existing_categories]
100
+ if conflicts:
101
+ return False, f"Categories already exist: {', '.join(conflicts)}"
102
+
103
+ created = []
104
+ for category in categories_to_create:
105
+ tm = TaskManager(
106
+ directory=self.project_dir,
107
+ category=category,
108
+ is_global=False,
109
+ )
110
+ if with_samples and category in template.get('sample_tasks', {}):
111
+ for task_text in template['sample_tasks'][category]:
112
+ tm.add_task(task_text)
113
+ created.append(category)
114
+
115
+ return True, f"Created {len(created)} categories: {', '.join(created)}"
116
+
117
+ def get_template_info(self, template_name):
118
+ """Get detailed information about a template"""
119
+ if template_name not in self.TEMPLATES:
120
+ return None
121
+ return self.TEMPLATES[template_name]
@@ -0,0 +1,5 @@
1
+ """Command handling for jot interactive UI"""
2
+
3
+ from jot.commands.handler import CommandHandler
4
+
5
+ __all__ = ['CommandHandler']