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 +2 -2
- todo_agent/core/todo_manager.py +33 -0
- todo_agent/infrastructure/calendar_utils.py +64 -0
- todo_agent/infrastructure/inference.py +13 -2
- todo_agent/infrastructure/llm_client_factory.py +4 -2
- todo_agent/infrastructure/ollama_client.py +6 -0
- todo_agent/infrastructure/openrouter_client.py +6 -0
- todo_agent/infrastructure/prompts/system_prompt.txt +125 -16
- todo_agent/interface/cli.py +31 -11
- todo_agent/interface/formatters.py +144 -48
- todo_agent/interface/tools.py +80 -8
- {todo_agent-0.2.4.dist-info → todo_agent-0.2.6.dist-info}/METADATA +1 -1
- todo_agent-0.2.6.dist-info/RECORD +29 -0
- todo_agent-0.2.4.dist-info/RECORD +0 -28
- {todo_agent-0.2.4.dist-info → todo_agent-0.2.6.dist-info}/WHEEL +0 -0
- {todo_agent-0.2.4.dist-info → todo_agent-0.2.6.dist-info}/entry_points.txt +0 -0
- {todo_agent-0.2.4.dist-info → todo_agent-0.2.6.dist-info}/licenses/LICENSE +0 -0
- {todo_agent-0.2.4.dist-info → todo_agent-0.2.6.dist-info}/top_level.txt +0 -0
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.
|
32
|
-
__version_tuple__ = version_tuple = (0, 2,
|
31
|
+
__version__ = version = '0.2.6'
|
32
|
+
__version_tuple__ = version_tuple = (0, 2, 6)
|
33
33
|
|
34
34
|
__commit_id__ = commit_id = None
|
todo_agent/core/todo_manager.py
CHANGED
@@ -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
|
96
|
+
# Format the template with the tools section, current datetime, and calendar
|
88
97
|
return system_prompt_template.format(
|
89
|
-
tools_section=tools_section,
|
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 (
|
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.
|
7
|
-
2.
|
8
|
-
3.
|
9
|
-
4.
|
10
|
-
5.
|
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
|
-
|
19
|
-
|
20
|
-
|
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
|
-
|
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
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
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
|
-
-
|
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}
|
todo_agent/interface/cli.py
CHANGED
@@ -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
|
169
|
-
|
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(
|
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
|
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
|
-
|
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
|
-
#
|
51
|
-
|
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(
|
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
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
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(
|
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
|
-
#
|
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
|
-
|
445
|
-
|
446
|
-
#
|
447
|
-
|
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
|
-
|
451
|
-
|
452
|
-
|
453
|
-
#
|
454
|
-
|
455
|
-
|
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
|
todo_agent/interface/tools.py
CHANGED
@@ -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
|
-
"
|
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
|
-
"
|
210
|
-
"
|
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:
|
@@ -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,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|