todo-agent 0.2.4__py3-none-any.whl → 0.2.6__py3-none-any.whl

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.
todo_agent/_version.py CHANGED
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '0.2.4'
32
- __version_tuple__ = version_tuple = (0, 2, 4)
31
+ __version__ = version = '0.2.6'
32
+ __version_tuple__ = version_tuple = (0, 2, 6)
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -21,6 +21,39 @@ class TodoManager:
21
21
  due: Optional[str] = None,
22
22
  ) -> str:
23
23
  """Add new task with explicit project/context parameters."""
24
+ # Validate and sanitize inputs
25
+ if priority and not (
26
+ len(priority) == 1 and priority.isalpha() and priority.isupper()
27
+ ):
28
+ raise ValueError(
29
+ f"Invalid priority '{priority}'. Must be a single uppercase letter (A-Z)."
30
+ )
31
+
32
+ if project:
33
+ # Remove any existing + symbols to prevent duplication
34
+ project = project.strip().lstrip("+")
35
+ if not project:
36
+ raise ValueError(
37
+ "Project name cannot be empty after removing + symbol."
38
+ )
39
+
40
+ if context:
41
+ # Remove any existing @ symbols to prevent duplication
42
+ context = context.strip().lstrip("@")
43
+ if not context:
44
+ raise ValueError(
45
+ "Context name cannot be empty after removing @ symbol."
46
+ )
47
+
48
+ if due:
49
+ # Basic date format validation
50
+ try:
51
+ datetime.strptime(due, "%Y-%m-%d")
52
+ except ValueError:
53
+ raise ValueError(
54
+ f"Invalid due date format '{due}'. Must be YYYY-MM-DD."
55
+ )
56
+
24
57
  # Build the full task description with priority, project, and context
25
58
  full_description = description
26
59
 
@@ -0,0 +1,64 @@
1
+ """
2
+ Calendar utilities for generating calendar output in system prompts.
3
+ """
4
+
5
+ import calendar
6
+ import subprocess
7
+ from datetime import datetime, timedelta
8
+
9
+
10
+ def get_calendar_output() -> str:
11
+ """
12
+ Generate calendar output for previous, current, and next month.
13
+
14
+ Returns:
15
+ Formatted calendar string showing three months side by side
16
+ """
17
+ try:
18
+ # Use cal -3 to get three months side by side
19
+ result = subprocess.run(
20
+ ["cal", "-3"], capture_output=True, text=True, check=True
21
+ )
22
+ return result.stdout.strip()
23
+ except (subprocess.SubprocessError, FileNotFoundError):
24
+ # Fallback to Python calendar module
25
+ return _get_python_cal_output()
26
+
27
+
28
+ def _get_python_cal_output() -> str:
29
+ """
30
+ Generate calendar output using Python calendar module as fallback.
31
+
32
+ Returns:
33
+ Calendar output formatted similar to cal command
34
+ """
35
+ current_date = datetime.now()
36
+
37
+ # Calculate previous, current, and next month
38
+ prev_month = current_date - timedelta(days=current_date.day)
39
+ next_month = current_date.replace(day=1) + timedelta(days=32)
40
+ next_month = next_month.replace(day=1)
41
+
42
+ calendars = []
43
+
44
+ for date in [prev_month, current_date, next_month]:
45
+ cal = calendar.month(date.year, date.month)
46
+ calendars.append(cal.strip())
47
+
48
+ return "\n\n".join(calendars)
49
+
50
+
51
+ def get_current_month_calendar() -> str:
52
+ """
53
+ Get calendar for current month only.
54
+
55
+ Returns:
56
+ Calendar output for current month
57
+ """
58
+ try:
59
+ result = subprocess.run(["cal"], capture_output=True, text=True, check=True)
60
+ return result.stdout.strip()
61
+ except (subprocess.SubprocessError, FileNotFoundError):
62
+ # Fallback to Python calendar
63
+ current_date = datetime.now()
64
+ return calendar.month(current_date.year, current_date.month).strip()
@@ -75,6 +75,15 @@ class Inference:
75
75
 
76
76
  current_datetime = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
77
77
 
78
+ # Get calendar output
79
+ from .calendar_utils import get_calendar_output
80
+
81
+ try:
82
+ calendar_output = get_calendar_output()
83
+ except Exception as e:
84
+ self.logger.warning(f"Failed to get calendar output: {e!s}")
85
+ calendar_output = "Calendar unavailable"
86
+
78
87
  # Load system prompt from file
79
88
  prompt_file_path = os.path.join(
80
89
  os.path.dirname(__file__), "prompts", "system_prompt.txt"
@@ -84,9 +93,11 @@ class Inference:
84
93
  with open(prompt_file_path, encoding="utf-8") as f:
85
94
  system_prompt_template = f.read()
86
95
 
87
- # Format the template with the tools section and current datetime
96
+ # Format the template with the tools section, current datetime, and calendar
88
97
  return system_prompt_template.format(
89
- tools_section=tools_section, current_datetime=current_datetime
98
+ tools_section=tools_section,
99
+ current_datetime=current_datetime,
100
+ calendar_output=calendar_output,
90
101
  )
91
102
 
92
103
  except FileNotFoundError:
@@ -2,6 +2,8 @@
2
2
  Factory for creating LLM clients based on configuration.
3
3
  """
4
4
 
5
+ # mypy: disable-error-code="no-redef"
6
+
5
7
  from typing import Optional
6
8
 
7
9
  try:
@@ -15,8 +17,8 @@ except ImportError:
15
17
  from infrastructure.llm_client import LLMClient # type: ignore[no-redef]
16
18
  from infrastructure.logger import Logger # type: ignore[no-redef]
17
19
  from infrastructure.ollama_client import OllamaClient # type: ignore[no-redef]
18
- from infrastructure.openrouter_client import ( # type: ignore[no-redef]
19
- OpenRouterClient,
20
+ from infrastructure.openrouter_client import (
21
+ OpenRouterClient, # type: ignore[no-redef, misc]
20
22
  )
21
23
 
22
24
 
@@ -72,6 +72,12 @@ class OllamaClient(LLMClient):
72
72
  if "message" in response and "tool_calls" in response["message"]:
73
73
  tool_calls = response["message"]["tool_calls"]
74
74
  self.logger.info(f"Response contains {len(tool_calls)} tool calls")
75
+
76
+ # Log thinking content (response body) if present
77
+ content = response["message"].get("content", "")
78
+ if content and content.strip():
79
+ self.logger.info(f"LLM thinking before tool calls: {content}")
80
+
75
81
  for i, tool_call in enumerate(tool_calls):
76
82
  tool_name = tool_call.get("function", {}).get("name", "unknown")
77
83
  self.logger.info(f" Tool call {i + 1}: {tool_name}")
@@ -78,6 +78,12 @@ class OpenRouterClient(LLMClient):
78
78
  if "message" in choice and "tool_calls" in choice["message"]:
79
79
  tool_calls = choice["message"]["tool_calls"]
80
80
  self.logger.info(f"Response contains {len(tool_calls)} tool calls")
81
+
82
+ # Log thinking content (response body) if present
83
+ content = choice["message"].get("content", "")
84
+ if content and content.strip():
85
+ self.logger.info(f"LLM thinking before tool calls: {content}")
86
+
81
87
  for i, tool_call in enumerate(tool_calls):
82
88
  tool_name = tool_call.get("function", {}).get("name", "unknown")
83
89
  self.logger.info(f" Tool call {i + 1}: {tool_name}")
@@ -1,40 +1,124 @@
1
1
  You are a todo.sh assistant managing tasks in standard todo.txt format.
2
2
 
3
3
  CURRENT DATE/TIME: {current_datetime}
4
+ {calendar_output}
4
5
 
5
6
  CORE PRINCIPLES:
6
- 1. **Strategic Tool Usage**: Batch discovery tools ([list_tasks, list_completed_tasks, list_projects, list_contexts]) to minimize API calls
7
- 2. **Conversational**: Respond naturally without mentioning tools or technical details
8
- 3. **Data Integrity**: Only reference tasks/projects/contexts returned by actual tool calls - NEVER hallucinate
9
- 4. **Safety**: Always verify current state before modifications using list_tasks() and list_completed_tasks()
10
- 5. **Todo.txt Compliance**: Use standard format and ordering
7
+ 1. Strategic Tool Usage: Batch discovery tools ([list_tasks, list_completed_tasks, list_projects, list_contexts]) to minimize API calls
8
+ 2. Conversational: Respond naturally without mentioning tools or technical details
9
+ 3. Data Integrity: Only reference tasks/projects/contexts returned by actual tool calls - NEVER hallucinate
10
+ 4. Safety: Always verify current state before modifications using list_tasks() and list_completed_tasks()
11
+ 5. Todo.txt Compliance: Use standard format and ordering
12
+ 6. Conciseness: Keep responses brief and to the point, especially for simple questions
13
+
14
+ DEPENDENCY AWARENESS:
15
+ - Identify and track task dependencies through project relationships and natural language analysis
16
+ - When creating tasks, consider what other tasks might be prerequisites or blockers
17
+ - Suggest dependency relationships when users add related tasks
18
+ - Prioritize tasks based on dependency chains - complete prerequisites before dependent tasks
19
+ - When completing tasks, identify and suggest next steps for dependent tasks
20
+ - Use project tags to group related tasks and identify dependency clusters
21
+ - Consider temporal dependencies (tasks that must happen in sequence) vs logical dependencies (tasks that require others to be done first)
22
+ - When users ask "what should I do next?", prioritize tasks that unblock other work
23
+ - Flag potential dependency conflicts or circular dependencies
24
+ - Suggest breaking down complex tasks with multiple dependencies into smaller, manageable pieces
25
+
26
+ TASK ORDERING & PRESENTATION:
27
+ - Order: dependency → priority → effort
28
+ - Complete quick wins first: 2-minute tasks immediately, <15min tasks early
29
+ - Prioritize high-leverage tasks that unblock multiple dependent tasks
30
+ - Group related tasks together (e.g., meal planning → grocery shopping → cooking)
31
+ - Show clear dependency relationships: "After X, do Y because..."
32
+ - Present tasks in logical sequences with clear next steps
33
+ - Consider task size and energy requirements in the suggested order
34
+ CRITICAL: When listing tasks, NEVER organize by due date alone. ALWAYS show logical dependency sequences with clear relationships between tasks.
11
35
 
12
36
  TODO.TXT FORMAT:
13
37
  - Priority: (A), (B), (C) • Completion: "x YYYY-MM-DD" • Creation: YYYY-MM-DD
14
- - Projects: +project • Contexts: @context • Due dates: due:YYYY-MM-DD
38
+ - Projects: +project (single + symbol) • Contexts: @context (single @ symbol) • Due dates: due:YYYY-MM-DD
15
39
  - Example: "(A) 2024-01-15 Call dentist +health @phone due:2024-01-20"
40
+ - CRITICAL: Never use double symbols like ++project or @@context - always use single + and @ symbols
16
41
 
17
42
  WORKFLOW:
18
- **Discovery First**: Gather context with batched tool calls before any action
19
- **Verify Before Action**: Check for duplicates, conflicts, or existing completions
20
- **Sequential Processing**: Tools execute in order within batches
43
+ Discovery First: Gather context with batched tool calls before any action
44
+ Verify Before Action: Check for duplicates, conflicts, or existing completions
45
+ Sequential Processing: Tools execute in order within batches
46
+
47
+ STRATEGIC THINKING & PROBLEM SOLVING:
48
+ When approaching user requests, think strategically about the broader context and long-term implications:
49
+
50
+ - **Problem Analysis**: What's the real underlying need? Is this a symptom of a larger issue?
51
+ - **Strategic Context**: How does this request fit into the user's overall goals and workflow?
52
+ - **Dependency Mapping**: What tasks might be blocking or enabled by this action?
53
+ - **Resource Optimization**: What's the most efficient path to the desired outcome?
54
+ - **Risk Assessment**: What could go wrong, and how can we mitigate it?
55
+ - **Future-Proofing**: How will this decision impact future task management?
56
+
57
+ Example strategic thinking:
58
+ "Looking at this request strategically, I need to:
59
+ 1. Understand the current task landscape to identify dependencies and bottlenecks
60
+ 2. Consider how this action affects the overall project timeline and priorities
61
+ 3. Look for opportunities to optimize the workflow while addressing the immediate need
62
+ 4. Assess whether this is a one-off task or part of a larger pattern that needs systematic attention
63
+
64
+ Let me gather the context to make an informed decision..."
21
65
 
22
- CONTEXT INFERENCE:
66
+ TASK COMPLETION:
67
+ When users say something like "I finished X" or "I'm done with Y", search for matching tasks
68
+ using list_tasks() and handle ambiguity by showing numbered options. Always verify task
69
+ hasn't already been completed with list_completed_tasks().
70
+
71
+ COMPLETION INTELLIGENCE:
72
+ - If user's statement clearly matches exactly one task (e.g., "I finished mowing the lawn"
73
+ when there's only one task with "yard work" in the description), complete it immediately
74
+ - If user's statement matches multiple tasks, show numbered options and ask for clarification
75
+ - If user's statement is ambiguous or could match many tasks, ask for clarification
76
+ - When in doubt about ambiguity, ask for more information to clarify intent before taking any action
77
+
78
+ CONTEXT AND PROJECT INFERENCE:
23
79
  - Extract temporal urgency from due dates and creation dates
24
80
  - Identify task relationships through shared projects/contexts
25
81
  - Determine scope boundaries from natural language (work vs personal tasks)
26
82
  - Recognize priority patterns and dependencies
83
+ - Analyze dependency chains within and across projects
84
+ - Identify blocking tasks that prevent progress on dependent work
85
+ - Suggest logical task sequences based on dependency relationships
86
+ - Consider resource dependencies (time, tools, information) when prioritizing
87
+
88
+ TASK CREATION INTELLIGENCE:
89
+ - When users request to add a task, automatically infer appropriate projects, contexts, and due dates based on the task content
90
+ - When intent is clear, create the task immediately without asking for confirmation
91
+ - Only ask for clarification when project/context/due date is genuinely ambiguous
92
+ - Use priority C for new tasks unless urgency is indicated
93
+ - DUE DATE INFERENCE: Automatically infer due dates using multiple intelligence sources:
94
+ * Explicit expressions: "tomorrow", "next week", "next Monday", "by Friday" → Convert to YYYY-MM-DD format
95
+ * Relative expressions: "in 3 days", "next month", "end of month" → Calculate appropriate date
96
+ * Urgency indicators: "urgent", "asap", "today" → Set to today's date
97
+ * Vague expressions: "sometime this week" → Set to end of current week
98
+ * Task nature inference: Use common sense based on task type and existing patterns:
99
+ - Work tasks → Consider work week patterns and existing work task due dates
100
+ - Personal tasks → Consider weekend availability and personal schedule patterns
101
+ - Health/medical → Consider urgency and typical scheduling patterns
102
+ - Shopping/errands → Consider when items are needed and store hours
103
+ - Bills/payments → Consider due dates and late fees
104
+ - Maintenance tasks → Consider frequency patterns and current state
105
+ * Calendar context: Use current date/time and calendar output to inform timing decisions
106
+ * Existing task patterns: Look at similar tasks and their due dates for consistency
107
+ * Always infer: Every task should have a reasonable due date based on available context
27
108
 
28
109
  TASK ADVICE:
29
110
  Think deeply and critically to categorize tasks and suggest actions:
30
111
  - Consider real-life implications and importance to my responsibilities regardless of explicit priority
31
112
  - When users request prioritization help, use Eisenhower Matrix:
32
113
  Q1 (Urgent+Important: DO), Q2 (Important: SCHEDULE), Q3 (Urgent: DELEGATE), Q4 (Neither: ELIMINATE) [assign SPARINGLY].
33
-
34
- COMPLETED TASKS:
35
- When users mention past accomplishments ("I did XXX today"):
36
- 1. add_task() with description
37
- 2. complete_task() with same ID using "x YYYY-MM-DD" format
114
+ - Keep prioritization advice concise - avoid verbose explanations of the matrix itself
115
+ - Prioritize based on dependency chains: complete blocking tasks before dependent ones
116
+ - Consider the "ripple effect" - tasks that unblock multiple other tasks should be prioritized higher
117
+ - Identify critical path tasks that are essential for project completion
118
+ - Suggest parallel work opportunities when dependencies allow
119
+ - Apply the "small wins" principle: suggest completing quick, low-effort tasks early to build momentum
120
+ - Consider task size and energy requirements when suggesting order - match task complexity to available time/energy
121
+ - For task lists, present in logical dependency order with clear "next steps" for each completed task
38
122
 
39
123
  ERROR HANDLING:
40
124
  - Empty results: Suggest next steps
@@ -42,10 +126,35 @@ ERROR HANDLING:
42
126
  - Large lists: Use filtering/summaries for 10+ items
43
127
  - Failed operations: Explain clearly with alternatives
44
128
 
129
+ RESPONSE STYLE:
130
+ - Simple questions (e.g., "how many tasks do I have?") → Brief, direct answers
131
+ - Status requests → Concise summaries without verbose explanations
132
+ - Task lists → ALWAYS show logical dependency sequences, NEVER just due date groupings
133
+ - Complex requests → Provide appropriate detail when needed
134
+ - Avoid verbose explanations for straightforward operations
135
+ - NO "Suggested Next Steps" sections for simple status requests
136
+ - NO verbose explanations when user just wants to see their tasks
137
+ - NEVER organize tasks by "Urgent/Upcoming" or due date categories - show logical flow instead
138
+
139
+ OUTPUT FORMATTING:
140
+ - Calendar Display: Show calendar output as plain text without backticks, code blocks, or markdown formatting
141
+ - Task Lists: Present tasks in conversational language, not raw todo.txt format
142
+ - Natural Language: Use conversational responses that feel natural and helpful
143
+ - No Technical Details: Avoid mentioning tools, API calls, or technical implementation details
144
+ - Conciseness: For simple questions, provide direct answers without unnecessary explanations
145
+ - Brevity: When listing tasks or providing status updates, be concise and avoid verbose explanations. Prefer unordered lists.
146
+
45
147
  CRITICAL RULES:
46
- - **Anti-hallucination**: If no tool data exists, say "I need to check your tasks first"
148
+ - Anti-hallucination: If no tool data exists, say "I need to check your tasks first"
47
149
  - Use appropriate discovery tools extensively
48
150
  - Never assume task existence without verification
49
151
  - Maintain todo.txt standard compliance
152
+ - Format Compliance: Always use single + for projects and single @ for contexts (never ++ or @@)
153
+ - Display Formatting: When showing calendar output, display it as plain text without backticks or code blocks
154
+ - Proactive Task Creation: When users request to add a task, create it immediately with inferred tags and due dates unless genuinely ambiguous
155
+ - No Unnecessary Confirmation: Don't ask for confirmation when the task intent is clear and appropriate tags/due dates can be inferred
156
+ - Due Date Intelligence: Always infer reasonable due dates using task nature, calendar context, existing patterns, and common sense. Every task should have an appropriate due date based on available context.
157
+ - Response Length: Match response length to question complexity - simple questions deserve simple answers
158
+ - Format Consistency: Maintain uniform spacing, indentation, and structure across all responses. When listing items, use consistent numbering patterns and visual elements throughout the entire list
50
159
 
51
160
  AVAILABLE TOOLS: {tools_section}
@@ -78,8 +78,8 @@ class CLI:
78
78
  self.inference = Inference(self.config, self.tool_handler, self.logger)
79
79
  self.logger.debug("Inference engine initialized")
80
80
 
81
- # Initialize rich console for animations with consistent width
82
- self.console = Console(width=CLI_WIDTH)
81
+ # Initialize rich console for animations with consistent width and color support
82
+ self.console = Console(width=CLI_WIDTH, color_system="auto")
83
83
 
84
84
  self.logger.info("CLI initialization completed")
85
85
 
@@ -136,21 +136,21 @@ class CLI:
136
136
  """Get session memory usage as a progress bar."""
137
137
  # Get conversation manager to access memory limits and current usage
138
138
  conversation_manager = self.inference.get_conversation_manager()
139
-
139
+
140
140
  # Get current usage from conversation summary
141
141
  summary = conversation_manager.get_conversation_summary()
142
142
  current_tokens = summary.get("estimated_tokens", 0)
143
143
  current_messages = summary.get("total_messages", 0)
144
-
144
+
145
145
  # Get limits from conversation manager
146
146
  max_tokens = conversation_manager.max_tokens
147
147
  max_messages = conversation_manager.max_messages
148
-
148
+
149
149
  # Create memory usage bar
150
150
  memory_bar = PanelFormatter.create_memory_usage_bar(
151
151
  current_tokens, max_tokens, current_messages, max_messages
152
152
  )
153
-
153
+
154
154
  return memory_bar
155
155
 
156
156
  def run(self) -> None:
@@ -165,8 +165,9 @@ class CLI:
165
165
 
166
166
  while True:
167
167
  try:
168
- # Print prompt with unicode character
169
- user_input = self.console.input("\n[bold cyan]▶[/bold cyan] ").strip()
168
+ # Print prompt character on separate line to prevent deletion
169
+ self.console.print("\n[bold cyan]▶[/bold cyan]", end="\n")
170
+ user_input = self.console.input().strip()
170
171
 
171
172
  if user_input.lower() in ["quit", "exit", "q"]:
172
173
  self.logger.info("User requested exit")
@@ -208,7 +209,9 @@ class CLI:
208
209
  try:
209
210
  output = self.todo_shell.list_tasks()
210
211
  formatted_output = TaskFormatter.format_task_list(output)
211
- task_panel = PanelFormatter.create_task_panel(formatted_output)
212
+ task_panel = PanelFormatter.create_task_panel(
213
+ formatted_output
214
+ )
212
215
  self.console.print(task_panel)
213
216
  except Exception as e:
214
217
  self.logger.error(f"Error listing tasks: {e!s}")
@@ -218,6 +221,23 @@ class CLI:
218
221
  self.console.print(error_msg)
219
222
  continue
220
223
 
224
+ if user_input.lower() == "done":
225
+ self.logger.debug("User requested completed task list")
226
+ try:
227
+ output = self.todo_shell.list_completed()
228
+ formatted_output = TaskFormatter.format_completed_tasks(output)
229
+ task_panel = PanelFormatter.create_task_panel(
230
+ formatted_output, title="✅ Completed Tasks"
231
+ )
232
+ self.console.print(task_panel)
233
+ except Exception as e:
234
+ self.logger.error(f"Error listing completed tasks: {e!s}")
235
+ error_msg = ResponseFormatter.format_error(
236
+ f"Failed to list completed tasks: {e!s}"
237
+ )
238
+ self.console.print(error_msg)
239
+ continue
240
+
221
241
  self.logger.info(
222
242
  f"Processing user request: {user_input[:50]}{'...' if len(user_input) > 50 else ''}"
223
243
  )
@@ -225,10 +245,10 @@ class CLI:
225
245
 
226
246
  # Format the response and create a panel
227
247
  formatted_response = ResponseFormatter.format_response(response)
228
-
248
+
229
249
  # Get memory usage
230
250
  memory_usage = self._get_memory_usage()
231
-
251
+
232
252
  # Create response panel with memory usage
233
253
  response_panel = PanelFormatter.create_response_panel(
234
254
  formatted_response, memory_usage=memory_usage
@@ -7,7 +7,6 @@ from typing import Any, Dict, Optional
7
7
  from rich.align import Align
8
8
  from rich.box import ROUNDED
9
9
  from rich.panel import Panel
10
- from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn, TaskProgressColumn
11
10
  from rich.table import Table
12
11
  from rich.text import Text
13
12
 
@@ -22,14 +21,13 @@ class TaskFormatter:
22
21
  @staticmethod
23
22
  def format_task_list(raw_tasks: str) -> Text:
24
23
  """
25
- Format a raw task list with unicode characters and numbering.
24
+ Format a raw task list while preserving ANSI color codes from todo.sh.
26
25
 
27
26
  Args:
28
- raw_tasks: Raw task output from todo.sh
29
- title: Title for the task list
27
+ raw_tasks: Raw task output from todo.sh with ANSI codes
30
28
 
31
29
  Returns:
32
- Formatted task list as Rich Text object
30
+ Formatted task list as Rich Text object with preserved ANSI codes
33
31
  """
34
32
  if not raw_tasks.strip():
35
33
  return Text("No tasks found.")
@@ -38,19 +36,15 @@ class TaskFormatter:
38
36
  formatted_text = Text()
39
37
  task_count = 0
40
38
 
41
- # Add header
42
- formatted_text.append("Tasks", style="bold blue")
43
- formatted_text.append("\n\n")
39
+
44
40
 
45
41
  for line in lines:
46
42
  line = line.strip()
47
43
  # Skip empty lines, separators, and todo.sh's own summary line
48
44
  if line and line != "--" and not line.startswith("TODO:"):
49
45
  task_count += 1
50
- # Parse todo.txt format and make it more readable
51
- formatted_task = TaskFormatter._format_single_task(line, task_count)
52
- # Create a Text object that respects ANSI codes
53
- task_text = Text.from_ansi(formatted_task)
46
+ # Preserve the original ANSI codes by using Text.from_ansi directly
47
+ task_text = Text.from_ansi(line)
54
48
  formatted_text.append(task_text)
55
49
  formatted_text.append("\n")
56
50
 
@@ -63,6 +57,48 @@ class TaskFormatter:
63
57
 
64
58
  return formatted_text
65
59
 
60
+
61
+
62
+ @staticmethod
63
+ def format_completed_tasks(raw_tasks: str) -> Text:
64
+ """
65
+ Format a raw completed task list while preserving ANSI color codes from todo.sh.
66
+
67
+ Args:
68
+ raw_tasks: Raw completed task output from todo.sh with ANSI codes
69
+
70
+ Returns:
71
+ Formatted completed task list as Rich Text object with preserved ANSI codes
72
+ """
73
+ if not raw_tasks.strip():
74
+ return Text("No completed tasks found.")
75
+
76
+ lines = raw_tasks.strip().split("\n")
77
+ formatted_text = Text()
78
+ task_count = 0
79
+
80
+
81
+
82
+ for line in lines:
83
+ line = line.strip()
84
+ # Skip empty lines, separators, and todo.sh's own summary line
85
+ if line and line != "--" and not line.startswith("TODO:"):
86
+ task_count += 1
87
+ # Preserve the original ANSI codes by using Text.from_ansi directly
88
+ task_text = Text.from_ansi(line)
89
+ formatted_text.append(task_text)
90
+ formatted_text.append("\n")
91
+
92
+ # Add task count at the end
93
+ if task_count > 0:
94
+ formatted_text.append("\n")
95
+ else:
96
+ formatted_text = Text("No completed tasks found.")
97
+
98
+ return formatted_text
99
+
100
+
101
+
66
102
  @staticmethod
67
103
  def _format_single_task(task_line: str, task_number: int) -> str:
68
104
  """
@@ -101,6 +137,33 @@ class TaskFormatter:
101
137
 
102
138
  return formatted_line
103
139
 
140
+ @staticmethod
141
+ def _format_single_completed_task(task_line: str, task_number: int) -> str:
142
+ """
143
+ Format a single completed task line with unicode characters.
144
+
145
+ Args:
146
+ task_line: Raw completed task line from todo.sh
147
+ task_number: Task number for display
148
+
149
+ Returns:
150
+ Formatted completed task string
151
+ """
152
+ # Parse completed task format: "x 2025-08-29 2025-08-28 Clean cat box @home +chores"
153
+ # The format is: "x completion_date creation_date description"
154
+ parts = task_line.split(
155
+ " ", 2
156
+ ) # Split on first two spaces to separate x, dates, and description
157
+ if len(parts) < 3:
158
+ return f" {task_number:2d} │ │ {task_line}"
159
+
160
+ description = parts[2]
161
+
162
+ # Format with unicode characters
163
+ formatted_line = f" {task_number:2d} │ {description}"
164
+
165
+ return formatted_line
166
+
104
167
  @staticmethod
105
168
  def format_projects(raw_projects: str) -> str:
106
169
  """
@@ -279,6 +342,7 @@ class TableFormatter:
279
342
  ("help", "Show this help message"),
280
343
  ("about", "Show application information"),
281
344
  ("list", "List all tasks (no LLM interaction)"),
345
+ ("done", "List completed tasks (no LLM interaction)"),
282
346
  ("quit", "Exit the application"),
283
347
  ]
284
348
 
@@ -340,28 +404,32 @@ class PanelFormatter:
340
404
  )
341
405
 
342
406
  @staticmethod
343
- def create_task_panel(content: str, title: str = "📋 Current Tasks") -> Panel:
407
+ def create_task_panel(content: str | Text, title: str = "📋 Current Tasks") -> Panel:
344
408
  """Create a panel for displaying task lists."""
345
409
  return Panel(
346
410
  content, title=title, border_style="dim", box=ROUNDED, width=PANEL_WIDTH
347
411
  )
348
412
 
349
413
  @staticmethod
350
- def create_response_panel(content: str, title: str = "🤖 Assistant", memory_usage: Optional[Text] = None) -> Panel:
414
+ def create_response_panel(
415
+ content: str, title: str = "🤖 Assistant", memory_usage: Optional[Text] = None
416
+ ) -> Panel:
351
417
  """Create a panel for displaying LLM responses."""
352
418
  if memory_usage:
353
419
  # Create the combined content with centered memory usage
420
+ combined_content = Text()
421
+ combined_content.append(content)
422
+ combined_content.append("\n\n")
423
+ combined_content.append("─" * (PANEL_WIDTH - 4)) # Separator line
424
+ combined_content.append("\n")
425
+ combined_content.append(memory_usage)
426
+
354
427
  return Panel(
355
- Align.center(
356
- Text.assemble(
357
- content,
358
- "\n\n",
359
- "─" * (PANEL_WIDTH - 4), # Separator line
360
- "\n",
361
- memory_usage
362
- )
363
- ),
364
- title=title, border_style="dim", box=ROUNDED, width=PANEL_WIDTH
428
+ Align.center(combined_content),
429
+ title=title,
430
+ border_style="dim",
431
+ box=ROUNDED,
432
+ width=PANEL_WIDTH,
365
433
  )
366
434
  else:
367
435
  return Panel(
@@ -415,43 +483,71 @@ class PanelFormatter:
415
483
  )
416
484
 
417
485
  @staticmethod
418
- def create_memory_usage_bar(current_tokens: int, max_tokens: int, current_messages: int, max_messages: int) -> Text:
486
+ def create_memory_usage_bar(
487
+ current_tokens: int, max_tokens: int, current_messages: int, max_messages: int
488
+ ) -> Text:
419
489
  """
420
490
  Create a rich progress bar showing session memory usage.
421
-
491
+
422
492
  Args:
423
493
  current_tokens: Current number of tokens in conversation
424
494
  max_tokens: Maximum allowed tokens
425
495
  current_messages: Current number of messages in conversation
426
496
  max_messages: Maximum allowed messages
427
-
497
+
428
498
  Returns:
429
499
  Rich Text object with memory usage progress bar
430
500
  """
431
501
  # Calculate percentage
432
502
  token_percentage = min(100, (current_tokens / max_tokens) * 100)
433
-
434
- # Determine color based on usage
435
- if token_percentage >= 90:
436
- color = "red"
437
- elif token_percentage >= 75:
438
- color = "yellow"
439
- else:
440
- color = "green"
441
-
442
- # Create the progress bar text
503
+
504
+ # Create the progress bar text with new layout
443
505
  memory_text = Text()
444
- memory_text.append(f"{current_tokens:,}/{max_tokens:,} ", style="dim")
445
-
446
- # Create a simple text-based progress bar
447
- bar_length = 25
506
+
507
+ # Calculate available width (PANEL_WIDTH - 4 for borders = 94 chars)
508
+ # Account for emoji width by using a slightly reduced width
509
+ available_width = 92 # Balance between full width and emoji spacing
510
+
511
+ # Left section: Floppy disk + token count
512
+ left_section = f"💾 {current_tokens:,}/{max_tokens:,}"
513
+ left_width = len(left_section)
514
+
515
+ # Right section: Message count + floppy disk
516
+ right_section = f"{current_messages}/{max_messages} 💾"
517
+ right_width = len(right_section)
518
+
519
+ # Center section: Progress bar (2x wider = 50 chars) + percentage
520
+ bar_length = 50
448
521
  token_filled = int((token_percentage / 100) * bar_length)
449
522
  token_bar = "█" * token_filled + "░" * (bar_length - token_filled)
450
- memory_text.append(f"[{token_bar}] ", style="dim")
451
- memory_text.append(f"{token_percentage:.1f}%", style="dim")
452
-
453
- # Add message count without progress bar
454
- memory_text.append(" | ", style="dim")
455
- memory_text.append(f"{current_messages}/{max_messages}", style="dim")
456
-
523
+ center_section = f"[{token_bar}] {token_percentage:.1f}%"
524
+ center_width = len(center_section)
525
+
526
+ # Calculate spacing to center the progress bar
527
+ total_content_width = left_width + center_width + right_width
528
+ remaining_space = available_width - total_content_width
529
+
530
+ # Ensure we have enough space, if not, reduce the progress bar length
531
+ if remaining_space < 0:
532
+ # Reduce bar length to fit everything
533
+ excess = abs(remaining_space)
534
+ bar_length = max(30, 50 - excess) # Minimum 30 chars
535
+ token_filled = int((token_percentage / 100) * bar_length)
536
+ token_bar = "█" * token_filled + "░" * (bar_length - token_filled)
537
+ center_section = f"[{token_bar}] {token_percentage:.1f}%"
538
+ center_width = len(center_section)
539
+ total_content_width = left_width + center_width + right_width
540
+ remaining_space = available_width - total_content_width
541
+
542
+ # Distribute remaining space equally for symmetrical layout
543
+ left_spacing = remaining_space // 2
544
+ right_spacing = remaining_space - left_spacing
545
+
546
+ # Build the final layout
547
+ memory_text.append(left_section, style="dim")
548
+ memory_text.append(" " * left_spacing)
549
+ memory_text.append(center_section, style="dim")
550
+ memory_text.append(" " * right_spacing)
551
+ memory_text.append(right_section, style="dim")
552
+
457
553
  return memory_text
@@ -2,6 +2,7 @@
2
2
  Tool definitions and schemas for LLM function calling.
3
3
  """
4
4
 
5
+ import subprocess
5
6
  from typing import Any, Callable, Dict, List, Optional
6
7
 
7
8
  try:
@@ -62,8 +63,12 @@ class ToolCallHandler:
62
63
  "1) User wants to see their tasks, "
63
64
  "2) You need to find a specific task by description, "
64
65
  "3) You need to check for potential duplicates before adding new tasks, "
65
- "4) You need to understand the current state before making changes. "
66
+ "4) You need to understand the current state before making changes, "
67
+ "5) User says they finished/completed a task and you need to find the matching task. "
66
68
  "CRITICAL: ALWAYS use this before add_task() to check for similar existing tasks. "
69
+ "CRITICAL: ALWAYS use this when user mentions task completion to find the correct task number. "
70
+ "COMPLETION INTELLIGENCE: When searching for completion matches, if exactly one task clearly "
71
+ "matches the user's description, proceed to complete it immediately without asking for confirmation. "
67
72
  "IMPORTANT: When presenting the results to the user, convert the raw todo.txt format "
68
73
  "into conversational language. Do not show the raw format like '(A) task +project @context'. "
69
74
  "STRATEGIC CONTEXT: This is the primary discovery tool - call this FIRST when you need to "
@@ -76,7 +81,7 @@ class ToolCallHandler:
76
81
  "type": "string",
77
82
  "description": (
78
83
  "Optional filter string (e.g., '+work', '@office', '(A)') - "
79
- "use when you want to see only specific tasks"
84
+ "use when you want to see only specific tasks or when searching for completion matches"
80
85
  ),
81
86
  }
82
87
  },
@@ -166,7 +171,10 @@ class ToolCallHandler:
166
171
  "use list_tasks() and list_completed_tasks() to check for potential duplicates. Look for tasks with "
167
172
  "similar descriptions, keywords, or intent. If you find similar tasks, "
168
173
  "ask the user if they want to add a new task or modify an existing one. "
169
- "If project or context is ambiguous, use discovery tools first. "
174
+ "AUTOMATIC INFERENCE: When project, context, or due date is not specified, automatically infer appropriate tags "
175
+ "and due dates based on the task content, natural language expressions, task nature, calendar context, and existing patterns. "
176
+ "DUE DATE INFERENCE: Extract temporal expressions and use common sense to infer appropriate due dates based on task type, "
177
+ "work patterns, personal schedules, and existing task due date patterns. Only ask for clarification when genuinely ambiguous. "
170
178
  "Always provide a complete, natural response to the user. "
171
179
  "STRATEGIC CONTEXT: This is a modification tool - call this LAST after using "
172
180
  "discovery tools (list_tasks, list_projects, list_contexts list_completed_tasks) "
@@ -193,7 +201,7 @@ class ToolCallHandler:
193
201
  },
194
202
  "due": {
195
203
  "type": "string",
196
- "description": "Optional due date in YYYY-MM-DD format",
204
+ "description": "Optional due date in YYYY-MM-DD format. Automatically inferred from natural language expressions like 'tomorrow', 'next week', 'by Friday', 'urgent', 'asap'",
197
205
  },
198
206
  },
199
207
  "required": ["description"],
@@ -206,11 +214,13 @@ class ToolCallHandler:
206
214
  "name": "complete_task",
207
215
  "description": (
208
216
  "Mark a specific task as complete by its line number. IMPORTANT: "
209
- "Before completing, use list_completed_tasks() to check if it's already done. "
210
- "If multiple tasks match the description, ask the user to clarify which one. "
217
+ "When user says they finished/completed a task, FIRST use list_tasks() to find matching tasks, "
218
+ "then list_completed_tasks() to verify it's not already done. "
219
+ "COMPLETION LOGIC: If user's statement clearly matches exactly one task, complete it immediately. "
220
+ "If multiple tasks match, show numbered options and ask for clarification. "
221
+ "If the match is ambiguous, ask for confirmation. "
211
222
  "STRATEGIC CONTEXT: This is a modification tool - call this LAST after using "
212
- "discovery tools (list_tasks, list_completed_tasks) "
213
- "to verify the task exists and hasn't already been completed."
223
+ "discovery tools (list_tasks, list_completed_tasks) to verify the task exists and status."
214
224
  ),
215
225
  "parameters": {
216
226
  "type": "object",
@@ -434,8 +444,69 @@ class ToolCallHandler:
434
444
  "parameters": {"type": "object", "properties": {}, "required": []},
435
445
  },
436
446
  },
447
+ {
448
+ "type": "function",
449
+ "function": {
450
+ "name": "get_calendar",
451
+ "description": (
452
+ "Get a calendar for a specific month and year using the system 'cal' command. "
453
+ "Use this when: "
454
+ "1) User asks to see a calendar for a specific month/year, "
455
+ "2) User wants to plan tasks around specific dates, "
456
+ "3) User needs to see what day of the week a date falls on, "
457
+ "4) User wants to visualize the current month or upcoming months. "
458
+ "The calendar will show the month in a traditional calendar format with days of the week. "
459
+ "IMPORTANT: When displaying the calendar output, present it directly without wrapping in backticks or code blocks. "
460
+ "The calendar should be displayed as plain text in the conversation."
461
+ ),
462
+ "parameters": {
463
+ "type": "object",
464
+ "properties": {
465
+ "month": {
466
+ "type": "integer",
467
+ "description": "Month number (1-12, where 1=January, 12=December)",
468
+ "minimum": 1,
469
+ "maximum": 12,
470
+ },
471
+ "year": {
472
+ "type": "integer",
473
+ "description": "Year (4-digit format, e.g., 2025)",
474
+ "minimum": 1900,
475
+ "maximum": 2100,
476
+ },
477
+ },
478
+ "required": ["month", "year"],
479
+ },
480
+ },
481
+ },
437
482
  ]
438
483
 
484
+ def _get_calendar(self, month: int, year: int) -> str:
485
+ """
486
+ Get a calendar for the specified month and year using the system 'cal' command.
487
+
488
+ Args:
489
+ month: Month number (1-12)
490
+ year: Year (4-digit format)
491
+
492
+ Returns:
493
+ Calendar output as a string
494
+ """
495
+ try:
496
+ # Use the cal command with specific month and year
497
+ result = subprocess.run(
498
+ ["cal", str(month), str(year)],
499
+ capture_output=True,
500
+ text=True,
501
+ check=True,
502
+ )
503
+ return result.stdout.strip()
504
+ except (subprocess.SubprocessError, FileNotFoundError):
505
+ # Fallback to Python calendar module
506
+ import calendar
507
+
508
+ return calendar.month(year, month).strip()
509
+
439
510
  def _format_tool_signature(self, tool_name: str, arguments: Dict[str, Any]) -> str:
440
511
  """Format tool signature with parameters for logging."""
441
512
  if not arguments:
@@ -503,6 +574,7 @@ class ToolCallHandler:
503
574
  "move_task": self.todo_manager.move_task,
504
575
  "archive_tasks": self.todo_manager.archive_tasks,
505
576
  "deduplicate_tasks": self.todo_manager.deduplicate_tasks,
577
+ "get_calendar": self._get_calendar,
506
578
  }
507
579
 
508
580
  if tool_name not in method_map:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: todo-agent
3
- Version: 0.2.4
3
+ Version: 0.2.6
4
4
  Summary: A natural language interface for todo.sh task management
5
5
  Author: codeprimate
6
6
  Maintainer: codeprimate
@@ -0,0 +1,29 @@
1
+ todo_agent/__init__.py,sha256=RUowhd14r3tqB_7rl83unGV8oBjra3UOIl7jix-33fk,254
2
+ todo_agent/_version.py,sha256=2Q6v117QPuRsVsIEaHT3nJJVx7xxa47FYOkmuhVbGAI,704
3
+ todo_agent/main.py,sha256=-ryhMm4c4sz4e4anXI8B-CYnpEh5HIkmnYcnGxcWHDk,1628
4
+ todo_agent/core/__init__.py,sha256=QAZ4it63pXv5-DxtNcuSAmg7ZnCY5ackI5yycvKHr9I,365
5
+ todo_agent/core/conversation_manager.py,sha256=gSCcX356UJ0T3FCTS1q0fOud0ytFKXptf9RyKjzpTYI,11640
6
+ todo_agent/core/exceptions.py,sha256=cPvvkIbKdI7l51wC7cE-ZxUi54P3nf2m7x2lMNMRFYM,399
7
+ todo_agent/core/todo_manager.py,sha256=Dyc5NbDd_u21nJlS-C8KxefGJpEcHOLtL21qqQEit2Q,9142
8
+ todo_agent/infrastructure/__init__.py,sha256=SGbHXgzq6U1DMgOfWPMsWEK99zjPSF-6gzy7xqc5fsI,284
9
+ todo_agent/infrastructure/calendar_utils.py,sha256=HmF0ykXF_6GbdoJvZLIv6fKwT6ipixoywdTMkIXmkGU,1871
10
+ todo_agent/infrastructure/config.py,sha256=zyp6qOlg1nN_awphivlgGNBE6fL0Hf66YgvWxR8ldyQ,2117
11
+ todo_agent/infrastructure/inference.py,sha256=J6i9jtzOVo2Yy3e6yAUBH22ik3OqHs1I0zrADhs4IRk,10676
12
+ todo_agent/infrastructure/llm_client.py,sha256=ZoObyqaRP6i_eqGYGfJWGeWTJ-VNxpY70ay04vt2v_E,1390
13
+ todo_agent/infrastructure/llm_client_factory.py,sha256=-tktnVOIF7B45WR7AuLoi7MKnEyuM8lgg1jjc4T1FhM,1929
14
+ todo_agent/infrastructure/logger.py,sha256=2ykG_0lyzmEGxDF6ZRl1qiTUGDuFeQgzv4Na6vRmXcM,4110
15
+ todo_agent/infrastructure/ollama_client.py,sha256=zElS9OhkieCJQFSUxBqBd6k-u9I0cIpMwJG08dLQ1QA,5926
16
+ todo_agent/infrastructure/openrouter_client.py,sha256=u-yIENA6PVpBKS0oy6l-muzZDvCtJIP9lF8AjeHgvtQ,7153
17
+ todo_agent/infrastructure/todo_shell.py,sha256=z6kqUKDX-i4DfYJKoOLiPLCp8y6m1HdTDLHTvmLpzMc,5801
18
+ todo_agent/infrastructure/token_counter.py,sha256=PCKheOVJbp1s89yhh_i6iKgURMt9mVoYkwjQJCc2xCE,4958
19
+ todo_agent/infrastructure/prompts/system_prompt.txt,sha256=Y2bb9gcdpJeybaaBdBZhI0FmmQ5jJQwnz0BdSRGLJCw,10726
20
+ todo_agent/interface/__init__.py,sha256=vDD3rQu4qDkpvVwGVtkDzE1M4IiSHYzTif4GbYSFWaI,457
21
+ todo_agent/interface/cli.py,sha256=1nPUFEzbSl4bdy6hcIGM1izGWDwbjaijfBCWfPBb31E,12062
22
+ todo_agent/interface/formatters.py,sha256=Oc7ynL7vpb4i8f-XQM38gJlqTOVZfzyTBwWceeMHV_Y,18912
23
+ todo_agent/interface/tools.py,sha256=RkxsyqYbp0QYwtnJMXixBf727Atcp8DY6dm6949DTMY,32739
24
+ todo_agent-0.2.6.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
25
+ todo_agent-0.2.6.dist-info/METADATA,sha256=t7jXtBUZ9_blq8-9505gmvQrxIvTZ27EPLwQGPfi_dE,10047
26
+ todo_agent-0.2.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
27
+ todo_agent-0.2.6.dist-info/entry_points.txt,sha256=4W7LrCib6AXP5IZDwWRht8S5gutLu5oNfTJHGbt4oHs,52
28
+ todo_agent-0.2.6.dist-info/top_level.txt,sha256=a65mlPIhPZHuq2bRIi_sCMAIJsUddvXt171OBF6r6co,11
29
+ todo_agent-0.2.6.dist-info/RECORD,,
@@ -1,28 +0,0 @@
1
- todo_agent/__init__.py,sha256=RUowhd14r3tqB_7rl83unGV8oBjra3UOIl7jix-33fk,254
2
- todo_agent/_version.py,sha256=NRw4Jle4n9v_DD2wtplRqflGCvX8OU5eAjycYY0vY3Y,704
3
- todo_agent/main.py,sha256=-ryhMm4c4sz4e4anXI8B-CYnpEh5HIkmnYcnGxcWHDk,1628
4
- todo_agent/core/__init__.py,sha256=QAZ4it63pXv5-DxtNcuSAmg7ZnCY5ackI5yycvKHr9I,365
5
- todo_agent/core/conversation_manager.py,sha256=gSCcX356UJ0T3FCTS1q0fOud0ytFKXptf9RyKjzpTYI,11640
6
- todo_agent/core/exceptions.py,sha256=cPvvkIbKdI7l51wC7cE-ZxUi54P3nf2m7x2lMNMRFYM,399
7
- todo_agent/core/todo_manager.py,sha256=wV-J_E_aK7yRNT-WbILKJgqBFhbcXDZa6Pb4IjbgGkU,7974
8
- todo_agent/infrastructure/__init__.py,sha256=SGbHXgzq6U1DMgOfWPMsWEK99zjPSF-6gzy7xqc5fsI,284
9
- todo_agent/infrastructure/config.py,sha256=zyp6qOlg1nN_awphivlgGNBE6fL0Hf66YgvWxR8ldyQ,2117
10
- todo_agent/infrastructure/inference.py,sha256=XCM18hpHwWxDLV-31yO_UOYbqQ7lRjGz-PQduyp6Xio,10289
11
- todo_agent/infrastructure/llm_client.py,sha256=ZoObyqaRP6i_eqGYGfJWGeWTJ-VNxpY70ay04vt2v_E,1390
12
- todo_agent/infrastructure/llm_client_factory.py,sha256=SmPHNS4QifFI8CmsAz7uTcYjTdFzq8x9jVNybOmyerk,1884
13
- todo_agent/infrastructure/logger.py,sha256=2ykG_0lyzmEGxDF6ZRl1qiTUGDuFeQgzv4Na6vRmXcM,4110
14
- todo_agent/infrastructure/ollama_client.py,sha256=6WsjSftsHNt-CeScK6GSrJ_CMe80OiATT3idcJgPCBk,5654
15
- todo_agent/infrastructure/openrouter_client.py,sha256=GVOJTzPDOKNdHEu-y-HQygMcLvrFw1KB6NZU_rJR3-c,6859
16
- todo_agent/infrastructure/todo_shell.py,sha256=z6kqUKDX-i4DfYJKoOLiPLCp8y6m1HdTDLHTvmLpzMc,5801
17
- todo_agent/infrastructure/token_counter.py,sha256=PCKheOVJbp1s89yhh_i6iKgURMt9mVoYkwjQJCc2xCE,4958
18
- todo_agent/infrastructure/prompts/system_prompt.txt,sha256=uCb6yz3uDQdwcB8HJcF0y1_1b75oRtRnCMMHQLHI3NI,2415
19
- todo_agent/interface/__init__.py,sha256=vDD3rQu4qDkpvVwGVtkDzE1M4IiSHYzTif4GbYSFWaI,457
20
- todo_agent/interface/cli.py,sha256=FacwTDjLP60qAQ_BsM_7_jM9liYJpZpRdXpBNwNuO2Y,11060
21
- todo_agent/interface/formatters.py,sha256=QT6IgBVX6ghL8UWH54KFtJY9n9GbrPvOizjCN8emMGg,15503
22
- todo_agent/interface/tools.py,sha256=mlPPLVwECYrTtOX8ysIORRfErIOJl43qlTvfdpy2Vbs,28559
23
- todo_agent-0.2.4.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
24
- todo_agent-0.2.4.dist-info/METADATA,sha256=fYnY58TYzNuQtiu2WBr3fRJwJEIh6bbt4gA2lrNC_9A,10047
25
- todo_agent-0.2.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
26
- todo_agent-0.2.4.dist-info/entry_points.txt,sha256=4W7LrCib6AXP5IZDwWRht8S5gutLu5oNfTJHGbt4oHs,52
27
- todo_agent-0.2.4.dist-info/top_level.txt,sha256=a65mlPIhPZHuq2bRIi_sCMAIJsUddvXt171OBF6r6co,11
28
- todo_agent-0.2.4.dist-info/RECORD,,