todo-agent 0.2.8__py3-none-any.whl → 0.2.9__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 +111 -1
- todo_agent/infrastructure/openrouter_client.py +26 -20
- todo_agent/infrastructure/prompts/system_prompt.txt +84 -5
- todo_agent/infrastructure/todo_shell.py +332 -9
- todo_agent/interface/cli.py +5 -3
- todo_agent/interface/tools.py +104 -3
- {todo_agent-0.2.8.dist-info → todo_agent-0.2.9.dist-info}/METADATA +33 -65
- {todo_agent-0.2.8.dist-info → todo_agent-0.2.9.dist-info}/RECORD +13 -13
- {todo_agent-0.2.8.dist-info → todo_agent-0.2.9.dist-info}/WHEEL +0 -0
- {todo_agent-0.2.8.dist-info → todo_agent-0.2.9.dist-info}/entry_points.txt +0 -0
- {todo_agent-0.2.8.dist-info → todo_agent-0.2.9.dist-info}/licenses/LICENSE +0 -0
- {todo_agent-0.2.8.dist-info → todo_agent-0.2.9.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.9'
|
32
|
+
__version_tuple__ = version_tuple = (0, 2, 9)
|
33
33
|
|
34
34
|
__commit_id__ = commit_id = None
|
todo_agent/core/todo_manager.py
CHANGED
@@ -78,7 +78,9 @@ class TodoManager:
|
|
78
78
|
if interval < 1:
|
79
79
|
raise ValueError("Interval must be at least 1.")
|
80
80
|
except ValueError:
|
81
|
-
raise ValueError(
|
81
|
+
raise ValueError(
|
82
|
+
f"Invalid interval '{parts[2]}'. Must be a positive integer."
|
83
|
+
)
|
82
84
|
|
83
85
|
# Build the full task description with priority, project, and context
|
84
86
|
full_description = description
|
@@ -167,6 +169,114 @@ class TodoManager:
|
|
167
169
|
result = self.todo_shell.remove_priority(task_number)
|
168
170
|
return f"Removed priority from task {task_number}: {result}"
|
169
171
|
|
172
|
+
def set_due_date(self, task_number: int, due_date: str) -> str:
|
173
|
+
"""
|
174
|
+
Set or update due date for a task by intelligently rewriting it.
|
175
|
+
|
176
|
+
Args:
|
177
|
+
task_number: The task number to modify
|
178
|
+
due_date: Due date in YYYY-MM-DD format, or empty string to remove due date
|
179
|
+
|
180
|
+
Returns:
|
181
|
+
Confirmation message with the updated task
|
182
|
+
"""
|
183
|
+
# Validate due date format only if not empty
|
184
|
+
if due_date.strip():
|
185
|
+
try:
|
186
|
+
datetime.strptime(due_date, "%Y-%m-%d")
|
187
|
+
except ValueError:
|
188
|
+
raise ValueError(
|
189
|
+
f"Invalid due date format '{due_date}'. Must be YYYY-MM-DD."
|
190
|
+
)
|
191
|
+
|
192
|
+
result = self.todo_shell.set_due_date(task_number, due_date)
|
193
|
+
if due_date.strip():
|
194
|
+
return f"Set due date {due_date} for task {task_number}: {result}"
|
195
|
+
else:
|
196
|
+
return f"Removed due date from task {task_number}: {result}"
|
197
|
+
|
198
|
+
def set_context(self, task_number: int, context: str) -> str:
|
199
|
+
"""
|
200
|
+
Set or update context for a task by intelligently rewriting it.
|
201
|
+
|
202
|
+
Args:
|
203
|
+
task_number: The task number to modify
|
204
|
+
context: Context name (without @ symbol), or empty string to remove context
|
205
|
+
|
206
|
+
Returns:
|
207
|
+
Confirmation message with the updated task
|
208
|
+
"""
|
209
|
+
# Validate context name if not empty
|
210
|
+
if context.strip():
|
211
|
+
# Remove any existing @ symbols to prevent duplication
|
212
|
+
clean_context = context.strip().lstrip("@")
|
213
|
+
if not clean_context:
|
214
|
+
raise ValueError(
|
215
|
+
"Context name cannot be empty after removing @ symbol."
|
216
|
+
)
|
217
|
+
|
218
|
+
result = self.todo_shell.set_context(task_number, context)
|
219
|
+
if context.strip():
|
220
|
+
clean_context = context.strip().lstrip("@")
|
221
|
+
return f"Set context @{clean_context} for task {task_number}: {result}"
|
222
|
+
else:
|
223
|
+
return f"Removed context from task {task_number}: {result}"
|
224
|
+
|
225
|
+
def set_project(self, task_number: int, projects: list) -> str:
|
226
|
+
"""
|
227
|
+
Set or update projects for a task by intelligently rewriting it.
|
228
|
+
|
229
|
+
Args:
|
230
|
+
task_number: The task number to modify
|
231
|
+
projects: List of project operations. Each item can be:
|
232
|
+
- "project" (add project)
|
233
|
+
- "-project" (remove project)
|
234
|
+
- Empty string removes all projects
|
235
|
+
|
236
|
+
Returns:
|
237
|
+
Confirmation message with the updated task
|
238
|
+
"""
|
239
|
+
# Validate project names if not empty
|
240
|
+
if projects:
|
241
|
+
for project in projects:
|
242
|
+
if project.strip() and not project.startswith("-"):
|
243
|
+
# Remove any existing + symbols to prevent duplication
|
244
|
+
clean_project = project.strip().lstrip("+")
|
245
|
+
if not clean_project:
|
246
|
+
raise ValueError(
|
247
|
+
"Project name cannot be empty after removing + symbol."
|
248
|
+
)
|
249
|
+
elif project.startswith("-"):
|
250
|
+
clean_project = project[1:].strip().lstrip("+")
|
251
|
+
if not clean_project:
|
252
|
+
raise ValueError(
|
253
|
+
"Project name cannot be empty after removing - and + symbols."
|
254
|
+
)
|
255
|
+
|
256
|
+
result = self.todo_shell.set_project(task_number, projects)
|
257
|
+
|
258
|
+
if not projects:
|
259
|
+
return f"No project changes made to task {task_number}: {result}"
|
260
|
+
else:
|
261
|
+
# Build operation description
|
262
|
+
operations = []
|
263
|
+
for project in projects:
|
264
|
+
if not project.strip():
|
265
|
+
# Empty string is a NOOP - skip
|
266
|
+
continue
|
267
|
+
elif project.startswith("-"):
|
268
|
+
clean_project = project[1:].strip().lstrip("+")
|
269
|
+
operations.append(f"removed +{clean_project}")
|
270
|
+
else:
|
271
|
+
clean_project = project.strip().lstrip("+")
|
272
|
+
operations.append(f"added +{clean_project}")
|
273
|
+
|
274
|
+
if not operations:
|
275
|
+
return f"No project changes made to task {task_number}: {result}"
|
276
|
+
else:
|
277
|
+
operation_desc = ", ".join(operations)
|
278
|
+
return f"Updated projects for task {task_number} ({operation_desc}): {result}"
|
279
|
+
|
170
280
|
def list_projects(self, **kwargs: Any) -> str:
|
171
281
|
"""List all available projects in todo.txt."""
|
172
282
|
result = self.todo_shell.list_projects()
|
@@ -72,26 +72,32 @@ class OpenRouterClient(LLMClient):
|
|
72
72
|
f"Token usage - Prompt: {prompt_tokens}, Completion: {completion_tokens}, Total: {total_tokens}"
|
73
73
|
)
|
74
74
|
|
75
|
-
#
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
75
|
+
# Extract and log choice details
|
76
|
+
choices = response.get("choices", [])
|
77
|
+
if not choices:
|
78
|
+
return
|
79
|
+
|
80
|
+
choice = choices[0]
|
81
|
+
message = choice.get("message", {})
|
82
|
+
|
83
|
+
# Always log reasoning and content if present
|
84
|
+
reasoning = message.get("reasoning", "")
|
85
|
+
if reasoning:
|
86
|
+
self.logger.info(f"LLM reasoning: {reasoning}")
|
87
|
+
|
88
|
+
content = message.get("content", "")
|
89
|
+
if content:
|
90
|
+
self.logger.info(f"LLM content: {content}")
|
91
|
+
|
92
|
+
# Handle tool calls
|
93
|
+
tool_calls = message.get("tool_calls", [])
|
94
|
+
if tool_calls:
|
95
|
+
self.logger.info(f"Response contains {len(tool_calls)} tool calls")
|
96
|
+
|
97
|
+
# Log each tool call
|
98
|
+
for i, tool_call in enumerate(tool_calls, 1):
|
99
|
+
tool_name = tool_call.get("function", {}).get("name", "unknown")
|
100
|
+
self.logger.info(f" Tool call {i}: {tool_name}")
|
95
101
|
|
96
102
|
self.logger.debug(f"Raw response: {json.dumps(response, indent=2)}")
|
97
103
|
|
@@ -10,7 +10,7 @@ CORE PRINCIPLES
|
|
10
10
|
- Present in natural language with strategic insights, never raw todo.txt format
|
11
11
|
- Include concise reasoning for all decisions and suggestions
|
12
12
|
- Make confident and specific recommendations
|
13
|
-
- Lead with the answer then follow with minimal supporting information afterward
|
13
|
+
- Lead with the answer or description of actions then follow with minimal supporting information afterward
|
14
14
|
- Prose is always preferred to formatted lists or formatting in general
|
15
15
|
|
16
16
|
OUTPUT FORMATTING
|
@@ -66,17 +66,20 @@ Gate 3: Execution Protocols
|
|
66
66
|
Task Creation Protocol:
|
67
67
|
DISCOVER: Current tasks + completed tasks
|
68
68
|
ANALYZE: Semantic duplicates? (similar intent/keywords)
|
69
|
-
INFER:
|
69
|
+
INFER: Context/timing from:
|
70
70
|
- Explicit temporal: "tomorrow", "by Friday"
|
71
71
|
- Task nature: bills->payment cycles, work->business hours
|
72
72
|
- Existing patterns: match similar task contexts
|
73
73
|
- Calendar context: work days, weekends, holidays
|
74
|
-
-
|
74
|
+
- Project inference: ALWAYS infer and add unambiguous +project tags
|
75
|
+
- Context inference: ALWAYS infer and add unambiguous @context tag
|
76
|
+
- Duration inference: ALWAYS infer and add appropriate duration: tag
|
77
|
+
- ACTIVATE Completion Date Intelligence Engine for due date inference
|
75
78
|
DECIDE:
|
76
79
|
- Clear intent + high confidence -> Create immediately
|
77
80
|
- Semantic duplicate found -> "Similar task exists: [task]. Add anyway or modify existing?"
|
78
81
|
- Ambiguous context -> Ask specific clarification
|
79
|
-
- Low confidence inference ->
|
82
|
+
- Low confidence inference -> Ask before acting
|
80
83
|
|
81
84
|
Completion Date Intelligence Engine
|
82
85
|
When inferring convenient and realistic completion dates:
|
@@ -124,7 +127,7 @@ MATCH:
|
|
124
127
|
- Single clear match -> Complete immediately + suggest next steps
|
125
128
|
- Multiple candidates -> Show numbered options with context
|
126
129
|
- Fuzzy match -> "Did you mean: [closest match]?"
|
127
|
-
- No match -> "No matching active tasks found. Recent completions?"
|
130
|
+
- No match -> "No matching active tasks found. Relax filters? Recent completions?"
|
128
131
|
|
129
132
|
STRATEGIC INTELLIGENCE MODULES
|
130
133
|
|
@@ -269,6 +272,56 @@ When encountering "rec:" syntax in task descriptions or the user indicates repea
|
|
269
272
|
- Preserve all task metadata (priority, project, context) in new instances
|
270
273
|
- Handle edge cases: leap years, month boundaries, weekday patterns
|
271
274
|
|
275
|
+
DURATION INTELLIGENCE ENGINE
|
276
|
+
When encountering duration: syntax in task descriptions:
|
277
|
+
- Parse duration patterns: duration:15m, duration:2h, duration:1d
|
278
|
+
- Apply reasonable defaults for tasks without duration tags
|
279
|
+
- Validate duration ranges (1m to 8h for most tasks)
|
280
|
+
- Use duration for task organization and prioritization
|
281
|
+
|
282
|
+
AUTOMATIC PROJECT AND CONTEXT INFERENCE:
|
283
|
+
When creating tasks, ALWAYS infer and add unambiguous project and context tags based on:
|
284
|
+
- Task description keywords and semantic patterns
|
285
|
+
- Existing project and context usage patterns in current and completed tasks
|
286
|
+
- Task nature and typical locations/contexts
|
287
|
+
- User's historical task organization
|
288
|
+
|
289
|
+
Project Inference Patterns:
|
290
|
+
- Health/medical: +health (dentist, doctor, pharmacy, exercise, vitamins)
|
291
|
+
- Work/business: +work (meetings, reports, presentations, emails, calls)
|
292
|
+
- Financial: +bills (payments, invoices, banking, taxes, expenses)
|
293
|
+
- Home maintenance: +chores (cleaning, repairs, maintenance, organization)
|
294
|
+
- Personal development: +learning (reading, courses, skills, education)
|
295
|
+
- Social: +social (calls, meetings, events, family, friends)
|
296
|
+
- Errands: +errands (shopping, appointments, deliveries, services)
|
297
|
+
- Work in Progress: +wip (work tasks, projects, tasks pending dependencies)
|
298
|
+
|
299
|
+
Context Inference Patterns:
|
300
|
+
- @phone: calls, appointments, customer service, scheduling
|
301
|
+
- @computer: work, research, writing, online tasks, emails
|
302
|
+
- @office: work meetings, in-person work, office tasks
|
303
|
+
- @home: chores, maintenance, personal tasks, relaxation
|
304
|
+
- @errands: shopping, appointments, deliveries, services
|
305
|
+
- @grocery: food shopping, household supplies
|
306
|
+
- @post-office: mail, packages, shipping, government services
|
307
|
+
|
308
|
+
AUTOMATIC DURATION INFERENCE:
|
309
|
+
When creating tasks, ALWAYS infer and add appropriate duration tags based on:
|
310
|
+
- Task description keywords and context
|
311
|
+
- Project and context patterns
|
312
|
+
- Task complexity and scope
|
313
|
+
- User's existing task patterns
|
314
|
+
|
315
|
+
Default Duration Patterns:
|
316
|
+
- Quick tasks: duration:15m (calls, emails, simple errands, quick checks)
|
317
|
+
- Medium tasks: duration:1h (meetings, focused work, moderate chores)
|
318
|
+
- Long tasks: duration:2h (deep work, complex projects, major errands)
|
319
|
+
- Context-specific defaults:
|
320
|
+
- @phone tasks: duration:15m (calls, appointments)
|
321
|
+
- @computer tasks: duration:1h (work, research, writing)
|
322
|
+
- @errands tasks: duration:45m (shopping, appointments)
|
323
|
+
- @home tasks: duration:30m (chores, maintenance)
|
324
|
+
|
272
325
|
Recurring Task Completion Protocol:
|
273
326
|
1. Detect when user completes a recurring task
|
274
327
|
2. Find the original recurring task in active tasks
|
@@ -299,6 +352,7 @@ CORE BEHAVIORAL RULES (Hierarchical)
|
|
299
352
|
TODO.TXT FORMAT COMPLIANCE
|
300
353
|
Priority: (A) (B) (C) | Projects: +name | Contexts: @location
|
301
354
|
Due dates: due:YYYY-MM-DD | Completion: x YYYY-MM-DD description
|
355
|
+
Duration: duration:XX (e.g., duration:30m, duration:2h, duration:1d)
|
302
356
|
Recurring tasks: rec:frequency[:interval] (e.g., rec:daily, rec:weekly:2, rec:monthly)
|
303
357
|
Single symbols only (never ++project or @@context)
|
304
358
|
Do not duplicate elements such as +context or (PRIORITY) within a single task description.
|
@@ -306,6 +360,31 @@ Do not duplicate elements such as +context or (PRIORITY) within a single task de
|
|
306
360
|
Example recurring task format:
|
307
361
|
(A) Take vitamins +health @home rec:daily due:2025-01-15
|
308
362
|
|
363
|
+
Example task with duration:
|
364
|
+
(A) Review quarterly report +work @office duration:2h due:2025-01-20
|
365
|
+
|
366
|
+
TOOL SELECTION STRATEGY
|
367
|
+
|
368
|
+
CRITICAL TOOL CHOICE GUIDELINES:
|
369
|
+
1. **For Adding Project Tags (+project)**: ALWAYS use set_project()
|
370
|
+
2. **For Adding Context Tags (@context)**: ALWAYS use set_context()
|
371
|
+
3. **For Adding Due Dates** (due:YYYY-MM-DD): ALWAYS use set_due_date()
|
372
|
+
4. **For Task Discovery**: Use list_tasks() once with appropriate filters, not multiple calls
|
373
|
+
5. **For Task Completion**: Use list_tasks() + list_completed_tasks() + complete_task() sequence
|
374
|
+
6. **For Task Addition**: Use list_tasks() + list_completed_tasks() + add_task() sequence
|
375
|
+
|
376
|
+
EXAMPLE CORRECT WORKFLOW:
|
377
|
+
User: "Add exercise and outdoors projects to mow lawn"
|
378
|
+
CORRECT: list_tasks() → set_project(task_number=15, projects=["exercise", "outdoors"])
|
379
|
+
INCORRECT: list_tasks(filter="+exercise") → list_tasks(filter="+outdoors") → append_to_task()
|
380
|
+
|
381
|
+
EFFICIENT DISCOVERY PRINCIPLES:
|
382
|
+
- Use list_tasks() once with NO filters to understand the full context
|
383
|
+
- Use list_completed_tasks() once with NO filters to understand historical patterns
|
384
|
+
- Avoid multiple discovery calls unless absolutely necessary for disambiguation
|
385
|
+
- When adding tags/projects/contexts, first discover the target task, then use the appropriate set_* tool
|
386
|
+
- Prefer single comprehensive discovery over multiple targeted searches
|
387
|
+
|
309
388
|
TOOL CALL FORMAT
|
310
389
|
|
311
390
|
IMPORTANT: Include concise reasoning in the content field when using tools.
|
@@ -142,12 +142,335 @@ class TodoShell:
|
|
142
142
|
"""Archive completed tasks."""
|
143
143
|
return self.execute(["todo.sh", "-f", "archive"])
|
144
144
|
|
145
|
-
def
|
146
|
-
"""
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
145
|
+
def set_due_date(self, task_number: int, due_date: str) -> str:
|
146
|
+
"""
|
147
|
+
Set or update due date for a task by intelligently rewriting it.
|
148
|
+
|
149
|
+
Args:
|
150
|
+
task_number: The task number to modify
|
151
|
+
due_date: Due date in YYYY-MM-DD format, or empty string to remove due date
|
152
|
+
|
153
|
+
Returns:
|
154
|
+
The updated task description
|
155
|
+
"""
|
156
|
+
# First, get the current task to parse its components
|
157
|
+
tasks_output = self.list_tasks()
|
158
|
+
task_lines = tasks_output.strip().split("\n")
|
159
|
+
|
160
|
+
# Find the task by its actual number (not array index)
|
161
|
+
current_task = None
|
162
|
+
for line in task_lines:
|
163
|
+
if line.strip():
|
164
|
+
# Extract task number from the beginning of the line (handling ANSI codes)
|
165
|
+
extracted_number = self._extract_task_number(line)
|
166
|
+
if extracted_number == task_number:
|
167
|
+
current_task = line
|
168
|
+
break
|
169
|
+
|
170
|
+
if not current_task:
|
171
|
+
raise TodoShellError(f"Task number {task_number} not found")
|
172
|
+
|
173
|
+
# Parse the current task components
|
174
|
+
components = self._parse_task_components(current_task)
|
175
|
+
|
176
|
+
# Update the due date (empty string removes it)
|
177
|
+
if due_date.strip():
|
178
|
+
components["due"] = due_date
|
179
|
+
else:
|
180
|
+
components["due"] = None
|
181
|
+
|
182
|
+
# Reconstruct the task
|
183
|
+
new_description = self._reconstruct_task(components)
|
184
|
+
|
185
|
+
# Replace the task with the new description
|
186
|
+
return self.replace(task_number, new_description)
|
187
|
+
|
188
|
+
def set_context(self, task_number: int, context: str) -> str:
|
189
|
+
"""
|
190
|
+
Set or update context for a task by intelligently rewriting it.
|
191
|
+
|
192
|
+
Args:
|
193
|
+
task_number: The task number to modify
|
194
|
+
context: Context name (without @ symbol), or empty string to remove context
|
195
|
+
|
196
|
+
Returns:
|
197
|
+
The updated task description
|
198
|
+
"""
|
199
|
+
# First, get the current task to parse its components
|
200
|
+
tasks_output = self.list_tasks()
|
201
|
+
task_lines = tasks_output.strip().split("\n")
|
202
|
+
|
203
|
+
# Find the task by its actual number (not array index)
|
204
|
+
current_task = None
|
205
|
+
for line in task_lines:
|
206
|
+
if line.strip():
|
207
|
+
# Extract task number from the beginning of the line (handling ANSI codes)
|
208
|
+
extracted_number = self._extract_task_number(line)
|
209
|
+
if extracted_number == task_number:
|
210
|
+
current_task = line
|
211
|
+
break
|
212
|
+
|
213
|
+
if not current_task:
|
214
|
+
raise TodoShellError(f"Task number {task_number} not found")
|
215
|
+
|
216
|
+
# Parse the current task components
|
217
|
+
components = self._parse_task_components(current_task)
|
218
|
+
|
219
|
+
# Update the context (empty string removes it)
|
220
|
+
if context.strip():
|
221
|
+
# Remove any existing @ symbols to prevent duplication
|
222
|
+
clean_context = context.strip().lstrip("@")
|
223
|
+
if not clean_context:
|
224
|
+
raise TodoShellError(
|
225
|
+
"Context name cannot be empty after removing @ symbol."
|
226
|
+
)
|
227
|
+
context_tag = f"@{clean_context}"
|
228
|
+
# Only add if not already present (deduplication)
|
229
|
+
if context_tag not in components["contexts"]:
|
230
|
+
components["contexts"] = [context_tag]
|
231
|
+
else:
|
232
|
+
# Context already exists, no change needed
|
233
|
+
return self._reconstruct_task(components)
|
234
|
+
else:
|
235
|
+
components["contexts"] = []
|
236
|
+
|
237
|
+
# Reconstruct the task
|
238
|
+
new_description = self._reconstruct_task(components)
|
239
|
+
|
240
|
+
# Replace the task with the new description
|
241
|
+
return self.replace(task_number, new_description)
|
242
|
+
|
243
|
+
def _extract_task_number(self, line: str) -> Optional[int]:
|
244
|
+
"""
|
245
|
+
Extract task number from a line that may contain ANSI color codes.
|
246
|
+
|
247
|
+
Args:
|
248
|
+
line: Task line that may contain ANSI color codes
|
249
|
+
|
250
|
+
Returns:
|
251
|
+
Task number if found, None otherwise
|
252
|
+
"""
|
253
|
+
from rich.text import Text
|
254
|
+
|
255
|
+
# Use rich to properly handle ANSI color codes
|
256
|
+
text = Text.from_ansi(line)
|
257
|
+
clean_line = text.plain
|
258
|
+
|
259
|
+
# Split on first space and check if first part is a number
|
260
|
+
parts = clean_line.split(" ", 1)
|
261
|
+
if parts and parts[0].isdigit():
|
262
|
+
return int(parts[0])
|
263
|
+
return None
|
264
|
+
|
265
|
+
def set_project(self, task_number: int, projects: list) -> str:
|
266
|
+
"""
|
267
|
+
Set or update projects for a task by intelligently rewriting it.
|
268
|
+
|
269
|
+
Args:
|
270
|
+
task_number: The task number to modify
|
271
|
+
projects: List of project operations. Each item can be:
|
272
|
+
- "project" (add project)
|
273
|
+
- "-project" (remove project)
|
274
|
+
- Empty string is a NOOP
|
275
|
+
|
276
|
+
Returns:
|
277
|
+
The updated task description
|
278
|
+
"""
|
279
|
+
# First, get the current task to parse its components
|
280
|
+
tasks_output = self.list_tasks()
|
281
|
+
task_lines = tasks_output.strip().split("\n")
|
282
|
+
|
283
|
+
# Find the task by its actual number (not array index)
|
284
|
+
current_task = None
|
285
|
+
for line in task_lines:
|
286
|
+
if line.strip():
|
287
|
+
# Extract task number from the beginning of the line (handling ANSI codes)
|
288
|
+
extracted_number = self._extract_task_number(line)
|
289
|
+
if extracted_number == task_number:
|
290
|
+
current_task = line
|
291
|
+
break
|
292
|
+
|
293
|
+
if not current_task:
|
294
|
+
raise TodoShellError(f"Task number {task_number} not found")
|
295
|
+
|
296
|
+
# Parse the current task components
|
297
|
+
components = self._parse_task_components(current_task)
|
298
|
+
|
299
|
+
# Store original projects to check if any changes were made
|
300
|
+
original_projects = components["projects"].copy()
|
301
|
+
|
302
|
+
# Handle project operations
|
303
|
+
if not projects:
|
304
|
+
# Empty list is a NOOP - return original task unchanged
|
305
|
+
return self._reconstruct_task(components)
|
306
|
+
else:
|
307
|
+
# Process each project operation
|
308
|
+
for project in projects:
|
309
|
+
if not project.strip():
|
310
|
+
# Empty string is a NOOP - skip this operation
|
311
|
+
continue
|
312
|
+
elif project.startswith("-"):
|
313
|
+
# Remove project
|
314
|
+
clean_project = project[1:].strip().lstrip("+")
|
315
|
+
if not clean_project:
|
316
|
+
raise TodoShellError(
|
317
|
+
"Project name cannot be empty after removing - and + symbols."
|
318
|
+
)
|
319
|
+
# Remove the project if it exists (with or without + prefix)
|
320
|
+
project_to_remove = f"+{clean_project}"
|
321
|
+
components["projects"] = [
|
322
|
+
p for p in components["projects"]
|
323
|
+
if p != project_to_remove and p != clean_project
|
324
|
+
]
|
325
|
+
else:
|
326
|
+
# Add project
|
327
|
+
clean_project = project.strip().lstrip("+")
|
328
|
+
if not clean_project:
|
329
|
+
raise TodoShellError(
|
330
|
+
"Project name cannot be empty after removing + symbol."
|
331
|
+
)
|
332
|
+
project_tag = f"+{clean_project}"
|
333
|
+
# Only add if not already present (deduplication)
|
334
|
+
if project_tag not in components["projects"]:
|
335
|
+
components["projects"].append(project_tag)
|
336
|
+
|
337
|
+
# Check if any changes were actually made
|
338
|
+
if components["projects"] == original_projects:
|
339
|
+
# No changes made - return original task unchanged
|
340
|
+
return self._reconstruct_task(components)
|
341
|
+
|
342
|
+
# Reconstruct the task
|
343
|
+
new_description = self._reconstruct_task(components)
|
344
|
+
|
345
|
+
# Replace the task with the new description
|
346
|
+
return self.replace(task_number, new_description)
|
347
|
+
|
348
|
+
def _parse_task_components(self, task_line: str) -> dict:
|
349
|
+
"""
|
350
|
+
Parse a todo.txt task line into its components.
|
351
|
+
|
352
|
+
Args:
|
353
|
+
task_line: Raw task line from todo.txt
|
354
|
+
|
355
|
+
Returns:
|
356
|
+
Dictionary with parsed components
|
357
|
+
"""
|
358
|
+
# Remove ANSI color codes first using rich
|
359
|
+
from rich.text import Text
|
360
|
+
text = Text.from_ansi(task_line)
|
361
|
+
task_line = text.plain
|
362
|
+
|
363
|
+
# Remove task number prefix if present (e.g., "1 " or "1. ")
|
364
|
+
# First try the format without dot (standard todo.sh format)
|
365
|
+
if " " in task_line and task_line.split(" ")[0].isdigit():
|
366
|
+
task_line = task_line.split(" ", 1)[1]
|
367
|
+
# Fallback to dot format if present
|
368
|
+
elif ". " in task_line:
|
369
|
+
task_line = task_line.split(". ", 1)[1]
|
370
|
+
|
371
|
+
components = {
|
372
|
+
"priority": None,
|
373
|
+
"description": "",
|
374
|
+
"projects": [],
|
375
|
+
"contexts": [],
|
376
|
+
"due": None,
|
377
|
+
"recurring": None,
|
378
|
+
"other_tags": [],
|
379
|
+
}
|
380
|
+
|
381
|
+
# Use sets to automatically deduplicate projects and contexts
|
382
|
+
projects_set = set()
|
383
|
+
contexts_set = set()
|
384
|
+
other_tags_set = set()
|
385
|
+
|
386
|
+
# Split by spaces to process each word
|
387
|
+
words = task_line.split()
|
388
|
+
|
389
|
+
for word in words:
|
390
|
+
# Priority: (A), (B), etc.
|
391
|
+
if word.startswith("(") and word.endswith(")") and len(word) == 3:
|
392
|
+
priority = word[1]
|
393
|
+
if priority.isalpha() and priority.isupper():
|
394
|
+
components["priority"] = priority
|
395
|
+
continue
|
396
|
+
|
397
|
+
# Projects: +project
|
398
|
+
if word.startswith("+"):
|
399
|
+
projects_set.add(word)
|
400
|
+
continue
|
401
|
+
|
402
|
+
# Contexts: @context
|
403
|
+
if word.startswith("@"):
|
404
|
+
contexts_set.add(word)
|
405
|
+
continue
|
406
|
+
|
407
|
+
# Due date: due:YYYY-MM-DD
|
408
|
+
if word.startswith("due:"):
|
409
|
+
components["due"] = word[4:] # Remove 'due:' prefix
|
410
|
+
continue
|
411
|
+
|
412
|
+
# Recurring: rec:frequency[:interval]
|
413
|
+
if word.startswith("rec:"):
|
414
|
+
components["recurring"] = word
|
415
|
+
continue
|
416
|
+
|
417
|
+
# Other tags (like custom tags)
|
418
|
+
if (
|
419
|
+
":" in word
|
420
|
+
and not word.startswith("due:")
|
421
|
+
and not word.startswith("rec:")
|
422
|
+
):
|
423
|
+
other_tags_set.add(word)
|
424
|
+
continue
|
425
|
+
|
426
|
+
# Regular description text
|
427
|
+
if components["description"]:
|
428
|
+
components["description"] += " " + word
|
429
|
+
else:
|
430
|
+
components["description"] = word
|
431
|
+
|
432
|
+
# Convert sets back to sorted lists for consistent ordering
|
433
|
+
components["projects"] = sorted(list(projects_set))
|
434
|
+
components["contexts"] = sorted(list(contexts_set))
|
435
|
+
components["other_tags"] = sorted(list(other_tags_set))
|
436
|
+
|
437
|
+
return components
|
438
|
+
|
439
|
+
def _reconstruct_task(self, components: dict) -> str:
|
440
|
+
"""
|
441
|
+
Reconstruct a task description from parsed components.
|
442
|
+
|
443
|
+
Args:
|
444
|
+
components: Dictionary with task components
|
445
|
+
|
446
|
+
Returns:
|
447
|
+
Reconstructed task description
|
448
|
+
"""
|
449
|
+
parts = []
|
450
|
+
|
451
|
+
# Add priority if present
|
452
|
+
if components["priority"]:
|
453
|
+
parts.append(f"({components['priority']})")
|
454
|
+
|
455
|
+
# Add description
|
456
|
+
if components["description"]:
|
457
|
+
parts.append(components["description"])
|
458
|
+
|
459
|
+
# Add projects
|
460
|
+
parts.extend(components["projects"])
|
461
|
+
|
462
|
+
# Add contexts
|
463
|
+
parts.extend(components["contexts"])
|
464
|
+
|
465
|
+
# Add due date
|
466
|
+
if components["due"]:
|
467
|
+
parts.append(f"due:{components['due']}")
|
468
|
+
|
469
|
+
# Add recurring pattern
|
470
|
+
if components["recurring"]:
|
471
|
+
parts.append(components["recurring"])
|
472
|
+
|
473
|
+
# Add other tags
|
474
|
+
parts.extend(components["other_tags"])
|
475
|
+
|
476
|
+
return " ".join(parts)
|
todo_agent/interface/cli.py
CHANGED
@@ -166,7 +166,7 @@ class CLI:
|
|
166
166
|
while True:
|
167
167
|
try:
|
168
168
|
# Print prompt character on separate line to prevent deletion
|
169
|
-
self.console.print("\n[bold cyan]▶[/bold cyan]", end="
|
169
|
+
self.console.print("\n[bold cyan]▶[/bold cyan]", end=" ")
|
170
170
|
user_input = self.console.input().strip()
|
171
171
|
|
172
172
|
if user_input.lower() in ["quit", "exit", "q"]:
|
@@ -244,8 +244,10 @@ class CLI:
|
|
244
244
|
# Format the response and create a panel
|
245
245
|
formatted_response = ResponseFormatter.format_response(response)
|
246
246
|
|
247
|
-
# Get memory usage
|
248
|
-
|
247
|
+
# Get memory usage
|
248
|
+
# DISABLED FOR NOW
|
249
|
+
# memory_usage = self._get_memory_usage()
|
250
|
+
memory_usage = None
|
249
251
|
|
250
252
|
# Create response panel with memory usage
|
251
253
|
response_panel = PanelFormatter.create_response_panel(
|
todo_agent/interface/tools.py
CHANGED
@@ -10,7 +10,7 @@ Discovery Tools (Call FIRST):
|
|
10
10
|
- list_completed_tasks(filter?, project?, context?, text_search?, date_from?, date_to?) - List completed tasks with optional filtering
|
11
11
|
|
12
12
|
Task Management Tools:
|
13
|
-
- add_task(description, priority?, project?, context?, due?) - Add new task to todo.txt
|
13
|
+
- add_task(description, priority?, project?, context?, due?, recurring?) - Add new task to todo.txt
|
14
14
|
- complete_task(task_number) - Mark task as complete by line number
|
15
15
|
- replace_task(task_number, new_description) - Replace entire task content
|
16
16
|
- append_to_task(task_number, text) - Add text to end of existing task
|
@@ -20,12 +20,16 @@ Task Management Tools:
|
|
20
20
|
Priority Management Tools:
|
21
21
|
- set_priority(task_number, priority) - Set or change task priority (A-Z)
|
22
22
|
- remove_priority(task_number) - Remove priority from task
|
23
|
+
- set_due_date(task_number, due_date) - Set or update due date for a task by intelligently rewriting it (use empty string to remove due date)
|
24
|
+
- set_context(task_number, context) - Set or update context for a task by intelligently rewriting it (use empty string to remove context)
|
25
|
+
- set_project(task_number, projects) - Set or update projects for a task by intelligently rewriting it (handles array of projects with add/remove operations)
|
23
26
|
|
24
27
|
Utility Tools:
|
25
28
|
- get_overview() - Show task statistics and summary
|
26
29
|
- move_task(task_number, destination, source?) - Move task between files
|
27
30
|
- archive_tasks() - Archive completed tasks from todo.txt to done.txt
|
28
31
|
- get_calendar(month, year) - Get calendar for specific month and year
|
32
|
+
- parse_date(date_expression) - Convert natural language date expressions to YYYY-MM-DD format
|
29
33
|
"""
|
30
34
|
|
31
35
|
import subprocess
|
@@ -296,7 +300,11 @@ class ToolCallHandler:
|
|
296
300
|
"name": "append_to_task",
|
297
301
|
"description": (
|
298
302
|
"Add text to the end of an existing task. Use this when user wants "
|
299
|
-
"to add additional information to a task without replacing it entirely."
|
303
|
+
"to add additional information to a task without replacing it entirely. "
|
304
|
+
"CRITICAL: DO NOT use this for adding project tags (+project) or context tags (@context) - "
|
305
|
+
"use set_project() or set_context() instead. "
|
306
|
+
"DO NOT use this for adding due dates - use set_due_date() instead. "
|
307
|
+
"This tool is for adding descriptive text, notes, or comments to tasks."
|
300
308
|
),
|
301
309
|
"parameters": {
|
302
310
|
"type": "object",
|
@@ -411,6 +419,97 @@ class ToolCallHandler:
|
|
411
419
|
},
|
412
420
|
},
|
413
421
|
},
|
422
|
+
{
|
423
|
+
"type": "function",
|
424
|
+
"function": {
|
425
|
+
"name": "set_due_date",
|
426
|
+
"description": (
|
427
|
+
"Set or update the due date for a task by intelligently rewriting it. "
|
428
|
+
"This preserves all existing task components (priority, projects, contexts, etc.) "
|
429
|
+
"while updating or adding the due date. Use empty string to remove the due date. "
|
430
|
+
"IMPORTANT: Use list_tasks() first "
|
431
|
+
"to find the correct task number if user doesn't specify it. "
|
432
|
+
"Use parse_date() tool to convert natural language expressions like 'tomorrow', "
|
433
|
+
"'next week', 'by Friday' to YYYY-MM-DD format."
|
434
|
+
),
|
435
|
+
"parameters": {
|
436
|
+
"type": "object",
|
437
|
+
"properties": {
|
438
|
+
"task_number": {
|
439
|
+
"type": "integer",
|
440
|
+
"description": "The line number of the task to modify (required)",
|
441
|
+
},
|
442
|
+
"due_date": {
|
443
|
+
"type": "string",
|
444
|
+
"description": "Due date in YYYY-MM-DD format, or empty string to remove due date (required). Use parse_date() tool to convert natural language expressions.",
|
445
|
+
},
|
446
|
+
},
|
447
|
+
"required": ["task_number", "due_date"],
|
448
|
+
},
|
449
|
+
},
|
450
|
+
},
|
451
|
+
{
|
452
|
+
"type": "function",
|
453
|
+
"function": {
|
454
|
+
"name": "set_context",
|
455
|
+
"description": (
|
456
|
+
"Set or update the context for a task by intelligently rewriting it. "
|
457
|
+
"This preserves all existing task components (priority, projects, due date, etc.) "
|
458
|
+
"while updating or adding the context. Use empty string to remove the context. "
|
459
|
+
"PREFERRED METHOD: Use this instead of append_to_task() when adding context tags (@context). "
|
460
|
+
"This tool properly manages context organization and prevents formatting issues. "
|
461
|
+
"IMPORTANT: Use list_tasks() first "
|
462
|
+
"to find the correct task number if user doesn't specify it."
|
463
|
+
),
|
464
|
+
"parameters": {
|
465
|
+
"type": "object",
|
466
|
+
"properties": {
|
467
|
+
"task_number": {
|
468
|
+
"type": "integer",
|
469
|
+
"description": "The line number of the task to modify (required)",
|
470
|
+
},
|
471
|
+
"context": {
|
472
|
+
"type": "string",
|
473
|
+
"description": "Context to set for the task (required). Use empty string to remove context.",
|
474
|
+
},
|
475
|
+
},
|
476
|
+
"required": ["task_number", "context"],
|
477
|
+
},
|
478
|
+
},
|
479
|
+
},
|
480
|
+
{
|
481
|
+
"type": "function",
|
482
|
+
"function": {
|
483
|
+
"name": "set_project",
|
484
|
+
"description": (
|
485
|
+
"Set or update projects for a task by intelligently rewriting it. "
|
486
|
+
"This preserves all existing task components and manages projects intelligently. "
|
487
|
+
"Supports multiple projects, prevents duplicates, and groups them together. "
|
488
|
+
"Empty array or empty strings are NOOPs (no changes). "
|
489
|
+
"Use '-project' syntax to remove specific projects. "
|
490
|
+
"PREFERRED METHOD: Use this instead of append_to_task() when adding project tags (+project). "
|
491
|
+
"This tool properly manages project organization and prevents formatting issues. "
|
492
|
+
"IMPORTANT: Use list_tasks() first "
|
493
|
+
"to find the correct task number if user doesn't specify it. "
|
494
|
+
"Project names should be provided without the + symbol (e.g., 'work' not '+work')."
|
495
|
+
),
|
496
|
+
"parameters": {
|
497
|
+
"type": "object",
|
498
|
+
"properties": {
|
499
|
+
"task_number": {
|
500
|
+
"type": "integer",
|
501
|
+
"description": "The line number of the task to modify (required)",
|
502
|
+
},
|
503
|
+
"projects": {
|
504
|
+
"type": "array",
|
505
|
+
"items": {"type": "string"},
|
506
|
+
"description": "Array of project operations. Each item can be: project name (add), '-project' (remove), or empty string (remove all) (required).",
|
507
|
+
},
|
508
|
+
},
|
509
|
+
"required": ["task_number", "projects"],
|
510
|
+
},
|
511
|
+
},
|
512
|
+
},
|
414
513
|
{
|
415
514
|
"type": "function",
|
416
515
|
"function": {
|
@@ -724,10 +823,12 @@ class ToolCallHandler:
|
|
724
823
|
"delete_task": self.todo_manager.delete_task,
|
725
824
|
"set_priority": self.todo_manager.set_priority,
|
726
825
|
"remove_priority": self.todo_manager.remove_priority,
|
826
|
+
"set_due_date": self.todo_manager.set_due_date,
|
827
|
+
"set_context": self.todo_manager.set_context,
|
828
|
+
"set_project": self.todo_manager.set_project,
|
727
829
|
"get_overview": self.todo_manager.get_overview,
|
728
830
|
"move_task": self.todo_manager.move_task,
|
729
831
|
"archive_tasks": self.todo_manager.archive_tasks,
|
730
|
-
"deduplicate_tasks": self.todo_manager.deduplicate_tasks,
|
731
832
|
"parse_date": self._parse_date,
|
732
833
|
"get_calendar": self._get_calendar,
|
733
834
|
}
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: todo-agent
|
3
|
-
Version: 0.2.
|
3
|
+
Version: 0.2.9
|
4
4
|
Summary: A natural language interface for todo.sh task management
|
5
5
|
Author: codeprimate
|
6
6
|
Maintainer: codeprimate
|
@@ -51,7 +51,7 @@ A natural language interface for [todo.sh](https://github.com/todotxt/todo.txt-c
|
|
51
51
|
|
52
52
|
## What it does
|
53
53
|
|
54
|
-
Transform natural language into todo.sh commands:
|
54
|
+
Transform natural language into todo.sh commands with intelligent task management:
|
55
55
|
|
56
56
|
```bash
|
57
57
|
# Use interactively
|
@@ -62,91 +62,43 @@ todo-agent "add buy groceries to shopping list"
|
|
62
62
|
todo-agent "show my work tasks"
|
63
63
|
```
|
64
64
|
|
65
|
-
##
|
66
|
-
|
67
|
-
### 1. Install
|
65
|
+
## Why Todo Agent?
|
68
66
|
|
69
|
-
|
67
|
+
**Speak naturally** instead of memorizing commands. Todo Agent understands "add dentist appointment next Monday" and automatically sets the right date, project, and context.
|
70
68
|
|
71
|
-
**
|
69
|
+
**Get intelligent insights** beyond basic task lists. It organizes tasks strategically, suggests priorities, and recommends optimal timing based on your patterns.
|
72
70
|
|
73
|
-
|
71
|
+
**Work smarter** with automatic duplicate detection, recurring task handling, and calendar-aware scheduling.
|
74
72
|
|
75
|
-
**
|
76
|
-
```bash
|
77
|
-
# Using Homebrew
|
78
|
-
brew install todo-txt
|
79
|
-
# Or using MacPorts
|
80
|
-
sudo port install todo-txt
|
81
|
-
```
|
73
|
+
**Choose your privacy** - use cloud AI (OpenRouter) or run locally (Ollama).
|
82
74
|
|
83
|
-
|
84
|
-
```bash
|
85
|
-
# Ubuntu/Debian
|
86
|
-
sudo apt-get install todo-txt-cli
|
87
|
-
# CentOS/RHEL/Fedora
|
88
|
-
sudo yum install todo-txt-cli
|
89
|
-
# or
|
90
|
-
sudo dnf install todo-txt-cli
|
91
|
-
# Arch Linux
|
92
|
-
sudo pacman -S todo-txt-cli
|
93
|
-
```
|
75
|
+
## Quick Start
|
94
76
|
|
95
|
-
|
96
|
-
```bash
|
97
|
-
# Using Chocolatey
|
98
|
-
choco install todo-txt-cli
|
99
|
-
# Using Scoop
|
100
|
-
scoop install todo-txt-cli
|
101
|
-
```
|
77
|
+
### 1. Install
|
102
78
|
|
103
|
-
|
104
|
-
```bash
|
105
|
-
git clone https://github.com/todotxt/todo.txt-cli.git
|
106
|
-
cd todo.txt-cli
|
107
|
-
make
|
108
|
-
sudo make install
|
109
|
-
```
|
79
|
+
#### Install todo.sh (required)
|
110
80
|
|
111
|
-
|
81
|
+
**macOS:** `brew install todo-txt`
|
82
|
+
**Linux:** `sudo apt-get install todo-txt-cli` (Ubuntu/Debian) or `sudo pacman -S todo-txt-cli` (Arch)
|
83
|
+
**Windows:** `choco install todo-txt-cli` (Chocolatey) or `scoop install todo-txt-cli` (Scoop)
|
112
84
|
|
113
|
-
|
85
|
+
#### Set up todo.sh
|
114
86
|
|
115
87
|
```bash
|
116
|
-
# Create
|
117
|
-
mkdir ~/todo
|
118
|
-
cd ~/todo
|
119
|
-
|
120
|
-
# Initialize todo.sh (this creates the initial todo.txt file)
|
88
|
+
# Create and initialize your todo directory
|
89
|
+
mkdir ~/todo && cd ~/todo
|
121
90
|
todo.sh init
|
122
91
|
|
123
|
-
#
|
124
|
-
todo.sh version
|
125
|
-
```
|
126
|
-
|
127
|
-
**Important:** Set the `TODO_DIR` environment variable to point to your todo.txt repository:
|
128
|
-
|
129
|
-
```bash
|
92
|
+
# Add to your shell profile (.bashrc, .zshrc, etc.)
|
130
93
|
export TODO_DIR="$HOME/todo"
|
131
94
|
```
|
132
95
|
|
133
|
-
You can add this to your shell profile (`.bashrc`, `.zshrc`, etc.) to make it permanent.
|
134
|
-
|
135
96
|
#### Install todo-agent
|
136
97
|
|
137
98
|
```bash
|
138
|
-
# Clone and install from source
|
139
99
|
git clone https://github.com/codeprimate/todo-agent.git
|
140
100
|
cd todo_agent
|
141
|
-
|
142
|
-
# Option 1: Install built package locally
|
143
101
|
make install
|
144
|
-
|
145
|
-
# Option 2: Install in development mode with dev dependencies
|
146
|
-
make install-dev
|
147
|
-
|
148
|
-
# Option 3: Install in development mode (basic)
|
149
|
-
pip install -e .
|
150
102
|
```
|
151
103
|
|
152
104
|
### 2. Set up your LLM provider
|
@@ -200,6 +152,22 @@ todo-agent "show completed tasks"
|
|
200
152
|
todo-agent "list my contexts"
|
201
153
|
```
|
202
154
|
|
155
|
+
### Strategic Planning
|
156
|
+
```bash
|
157
|
+
todo-agent "what should I do next?"
|
158
|
+
todo-agent "organize my tasks by priority"
|
159
|
+
todo-agent "show me everything due this week"
|
160
|
+
todo-agent "what tasks are blocking other work?"
|
161
|
+
```
|
162
|
+
|
163
|
+
### Natural Language Intelligence
|
164
|
+
```bash
|
165
|
+
todo-agent "add dentist appointment next Monday"
|
166
|
+
todo-agent "set up recurring daily vitamin reminder"
|
167
|
+
todo-agent "move all completed tasks to archive"
|
168
|
+
todo-agent "show me tasks I can do from home"
|
169
|
+
```
|
170
|
+
|
203
171
|
## Configuration
|
204
172
|
|
205
173
|
|
@@ -1,10 +1,10 @@
|
|
1
1
|
todo_agent/__init__.py,sha256=RUowhd14r3tqB_7rl83unGV8oBjra3UOIl7jix-33fk,254
|
2
|
-
todo_agent/_version.py,sha256=
|
2
|
+
todo_agent/_version.py,sha256=051on7ZmwGNyKvbO1AXKoElw7RjLuRmeJqVOApytNd4,704
|
3
3
|
todo_agent/main.py,sha256=-ryhMm4c4sz4e4anXI8B-CYnpEh5HIkmnYcnGxcWHDk,1628
|
4
4
|
todo_agent/core/__init__.py,sha256=QAZ4it63pXv5-DxtNcuSAmg7ZnCY5ackI5yycvKHr9I,365
|
5
5
|
todo_agent/core/conversation_manager.py,sha256=nRFcDMqZtumVipggzVe94Hwz9HDbc2CrTssk_HImwEI,13548
|
6
6
|
todo_agent/core/exceptions.py,sha256=cPvvkIbKdI7l51wC7cE-ZxUi54P3nf2m7x2lMNMRFYM,399
|
7
|
-
todo_agent/core/todo_manager.py,sha256=
|
7
|
+
todo_agent/core/todo_manager.py,sha256=wT82Wpwby6LR9VacLXhdDiGiMNoQbovXmU9943e5X2M,14908
|
8
8
|
todo_agent/infrastructure/__init__.py,sha256=SGbHXgzq6U1DMgOfWPMsWEK99zjPSF-6gzy7xqc5fsI,284
|
9
9
|
todo_agent/infrastructure/calendar_utils.py,sha256=HmF0ykXF_6GbdoJvZLIv6fKwT6ipixoywdTMkIXmkGU,1871
|
10
10
|
todo_agent/infrastructure/config.py,sha256=zyp6qOlg1nN_awphivlgGNBE6fL0Hf66YgvWxR8ldyQ,2117
|
@@ -13,17 +13,17 @@ todo_agent/infrastructure/llm_client.py,sha256=ZoObyqaRP6i_eqGYGfJWGeWTJ-VNxpY70
|
|
13
13
|
todo_agent/infrastructure/llm_client_factory.py,sha256=-tktnVOIF7B45WR7AuLoi7MKnEyuM8lgg1jjc4T1FhM,1929
|
14
14
|
todo_agent/infrastructure/logger.py,sha256=2ykG_0lyzmEGxDF6ZRl1qiTUGDuFeQgzv4Na6vRmXcM,4110
|
15
15
|
todo_agent/infrastructure/ollama_client.py,sha256=RVgHqX37k7q5xEaEXdhcCv2HYQiszs1QV39RxFFMIas,5902
|
16
|
-
todo_agent/infrastructure/openrouter_client.py,sha256=
|
17
|
-
todo_agent/infrastructure/todo_shell.py,sha256=
|
16
|
+
todo_agent/infrastructure/openrouter_client.py,sha256=FV240sQxoJ8j_Umh0qc6IorHkwk3LAFTSekGHKxuFwk,7000
|
17
|
+
todo_agent/infrastructure/todo_shell.py,sha256=kYS0UwDZU1OpUVpWMkVsnsxnHINoWliRqzDRD8VTXJQ,17375
|
18
18
|
todo_agent/infrastructure/token_counter.py,sha256=PCKheOVJbp1s89yhh_i6iKgURMt9mVoYkwjQJCc2xCE,4958
|
19
|
-
todo_agent/infrastructure/prompts/system_prompt.txt,sha256=
|
19
|
+
todo_agent/infrastructure/prompts/system_prompt.txt,sha256=HTv7Yl2oQi-AbQgdBzZKNKnmAMdOuOVRlbi75L-w-5g,19962
|
20
20
|
todo_agent/interface/__init__.py,sha256=vDD3rQu4qDkpvVwGVtkDzE1M4IiSHYzTif4GbYSFWaI,457
|
21
|
-
todo_agent/interface/cli.py,sha256=
|
21
|
+
todo_agent/interface/cli.py,sha256=9DUKTrxTwKx5Go0cA8nNCKDuM6QDmAG8jzK4_v_1WS8,12096
|
22
22
|
todo_agent/interface/formatters.py,sha256=DiQLemndiuFWjLcBRPpu1wVnJcoYAFP8t_fJstOgaDs,18918
|
23
|
-
todo_agent/interface/tools.py,sha256=
|
24
|
-
todo_agent-0.2.
|
25
|
-
todo_agent-0.2.
|
26
|
-
todo_agent-0.2.
|
27
|
-
todo_agent-0.2.
|
28
|
-
todo_agent-0.2.
|
29
|
-
todo_agent-0.2.
|
23
|
+
todo_agent/interface/tools.py,sha256=OE9qOwWo7itqmNc85tZSCqWHCvValMa4fQE-EygnNNQ,45929
|
24
|
+
todo_agent-0.2.9.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
25
|
+
todo_agent-0.2.9.dist-info/METADATA,sha256=OPm-ho33qlhnZqrnNlqRR2p4tLqlXzkNk-fgcY73Dg0,10134
|
26
|
+
todo_agent-0.2.9.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
27
|
+
todo_agent-0.2.9.dist-info/entry_points.txt,sha256=4W7LrCib6AXP5IZDwWRht8S5gutLu5oNfTJHGbt4oHs,52
|
28
|
+
todo_agent-0.2.9.dist-info/top_level.txt,sha256=a65mlPIhPZHuq2bRIi_sCMAIJsUddvXt171OBF6r6co,11
|
29
|
+
todo_agent-0.2.9.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|