tunacode-cli 0.0.39__py3-none-any.whl → 0.0.41__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.

Potentially problematic release.


This version of tunacode-cli might be problematic. Click here for more details.

Files changed (33) hide show
  1. tunacode/cli/commands/__init__.py +2 -0
  2. tunacode/cli/commands/implementations/__init__.py +3 -0
  3. tunacode/cli/commands/implementations/debug.py +1 -1
  4. tunacode/cli/commands/implementations/todo.py +217 -0
  5. tunacode/cli/commands/registry.py +2 -0
  6. tunacode/cli/main.py +12 -5
  7. tunacode/cli/repl.py +197 -132
  8. tunacode/configuration/defaults.py +1 -0
  9. tunacode/configuration/models.py +6 -0
  10. tunacode/constants.py +32 -3
  11. tunacode/context.py +7 -3
  12. tunacode/core/agents/main.py +52 -9
  13. tunacode/core/setup/config_setup.py +5 -0
  14. tunacode/core/state.py +50 -1
  15. tunacode/core/token_usage/api_response_parser.py +44 -0
  16. tunacode/core/token_usage/cost_calculator.py +58 -0
  17. tunacode/core/token_usage/usage_tracker.py +98 -0
  18. tunacode/prompts/system.md +69 -5
  19. tunacode/tools/todo.py +343 -0
  20. tunacode/types.py +20 -1
  21. tunacode/ui/input.py +1 -1
  22. tunacode/ui/output.py +36 -0
  23. tunacode/utils/message_utils.py +17 -0
  24. tunacode/utils/text_utils.py +131 -25
  25. tunacode/utils/token_counter.py +78 -8
  26. {tunacode_cli-0.0.39.dist-info → tunacode_cli-0.0.41.dist-info}/METADATA +3 -1
  27. {tunacode_cli-0.0.39.dist-info → tunacode_cli-0.0.41.dist-info}/RECORD +31 -27
  28. tunacode/cli/textual_app.py +0 -420
  29. tunacode/cli/textual_bridge.py +0 -161
  30. {tunacode_cli-0.0.39.dist-info → tunacode_cli-0.0.41.dist-info}/WHEEL +0 -0
  31. {tunacode_cli-0.0.39.dist-info → tunacode_cli-0.0.41.dist-info}/entry_points.txt +0 -0
  32. {tunacode_cli-0.0.39.dist-info → tunacode_cli-0.0.41.dist-info}/licenses/LICENSE +0 -0
  33. {tunacode_cli-0.0.39.dist-info → tunacode_cli-0.0.41.dist-info}/top_level.txt +0 -0
@@ -26,6 +26,7 @@ from .implementations import (
26
26
  ParseToolsCommand,
27
27
  RefreshConfigCommand,
28
28
  ThoughtsCommand,
29
+ TodoCommand,
29
30
  UpdateCommand,
30
31
  YoloCommand,
31
32
  )
@@ -59,4 +60,5 @@ __all__ = [
59
60
  "UpdateCommand",
60
61
  "ModelCommand",
61
62
  "InitCommand",
63
+ "TodoCommand",
62
64
  ]
@@ -13,6 +13,7 @@ from .debug import (
13
13
  from .development import BranchCommand, InitCommand
14
14
  from .model import ModelCommand
15
15
  from .system import ClearCommand, HelpCommand, RefreshConfigCommand, StreamingCommand, UpdateCommand
16
+ from .todo import TodoCommand
16
17
 
17
18
  __all__ = [
18
19
  # System commands
@@ -35,4 +36,6 @@ __all__ = [
35
36
  "ModelCommand",
36
37
  # Conversation commands
37
38
  "CompactCommand",
39
+ # Todo commands
40
+ "TodoCommand",
38
41
  ]
@@ -170,7 +170,7 @@ class ParseToolsCommand(SimpleCommand):
170
170
  from tunacode.cli.repl import _tool_handler
171
171
 
172
172
  def tool_callback_with_state(part, node):
173
- return _tool_handler(part, node, context.state_manager)
173
+ return _tool_handler(part, context.state_manager)
174
174
 
175
175
  try:
176
176
  await extract_and_execute_tool_calls(
@@ -0,0 +1,217 @@
1
+ """Todo command implementation."""
2
+
3
+ from datetime import datetime
4
+
5
+ from rich.box import ROUNDED
6
+ from rich.table import Table
7
+
8
+ from tunacode.types import CommandArgs, CommandContext, CommandResult, TodoItem
9
+ from tunacode.ui import console as ui
10
+
11
+ from ..base import CommandCategory, CommandSpec, SimpleCommand
12
+
13
+
14
+ class TodoCommand(SimpleCommand):
15
+ """Manage todo items."""
16
+
17
+ spec = CommandSpec(
18
+ name="todo",
19
+ aliases=["/todo", "todos"],
20
+ description="Manage todo items.",
21
+ category=CommandCategory.DEVELOPMENT,
22
+ )
23
+
24
+ async def execute(self, args: CommandArgs, context: CommandContext) -> CommandResult:
25
+ if not args:
26
+ await self.list_todos(context)
27
+ return
28
+
29
+ subcommand = args[0].lower()
30
+ subcommand_args = args[1:]
31
+
32
+ if subcommand == "list":
33
+ await self.list_todos(context)
34
+ elif subcommand == "add":
35
+ await self.add_todo(subcommand_args, context)
36
+ elif subcommand == "done":
37
+ await self.mark_done(subcommand_args, context)
38
+ elif subcommand == "update":
39
+ await self.update_todo(subcommand_args, context)
40
+ elif subcommand == "priority":
41
+ await self.set_priority(subcommand_args, context)
42
+ elif subcommand == "remove":
43
+ await self.remove_todo(subcommand_args, context)
44
+ elif subcommand == "clear":
45
+ await self.clear_todos(context)
46
+ else:
47
+ await ui.error(
48
+ "Invalid todo subcommand. Available subcommands: list, add, done, update, priority, remove, clear"
49
+ )
50
+
51
+ async def list_todos(self, context: CommandContext) -> None:
52
+ """Display the todo list with Rich formatting."""
53
+ todos = context.state_manager.session.todos
54
+ if not todos:
55
+ await ui.info("No todos found.")
56
+ return
57
+
58
+ # Create Rich table
59
+ table = Table(show_header=True, box=ROUNDED, padding=(0, 1))
60
+ table.add_column("ID", style="bold cyan", width=4, justify="center")
61
+ table.add_column("Status", width=12, justify="center")
62
+ table.add_column("Task", style="white", min_width=20)
63
+ table.add_column("Priority", width=10, justify="center")
64
+ table.add_column("Created", style="dim", width=12)
65
+
66
+ # Sort todos by status and priority
67
+ pending = [t for t in todos if t.status == "pending"]
68
+ in_progress = [t for t in todos if t.status == "in_progress"]
69
+ completed = [t for t in todos if t.status == "completed"]
70
+
71
+ # Sort each group by priority (high->medium->low)
72
+ priority_order = {"high": 0, "medium": 1, "low": 2}
73
+
74
+ for group in [in_progress, pending, completed]:
75
+ group.sort(key=lambda x: priority_order.get(x.priority, 3))
76
+
77
+ # Add rows to table
78
+ for todo in in_progress + pending + completed:
79
+ # Status with emoji
80
+ if todo.status == "pending":
81
+ status_display = "○ pending"
82
+ elif todo.status == "in_progress":
83
+ status_display = "○ in progress"
84
+ else:
85
+ status_display = "✓ completed"
86
+
87
+ # Priority with color coding
88
+ if todo.priority == "high":
89
+ priority_display = "[red] high[/red]"
90
+ elif todo.priority == "medium":
91
+ priority_display = "[yellow] medium[/yellow]"
92
+ else:
93
+ priority_display = "[green] low[/green]"
94
+
95
+ # Format created date
96
+ created_display = todo.created_at.strftime("%m/%d %H:%M")
97
+
98
+ table.add_row(todo.id, status_display, todo.content, priority_display, created_display)
99
+
100
+ await ui.print(table)
101
+
102
+ async def add_todo(self, args: CommandArgs, context: CommandContext) -> None:
103
+ """Add a new todo and show updated list."""
104
+ if not args:
105
+ await ui.error("Please provide a task to add.")
106
+ return
107
+
108
+ content = " ".join(args)
109
+ new_id = f"{int(datetime.now().timestamp() * 1000000)}"
110
+ new_todo = TodoItem(
111
+ id=new_id,
112
+ content=content,
113
+ status="pending",
114
+ priority="medium",
115
+ created_at=datetime.now(),
116
+ )
117
+ context.state_manager.add_todo(new_todo)
118
+
119
+ await ui.success(f"Todo created: {content}")
120
+ await self.list_todos(context)
121
+
122
+ async def mark_done(self, args: CommandArgs, context: CommandContext) -> None:
123
+ """Mark a todo as done and show updated list."""
124
+ if not args:
125
+ await ui.error("Please provide a todo ID to mark as done.")
126
+ return
127
+
128
+ todo_id = args[0]
129
+ # Find the todo to get its content for feedback
130
+ todo_content = None
131
+ for todo in context.state_manager.session.todos:
132
+ if todo.id == todo_id:
133
+ todo_content = todo.content
134
+ break
135
+
136
+ if not todo_content:
137
+ await ui.error(f"Todo with id {todo_id} not found.")
138
+ return
139
+
140
+ context.state_manager.update_todo(todo_id, "completed")
141
+ await ui.success(f"Marked todo {todo_id} as done: {todo_content}")
142
+ await self.list_todos(context)
143
+
144
+ async def update_todo(self, args: CommandArgs, context: CommandContext) -> None:
145
+ """Update a todo status and show updated list."""
146
+ if len(args) < 2:
147
+ await ui.error("Please provide a todo ID and a new status.")
148
+ return
149
+
150
+ todo_id = args[0]
151
+ new_status = args[1].lower()
152
+ if new_status not in ["pending", "in_progress", "completed"]:
153
+ await ui.error("Invalid status. Must be one of: pending, in_progress, completed")
154
+ return
155
+
156
+ for todo in context.state_manager.session.todos:
157
+ if todo.id == todo_id:
158
+ todo.status = new_status
159
+ await ui.success(f"Updated todo {todo_id} to status {new_status}: {todo.content}")
160
+ await self.list_todos(context)
161
+ return
162
+
163
+ await ui.error(f"Todo with id {todo_id} not found.")
164
+
165
+ async def set_priority(self, args: CommandArgs, context: CommandContext) -> None:
166
+ """Set todo priority and show updated list."""
167
+ if len(args) < 2:
168
+ await ui.error("Please provide a todo ID and a new priority.")
169
+ return
170
+
171
+ todo_id = args[0]
172
+ new_priority = args[1].lower()
173
+ if new_priority not in ["high", "medium", "low"]:
174
+ await ui.error("Invalid priority. Must be one of: high, medium, low")
175
+ return
176
+
177
+ for todo in context.state_manager.session.todos:
178
+ if todo.id == todo_id:
179
+ todo.priority = new_priority
180
+ await ui.success(f"Set todo {todo_id} to priority {new_priority}: {todo.content}")
181
+ await self.list_todos(context)
182
+ return
183
+
184
+ await ui.error(f"Todo with id {todo_id} not found.")
185
+
186
+ async def remove_todo(self, args: CommandArgs, context: CommandContext) -> None:
187
+ """Remove a todo and show updated list."""
188
+ if not args:
189
+ await ui.error("Please provide a todo ID to remove.")
190
+ return
191
+
192
+ todo_id = args[0]
193
+ # Find the todo to get its content for feedback
194
+ todo_content = None
195
+ for todo in context.state_manager.session.todos:
196
+ if todo.id == todo_id:
197
+ todo_content = todo.content
198
+ break
199
+
200
+ if not todo_content:
201
+ await ui.error(f"Todo with id {todo_id} not found.")
202
+ return
203
+
204
+ context.state_manager.remove_todo(todo_id)
205
+ await ui.success(f"Removed todo {todo_id}: {todo_content}")
206
+ await self.list_todos(context)
207
+
208
+ async def clear_todos(self, context: CommandContext) -> None:
209
+ """Clear all todos and show confirmation."""
210
+ todo_count = len(context.state_manager.session.todos)
211
+ if todo_count == 0:
212
+ await ui.info("No todos to clear.")
213
+ return
214
+
215
+ context.state_manager.clear_todos()
216
+ await ui.success(f"Cleared all {todo_count} todos.")
217
+ await self.list_todos(context)
@@ -26,6 +26,7 @@ from .implementations.system import (
26
26
  StreamingCommand,
27
27
  UpdateCommand,
28
28
  )
29
+ from .implementations.todo import TodoCommand
29
30
 
30
31
 
31
32
  @dataclass
@@ -119,6 +120,7 @@ class CommandRegistry:
119
120
  CompactCommand,
120
121
  ModelCommand,
121
122
  InitCommand,
123
+ TodoCommand,
122
124
  ]
123
125
 
124
126
  # Register all discovered commands
tunacode/cli/main.py CHANGED
@@ -17,7 +17,7 @@ from tunacode.ui import console as ui
17
17
  from tunacode.utils.system import check_for_updates
18
18
 
19
19
  app_settings = ApplicationSettings()
20
- app = typer.Typer(help="🐟 TunaCode - Your AI-powered development assistant")
20
+ app = typer.Typer(help="TunaCode - OS AI-powered development assistant")
21
21
  state_manager = StateManager()
22
22
 
23
23
 
@@ -30,8 +30,11 @@ def main(
30
30
  ),
31
31
  model: str = typer.Option(None, "--model", help="Default model to use (e.g., openai/gpt-4)"),
32
32
  key: str = typer.Option(None, "--key", help="API key for the provider"),
33
+ context: int = typer.Option(
34
+ None, "--context", help="Maximum context window size for custom models"
35
+ ),
33
36
  ):
34
- """🚀 Start TunaCode - Your AI-powered development assistant"""
37
+ """Start TunaCode - Your AI-powered development assistant"""
35
38
 
36
39
  async def async_main():
37
40
  if version:
@@ -43,9 +46,13 @@ def main(
43
46
  # Start update check in background
44
47
  update_task = asyncio.create_task(asyncio.to_thread(check_for_updates))
45
48
 
46
- cli_config = {}
47
- if baseurl or model or key:
48
- cli_config = {"baseurl": baseurl, "model": model, "key": key}
49
+ cli_config = {
50
+ "baseurl": baseurl,
51
+ "model": model,
52
+ "key": key,
53
+ "custom_context_window": context,
54
+ }
55
+ cli_config = {k: v for k, v in cli_config.items() if v is not None}
49
56
 
50
57
  try:
51
58
  await setup(run_setup, state_manager, cli_config)