tunacode-cli 0.0.17__py3-none-any.whl → 0.0.19__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 (47) hide show
  1. tunacode/cli/commands.py +73 -41
  2. tunacode/cli/main.py +29 -26
  3. tunacode/cli/repl.py +91 -37
  4. tunacode/cli/textual_app.py +69 -66
  5. tunacode/cli/textual_bridge.py +33 -32
  6. tunacode/configuration/settings.py +2 -9
  7. tunacode/constants.py +2 -4
  8. tunacode/context.py +1 -1
  9. tunacode/core/agents/__init__.py +12 -0
  10. tunacode/core/agents/main.py +89 -63
  11. tunacode/core/agents/orchestrator.py +99 -0
  12. tunacode/core/agents/planner_schema.py +9 -0
  13. tunacode/core/agents/readonly.py +51 -0
  14. tunacode/core/background/__init__.py +0 -0
  15. tunacode/core/background/manager.py +36 -0
  16. tunacode/core/llm/__init__.py +0 -0
  17. tunacode/core/llm/planner.py +63 -0
  18. tunacode/core/setup/config_setup.py +79 -44
  19. tunacode/core/setup/coordinator.py +20 -13
  20. tunacode/core/setup/git_safety_setup.py +35 -49
  21. tunacode/core/state.py +2 -9
  22. tunacode/exceptions.py +0 -2
  23. tunacode/prompts/system.txt +179 -69
  24. tunacode/tools/__init__.py +10 -1
  25. tunacode/tools/base.py +1 -1
  26. tunacode/tools/bash.py +5 -5
  27. tunacode/tools/grep.py +210 -250
  28. tunacode/tools/read_file.py +2 -8
  29. tunacode/tools/run_command.py +4 -11
  30. tunacode/tools/update_file.py +2 -6
  31. tunacode/ui/completers.py +32 -31
  32. tunacode/ui/console.py +3 -3
  33. tunacode/ui/input.py +8 -5
  34. tunacode/ui/keybindings.py +1 -3
  35. tunacode/ui/lexers.py +16 -16
  36. tunacode/ui/output.py +2 -2
  37. tunacode/ui/panels.py +8 -8
  38. tunacode/ui/prompt_manager.py +19 -7
  39. tunacode/utils/import_cache.py +11 -0
  40. tunacode/utils/user_configuration.py +24 -2
  41. {tunacode_cli-0.0.17.dist-info → tunacode_cli-0.0.19.dist-info}/METADATA +68 -11
  42. tunacode_cli-0.0.19.dist-info/RECORD +75 -0
  43. tunacode_cli-0.0.17.dist-info/RECORD +0 -67
  44. {tunacode_cli-0.0.17.dist-info → tunacode_cli-0.0.19.dist-info}/WHEEL +0 -0
  45. {tunacode_cli-0.0.17.dist-info → tunacode_cli-0.0.19.dist-info}/entry_points.txt +0 -0
  46. {tunacode_cli-0.0.17.dist-info → tunacode_cli-0.0.19.dist-info}/licenses/LICENSE +0 -0
  47. {tunacode_cli-0.0.17.dist-info → tunacode_cli-0.0.19.dist-info}/top_level.txt +0 -0
@@ -18,10 +18,9 @@ from textual.binding import Binding
18
18
  from textual.containers import Container, Horizontal, Vertical, VerticalScroll
19
19
  from textual.message import Message
20
20
  from textual.reactive import reactive
21
- from textual.widgets import (
22
- Button, Footer, Header, Input, Label, Markdown as MarkdownWidget,
23
- Static, TextArea, TabbedContent, TabPane
24
- )
21
+ from textual.widgets import Button, Footer, Header, Input, Label
22
+ from textual.widgets import Markdown as MarkdownWidget
23
+ from textual.widgets import Static, TabbedContent, TabPane, TextArea
25
24
 
26
25
  from tunacode.configuration.settings import ApplicationSettings
27
26
  from tunacode.core.state import StateManager
@@ -31,52 +30,58 @@ from tunacode.utils.system import check_for_updates
31
30
 
32
31
  class ChatMessage(Static):
33
32
  """A single chat message widget."""
34
-
33
+
35
34
  def __init__(self, sender: str, content: str, message_type: str = "user"):
36
35
  super().__init__()
37
36
  self.sender = sender
38
37
  self.content = content
39
38
  self.message_type = message_type
40
-
39
+
41
40
  def compose(self) -> ComposeResult:
42
41
  """Compose the chat message."""
43
42
  if self.message_type == "user":
44
43
  yield Static(f"[bold cyan]❯ You[/bold cyan]\n{self.content}", classes="user-message")
45
44
  elif self.message_type == "agent":
46
- yield Static(f"[bold green]🤖 TunaCode[/bold green]\n{self.content}", classes="agent-message")
45
+ yield Static(
46
+ f"[bold green]🤖 TunaCode[/bold green]\n{self.content}", classes="agent-message"
47
+ )
47
48
  elif self.message_type == "system":
48
- yield Static(f"[bold yellow]⚠️ System[/bold yellow]\n{self.content}", classes="system-message")
49
+ yield Static(
50
+ f"[bold yellow]⚠️ System[/bold yellow]\n{self.content}", classes="system-message"
51
+ )
49
52
  elif self.message_type == "tool":
50
- yield Static(f"[bold magenta]🔧 Tool[/bold magenta]\n{self.content}", classes="tool-message")
53
+ yield Static(
54
+ f"[bold magenta]🔧 Tool[/bold magenta]\n{self.content}", classes="tool-message"
55
+ )
51
56
 
52
57
 
53
58
  class Sidebar(Container):
54
59
  """Sidebar with model info and commands."""
55
-
60
+
56
61
  def __init__(self, state_manager: StateManager):
57
62
  super().__init__()
58
63
  self.state_manager = state_manager
59
-
64
+
60
65
  def compose(self) -> ComposeResult:
61
66
  """Compose the sidebar."""
62
67
  yield Static("[bold]TunaCode[/bold]", classes="sidebar-title")
63
68
  yield Static(f"Model: {self.state_manager.session.current_model}", id="current-model")
64
69
  yield Static("", classes="spacer")
65
-
70
+
66
71
  yield Static("[bold]Commands[/bold]", classes="section-title")
67
72
  yield Static("/help - Show help", classes="command-item")
68
73
  yield Static("/clear - Clear chat", classes="command-item")
69
74
  yield Static("/model - Switch model", classes="command-item")
70
75
  yield Static("/yolo - Toggle confirmations", classes="command-item")
71
76
  yield Static("", classes="spacer")
72
-
77
+
73
78
  yield Static("[bold]Status[/bold]", classes="section-title")
74
79
  yield Static("● Ready", id="status", classes="status-ready")
75
80
 
76
81
 
77
82
  class ChatHistory(VerticalScroll):
78
83
  """Scrollable chat history container."""
79
-
84
+
80
85
  def add_message(self, sender: str, content: str, message_type: str = "user") -> None:
81
86
  """Add a new message to the chat history."""
82
87
  message = ChatMessage(sender, content, message_type)
@@ -86,20 +91,20 @@ class ChatHistory(VerticalScroll):
86
91
 
87
92
  class InputArea(Container):
88
93
  """Input area with text area and send button."""
89
-
94
+
90
95
  class SendMessage(Message):
91
96
  """Message sent when user submits input."""
92
-
97
+
93
98
  def __init__(self, content: str) -> None:
94
99
  self.content = content
95
100
  super().__init__()
96
-
101
+
97
102
  def compose(self) -> ComposeResult:
98
103
  """Compose the input area."""
99
104
  with Horizontal():
100
105
  yield TextArea(id="message-input")
101
106
  yield Button("Send", id="send-button", variant="primary")
102
-
107
+
103
108
  @on(Button.Pressed, "#send-button")
104
109
  def send_message(self) -> None:
105
110
  """Send the current message."""
@@ -108,7 +113,7 @@ class InputArea(Container):
108
113
  if content:
109
114
  self.post_message(self.SendMessage(content))
110
115
  text_area.clear()
111
-
116
+
112
117
  def on_key(self, event) -> None:
113
118
  """Handle key events."""
114
119
  if event.key == "ctrl+enter":
@@ -117,7 +122,7 @@ class InputArea(Container):
117
122
 
118
123
  class TunaCodeApp(App):
119
124
  """Main TunaCode Textual application."""
120
-
125
+
121
126
  CSS = """
122
127
  Sidebar {
123
128
  width: 30;
@@ -202,79 +207,75 @@ class TunaCodeApp(App):
202
207
  margin-left: 1;
203
208
  }
204
209
  """
205
-
210
+
206
211
  BINDINGS = [
207
212
  Binding("ctrl+c", "quit", "Quit"),
208
213
  Binding("ctrl+l", "clear_chat", "Clear"),
209
214
  Binding("f1", "help", "Help"),
210
215
  Binding("f2", "model_info", "Model"),
211
216
  ]
212
-
217
+
213
218
  def __init__(self, state_manager: StateManager):
214
219
  super().__init__()
215
220
  self.state_manager = state_manager
216
221
  self.chat_history: Optional[ChatHistory] = None
217
222
  self.sidebar: Optional[Sidebar] = None
218
223
  self.input_area: Optional[InputArea] = None
219
-
224
+
220
225
  def compose(self) -> ComposeResult:
221
226
  """Compose the main application layout."""
222
227
  yield Header()
223
-
228
+
224
229
  with Horizontal():
225
230
  self.sidebar = Sidebar(self.state_manager)
226
231
  yield self.sidebar
227
-
232
+
228
233
  with Vertical():
229
234
  self.chat_history = ChatHistory()
230
235
  yield self.chat_history
231
-
236
+
232
237
  self.input_area = InputArea()
233
238
  yield self.input_area
234
-
239
+
235
240
  yield Footer()
236
-
241
+
237
242
  def on_mount(self) -> None:
238
243
  """Called when the app is mounted."""
239
244
  # Add welcome messages
240
245
  self.chat_history.add_message(
241
- "System",
242
- "Welcome to TunaCode v0.11 - Your AI-powered development assistant",
243
- "system"
246
+ "System", "Welcome to TunaCode v0.11 - Your AI-powered development assistant", "system"
244
247
  )
245
248
  self.chat_history.add_message(
246
- "System",
247
- f"Current model: {self.state_manager.session.current_model}",
248
- "system"
249
+ "System", f"Current model: {self.state_manager.session.current_model}", "system"
249
250
  )
250
251
  self.chat_history.add_message(
251
252
  "System",
252
253
  "⚠️ IMPORTANT: Always use git branches before making major changes\nType '/help' for available commands",
253
- "system"
254
+ "system",
254
255
  )
255
-
256
+
256
257
  @on(InputArea.SendMessage)
257
258
  async def handle_message(self, message: InputArea.SendMessage) -> None:
258
259
  """Handle incoming messages from the input area."""
259
260
  content = message.content
260
-
261
+
261
262
  # Add user message to chat
262
263
  self.chat_history.add_message("You", content, "user")
263
-
264
+
264
265
  # Update status
265
266
  status_widget = self.sidebar.query_one("#status", Static)
266
267
  status_widget.update("● Processing...")
267
268
  status_widget.classes = "status-busy"
268
-
269
+
269
270
  if content.startswith("/"):
270
271
  await self.handle_command(content)
271
272
  else:
272
273
  await self.handle_user_input(content)
273
-
274
+
274
275
  # Reset status
275
276
  status_widget.update("● Ready")
276
277
  status_widget.classes = "status-ready"
277
-
278
+
278
279
  async def handle_command(self, command: str) -> None:
279
280
  """Handle slash commands."""
280
281
  if command == "/help":
@@ -293,57 +294,58 @@ F1 - Help
293
294
  F2 - Model info
294
295
  Ctrl+Enter - Send message"""
295
296
  self.chat_history.add_message("System", help_text, "system")
296
-
297
+
297
298
  elif command == "/clear":
298
299
  await self.action_clear_chat()
299
-
300
+
300
301
  elif command == "/model":
301
302
  model_info = f"Current model: {self.state_manager.session.current_model}"
302
303
  self.chat_history.add_message("System", model_info, "system")
303
-
304
+
304
305
  elif command == "/yolo":
305
306
  # Toggle yolo mode
306
- current_state = getattr(self.state_manager.session, 'yolo_mode', False)
307
+ current_state = getattr(self.state_manager.session, "yolo_mode", False)
307
308
  self.state_manager.session.yolo_mode = not current_state
308
309
  new_state = "enabled" if not current_state else "disabled"
309
310
  self.chat_history.add_message("System", f"Confirmation prompts {new_state}", "system")
310
-
311
+
311
312
  elif command == "/quit":
312
313
  await self.action_quit()
313
-
314
+
314
315
  else:
315
316
  self.chat_history.add_message("System", f"Unknown command: {command}", "system")
316
-
317
+
317
318
  async def handle_user_input(self, text: str) -> None:
318
319
  """Handle regular user input."""
319
320
  try:
320
321
  # Use the bridge to process the input
321
- if not hasattr(self, '_bridge'):
322
+ if not hasattr(self, "_bridge"):
322
323
  from tunacode.cli.textual_bridge import TextualAgentBridge
324
+
323
325
  self._bridge = TextualAgentBridge(self.state_manager, self._bridge_message_callback)
324
-
326
+
325
327
  # Process the request
326
328
  response = await self._bridge.process_user_input(text)
327
-
329
+
328
330
  # Add the agent's response to chat
329
331
  self.chat_history.add_message("TunaCode", response, "agent")
330
-
332
+
331
333
  except Exception as e:
332
334
  self.chat_history.add_message("System", f"Error: {str(e)}", "system")
333
-
335
+
334
336
  async def _bridge_message_callback(self, message_type: str, content: str) -> None:
335
337
  """Callback for bridge to send messages to the UI."""
336
338
  self.chat_history.add_message("System", content, message_type)
337
-
339
+
338
340
  def action_clear_chat(self) -> None:
339
341
  """Clear the chat history."""
340
342
  self.chat_history.remove_children()
341
343
  self.chat_history.add_message("System", "Chat cleared", "system")
342
-
344
+
343
345
  def action_help(self) -> None:
344
346
  """Show help information."""
345
347
  self.handle_command("/help")
346
-
348
+
347
349
  def action_model_info(self) -> None:
348
350
  """Show model information."""
349
351
  self.handle_command("/model")
@@ -358,29 +360,30 @@ async def run_textual_app(state_manager: StateManager) -> None:
358
360
  def main():
359
361
  """Main entry point for the Textual app."""
360
362
  import sys
361
-
363
+
362
364
  # Handle command line arguments
363
365
  version_flag = "--version" in sys.argv or "-v" in sys.argv
364
366
  if version_flag:
365
367
  from tunacode.constants import APP_VERSION
368
+
366
369
  print(f"TunaCode v{APP_VERSION}")
367
370
  return
368
-
371
+
369
372
  # Initialize state manager
370
373
  state_manager = StateManager()
371
374
  app_settings = ApplicationSettings()
372
-
375
+
373
376
  # Show banner
374
377
  print("🐟 TunaCode - Modern AI Development Assistant")
375
378
  print("=" * 50)
376
-
379
+
377
380
  # Check for updates
378
381
  has_update, latest_version = check_for_updates()
379
382
  if has_update:
380
383
  print(f"📦 Update available: v{latest_version}")
381
384
  print("Run: pip install --upgrade tunacode-cli")
382
385
  print()
383
-
386
+
384
387
  # Parse CLI arguments for configuration
385
388
  cli_config = {}
386
389
  args = sys.argv[1:]
@@ -400,24 +403,24 @@ def main():
400
403
  i += 1
401
404
  else:
402
405
  i += 1
403
-
406
+
404
407
  async def run_app():
405
408
  try:
406
409
  # Run setup
407
410
  run_setup = cli_config.get("setup", False)
408
411
  await setup(run_setup, state_manager, cli_config)
409
-
412
+
410
413
  # Run the Textual app
411
414
  await run_textual_app(state_manager)
412
-
415
+
413
416
  except KeyboardInterrupt:
414
417
  print("\n👋 Goodbye!")
415
418
  except Exception as e:
416
419
  print(f"❌ Error: {e}")
417
-
420
+
418
421
  # Run the async app
419
422
  asyncio.run(run_app())
420
423
 
421
424
 
422
425
  if __name__ == "__main__":
423
- main()
426
+ main()
@@ -6,90 +6,89 @@ with the new Textual-based interface while maintaining compatibility.
6
6
  """
7
7
 
8
8
  import asyncio
9
- from typing import Callable, Optional
10
9
  from asyncio.exceptions import CancelledError
10
+ from typing import Callable, Optional
11
11
 
12
12
  from pydantic_ai.exceptions import UnexpectedModelBehavior
13
13
 
14
+ from tunacode.cli.commands import CommandRegistry
15
+ from tunacode.cli.repl import _parse_args
14
16
  from tunacode.core.agents import main as agent
15
17
  from tunacode.core.agents.main import patch_tool_messages
16
18
  from tunacode.core.tool_handler import ToolHandler
17
19
  from tunacode.exceptions import AgentError, UserAbortError, ValidationError
18
20
  from tunacode.types import StateManager
19
- from tunacode.cli.commands import CommandRegistry
20
- from tunacode.cli.repl import _parse_args
21
21
  from tunacode.ui.tool_ui import ToolUI
22
22
 
23
23
 
24
24
  class TextualAgentBridge:
25
25
  """Bridge between Textual UI and existing agent logic."""
26
-
26
+
27
27
  def __init__(self, state_manager: StateManager, message_callback: Callable):
28
28
  self.state_manager = state_manager
29
29
  self.message_callback = message_callback
30
30
  self.tool_ui = ToolUI()
31
31
  self.command_registry = CommandRegistry()
32
32
  self.command_registry.register_all_default_commands()
33
-
33
+
34
34
  async def process_user_input(self, text: str) -> str:
35
35
  """Process user input and return the agent's response."""
36
36
  if text.startswith("/"):
37
37
  return await self._handle_command(text)
38
38
  else:
39
39
  return await self._process_agent_request(text)
40
-
40
+
41
41
  async def _handle_command(self, command: str) -> str:
42
42
  """Handle slash commands."""
43
43
  try:
44
44
  from tunacode.types import CommandContext
45
-
45
+
46
46
  # Create command context
47
47
  context = CommandContext(
48
- state_manager=self.state_manager,
49
- process_request=self._process_agent_request
48
+ state_manager=self.state_manager, process_request=self._process_agent_request
50
49
  )
51
-
50
+
52
51
  # Set the process_request callback for commands that need it
53
52
  self.command_registry.set_process_request_callback(self._process_agent_request)
54
-
53
+
55
54
  # Execute the command
56
55
  result = await self.command_registry.execute(command, context)
57
-
56
+
58
57
  if result == "restart":
59
58
  return "Application restart requested."
60
59
  elif result:
61
60
  return str(result)
62
61
  else:
63
62
  return f"Command '{command}' executed successfully."
64
-
63
+
65
64
  except ValidationError as e:
66
65
  return f"Error: {str(e)}"
67
66
  except Exception as e:
68
67
  return f"Command error: {str(e)}"
69
-
68
+
70
69
  async def _process_agent_request(self, text: str) -> str:
71
70
  """Process input using the agent."""
72
71
  try:
73
72
  # Expand @file references before sending to the agent
74
73
  try:
75
74
  from tunacode.utils.text_utils import expand_file_refs
75
+
76
76
  text = expand_file_refs(text)
77
77
  except ValueError as e:
78
78
  return f"Error: {str(e)}"
79
-
79
+
80
80
  # Notify UI about processing start
81
81
  await self.message_callback("tool", "Processing request...")
82
-
82
+
83
83
  # Create a tool callback that integrates with Textual
84
84
  def tool_callback_with_state(part, node):
85
85
  return self._tool_handler(part, node)
86
-
86
+
87
87
  # Get or create agent instance
88
88
  instance = agent.get_or_create_agent(
89
- self.state_manager.session.current_model,
90
- self.state_manager
89
+ self.state_manager.session.current_model, self.state_manager
91
90
  )
92
-
91
+
93
92
  # Process the request
94
93
  async with instance.run_mcp_servers():
95
94
  res = await agent.process_request(
@@ -98,12 +97,12 @@ class TextualAgentBridge:
98
97
  self.state_manager,
99
98
  tool_callback=tool_callback_with_state,
100
99
  )
101
-
100
+
102
101
  if res and res.result:
103
102
  return res.result.output
104
103
  else:
105
104
  return "Request processed (no output)."
106
-
105
+
107
106
  except CancelledError:
108
107
  return "Request cancelled."
109
108
  except UserAbortError:
@@ -117,29 +116,31 @@ class TextualAgentBridge:
117
116
  agent_error = AgentError(f"Agent processing failed: {str(e)}")
118
117
  agent_error.__cause__ = e
119
118
  return f"Error: {str(e)}"
120
-
119
+
121
120
  async def _tool_handler(self, part, node):
122
121
  """Handle tool execution with Textual UI integration."""
123
122
  await self.message_callback("tool", f"Tool: {part.tool_name}")
124
-
123
+
125
124
  try:
126
125
  # Create tool handler with state
127
126
  tool_handler = ToolHandler(self.state_manager)
128
127
  args = _parse_args(part.args)
129
-
128
+
130
129
  # Check if confirmation is needed
131
130
  if tool_handler.should_confirm(part.tool_name):
132
131
  # Create confirmation request
133
132
  request = tool_handler.create_confirmation_request(part.tool_name, args)
134
-
133
+
135
134
  # For now, show a simple confirmation in the UI
136
135
  # In a full implementation, this would show a proper modal dialog
137
- await self.message_callback("system", f"Tool confirmation: {part.tool_name} with args: {args}")
138
-
136
+ await self.message_callback(
137
+ "system", f"Tool confirmation: {part.tool_name} with args: {args}"
138
+ )
139
+
139
140
  # For demo purposes, auto-approve (in production, this should be interactive)
140
141
  if not tool_handler.process_confirmation(True, part.tool_name):
141
142
  raise UserAbortError("User aborted tool execution.")
142
-
143
+
143
144
  except UserAbortError:
144
145
  patch_tool_messages("Operation aborted by user.", self.state_manager)
145
146
  raise
@@ -147,12 +148,12 @@ class TextualAgentBridge:
147
148
 
148
149
  class TextualToolConfirmation:
149
150
  """Handle tool confirmations in Textual UI."""
150
-
151
+
151
152
  def __init__(self, app_instance):
152
153
  self.app = app_instance
153
-
154
+
154
155
  async def show_confirmation(self, tool_name: str, args: dict) -> bool:
155
156
  """Show tool confirmation dialog and return user's choice."""
156
157
  # This would show a modal dialog in the Textual app
157
158
  # For now, return True (auto-approve)
158
- return True
159
+ return True
@@ -7,15 +7,8 @@ Handles configuration paths, model registries, and application metadata.
7
7
 
8
8
  from pathlib import Path
9
9
 
10
- from tunacode.constants import (
11
- APP_NAME,
12
- APP_VERSION,
13
- CONFIG_FILE_NAME,
14
- TOOL_READ_FILE,
15
- TOOL_RUN_COMMAND,
16
- TOOL_UPDATE_FILE,
17
- TOOL_WRITE_FILE,
18
- )
10
+ from tunacode.constants import (APP_NAME, APP_VERSION, CONFIG_FILE_NAME, TOOL_READ_FILE,
11
+ TOOL_RUN_COMMAND, TOOL_UPDATE_FILE, TOOL_WRITE_FILE)
19
12
  from tunacode.types import ConfigFile, ConfigPath, ToolName
20
13
 
21
14
 
tunacode/constants.py CHANGED
@@ -7,7 +7,7 @@ Centralizes all magic strings, UI text, error messages, and application constant
7
7
 
8
8
  # Application info
9
9
  APP_NAME = "TunaCode"
10
- APP_VERSION = "0.0.17"
10
+ APP_VERSION = "0.0.19"
11
11
 
12
12
  # File patterns
13
13
  GUIDE_FILE_PATTERN = "{name}.md"
@@ -101,9 +101,7 @@ ERROR_INVALID_PROVIDER = "Invalid provider number"
101
101
  ERROR_FILE_NOT_FOUND = "Error: File not found at '{filepath}'."
102
102
  ERROR_FILE_TOO_LARGE = "Error: File '{filepath}' is too large (> 100KB)."
103
103
  ERROR_FILE_DECODE = "Error reading file '{filepath}': Could not decode using UTF-8."
104
- ERROR_FILE_DECODE_DETAILS = (
105
- "It might be a binary file or use a different encoding. {error}"
106
- )
104
+ ERROR_FILE_DECODE_DETAILS = "It might be a binary file or use a different encoding. {error}"
107
105
  ERROR_COMMAND_NOT_FOUND = "Error: Command not found or failed to execute:"
108
106
  ERROR_COMMAND_EXECUTION = (
109
107
  "Error: Command not found or failed to execute: {command}. Details: {error}"
tunacode/context.py CHANGED
@@ -4,8 +4,8 @@ import subprocess
4
4
  from pathlib import Path
5
5
  from typing import Dict, List
6
6
 
7
- from tunacode.utils.system import list_cwd
8
7
  from tunacode.utils.ripgrep import ripgrep
8
+ from tunacode.utils.system import list_cwd
9
9
 
10
10
 
11
11
  async def get_git_status() -> Dict[str, object]:
@@ -0,0 +1,12 @@
1
+ """Agent helper modules."""
2
+
3
+ from .main import get_or_create_agent, process_request
4
+ from .orchestrator import OrchestratorAgent
5
+ from .readonly import ReadOnlyAgent
6
+
7
+ __all__ = [
8
+ "process_request",
9
+ "get_or_create_agent",
10
+ "OrchestratorAgent",
11
+ "ReadOnlyAgent",
12
+ ]