tunacode-cli 0.0.55__py3-none-any.whl → 0.0.78.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.

Potentially problematic release.


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

Files changed (114) hide show
  1. tunacode/cli/commands/__init__.py +2 -2
  2. tunacode/cli/commands/implementations/__init__.py +2 -3
  3. tunacode/cli/commands/implementations/command_reload.py +48 -0
  4. tunacode/cli/commands/implementations/debug.py +2 -2
  5. tunacode/cli/commands/implementations/development.py +10 -8
  6. tunacode/cli/commands/implementations/model.py +357 -29
  7. tunacode/cli/commands/implementations/quickstart.py +43 -0
  8. tunacode/cli/commands/implementations/system.py +96 -3
  9. tunacode/cli/commands/implementations/template.py +0 -2
  10. tunacode/cli/commands/registry.py +139 -5
  11. tunacode/cli/commands/slash/__init__.py +32 -0
  12. tunacode/cli/commands/slash/command.py +157 -0
  13. tunacode/cli/commands/slash/loader.py +135 -0
  14. tunacode/cli/commands/slash/processor.py +294 -0
  15. tunacode/cli/commands/slash/types.py +93 -0
  16. tunacode/cli/commands/slash/validator.py +400 -0
  17. tunacode/cli/main.py +23 -2
  18. tunacode/cli/repl.py +217 -190
  19. tunacode/cli/repl_components/command_parser.py +38 -4
  20. tunacode/cli/repl_components/error_recovery.py +85 -4
  21. tunacode/cli/repl_components/output_display.py +12 -1
  22. tunacode/cli/repl_components/tool_executor.py +1 -1
  23. tunacode/configuration/defaults.py +12 -3
  24. tunacode/configuration/key_descriptions.py +284 -0
  25. tunacode/configuration/settings.py +0 -1
  26. tunacode/constants.py +12 -40
  27. tunacode/core/agents/__init__.py +43 -2
  28. tunacode/core/agents/agent_components/__init__.py +7 -0
  29. tunacode/core/agents/agent_components/agent_config.py +249 -55
  30. tunacode/core/agents/agent_components/agent_helpers.py +43 -13
  31. tunacode/core/agents/agent_components/node_processor.py +179 -139
  32. tunacode/core/agents/agent_components/response_state.py +123 -6
  33. tunacode/core/agents/agent_components/state_transition.py +116 -0
  34. tunacode/core/agents/agent_components/streaming.py +296 -0
  35. tunacode/core/agents/agent_components/task_completion.py +19 -6
  36. tunacode/core/agents/agent_components/tool_buffer.py +21 -1
  37. tunacode/core/agents/agent_components/tool_executor.py +10 -0
  38. tunacode/core/agents/main.py +522 -370
  39. tunacode/core/agents/main_legact.py +538 -0
  40. tunacode/core/agents/prompts.py +66 -0
  41. tunacode/core/agents/utils.py +29 -121
  42. tunacode/core/code_index.py +83 -29
  43. tunacode/core/setup/__init__.py +0 -2
  44. tunacode/core/setup/config_setup.py +110 -20
  45. tunacode/core/setup/config_wizard.py +230 -0
  46. tunacode/core/setup/coordinator.py +14 -5
  47. tunacode/core/state.py +16 -20
  48. tunacode/core/token_usage/usage_tracker.py +5 -3
  49. tunacode/core/tool_authorization.py +352 -0
  50. tunacode/core/tool_handler.py +67 -40
  51. tunacode/exceptions.py +119 -5
  52. tunacode/prompts/system.xml +751 -0
  53. tunacode/services/mcp.py +125 -7
  54. tunacode/setup.py +5 -25
  55. tunacode/tools/base.py +163 -0
  56. tunacode/tools/bash.py +110 -1
  57. tunacode/tools/glob.py +332 -34
  58. tunacode/tools/grep.py +179 -82
  59. tunacode/tools/grep_components/result_formatter.py +98 -4
  60. tunacode/tools/list_dir.py +132 -2
  61. tunacode/tools/prompts/bash_prompt.xml +72 -0
  62. tunacode/tools/prompts/glob_prompt.xml +45 -0
  63. tunacode/tools/prompts/grep_prompt.xml +98 -0
  64. tunacode/tools/prompts/list_dir_prompt.xml +31 -0
  65. tunacode/tools/prompts/react_prompt.xml +23 -0
  66. tunacode/tools/prompts/read_file_prompt.xml +54 -0
  67. tunacode/tools/prompts/run_command_prompt.xml +64 -0
  68. tunacode/tools/prompts/update_file_prompt.xml +53 -0
  69. tunacode/tools/prompts/write_file_prompt.xml +37 -0
  70. tunacode/tools/react.py +153 -0
  71. tunacode/tools/read_file.py +91 -0
  72. tunacode/tools/run_command.py +114 -0
  73. tunacode/tools/schema_assembler.py +167 -0
  74. tunacode/tools/update_file.py +94 -0
  75. tunacode/tools/write_file.py +86 -0
  76. tunacode/tools/xml_helper.py +83 -0
  77. tunacode/tutorial/__init__.py +9 -0
  78. tunacode/tutorial/content.py +98 -0
  79. tunacode/tutorial/manager.py +182 -0
  80. tunacode/tutorial/steps.py +124 -0
  81. tunacode/types.py +20 -27
  82. tunacode/ui/completers.py +434 -50
  83. tunacode/ui/config_dashboard.py +585 -0
  84. tunacode/ui/console.py +63 -11
  85. tunacode/ui/input.py +20 -3
  86. tunacode/ui/keybindings.py +7 -4
  87. tunacode/ui/model_selector.py +395 -0
  88. tunacode/ui/output.py +40 -19
  89. tunacode/ui/panels.py +212 -43
  90. tunacode/ui/path_heuristics.py +91 -0
  91. tunacode/ui/prompt_manager.py +5 -1
  92. tunacode/ui/tool_ui.py +33 -10
  93. tunacode/utils/api_key_validation.py +93 -0
  94. tunacode/utils/config_comparator.py +340 -0
  95. tunacode/utils/json_utils.py +206 -0
  96. tunacode/utils/message_utils.py +14 -4
  97. tunacode/utils/models_registry.py +593 -0
  98. tunacode/utils/ripgrep.py +332 -9
  99. tunacode/utils/text_utils.py +18 -1
  100. tunacode/utils/user_configuration.py +45 -0
  101. tunacode_cli-0.0.78.6.dist-info/METADATA +260 -0
  102. tunacode_cli-0.0.78.6.dist-info/RECORD +158 -0
  103. {tunacode_cli-0.0.55.dist-info → tunacode_cli-0.0.78.6.dist-info}/WHEEL +1 -2
  104. tunacode/cli/commands/implementations/todo.py +0 -217
  105. tunacode/context.py +0 -71
  106. tunacode/core/setup/git_safety_setup.py +0 -182
  107. tunacode/prompts/system.md +0 -731
  108. tunacode/tools/read_file_async_poc.py +0 -196
  109. tunacode/tools/todo.py +0 -349
  110. tunacode_cli-0.0.55.dist-info/METADATA +0 -322
  111. tunacode_cli-0.0.55.dist-info/RECORD +0 -126
  112. tunacode_cli-0.0.55.dist-info/top_level.txt +0 -1
  113. {tunacode_cli-0.0.55.dist-info → tunacode_cli-0.0.78.6.dist-info}/entry_points.txt +0 -0
  114. {tunacode_cli-0.0.55.dist-info → tunacode_cli-0.0.78.6.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,83 @@
1
+ """Helper module for loading prompts and schemas from XML files."""
2
+
3
+ import logging
4
+ from functools import lru_cache
5
+ from pathlib import Path
6
+ from typing import Any, Dict, List, Optional
7
+
8
+ import defusedxml.ElementTree as ET
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ @lru_cache(maxsize=32)
14
+ def load_prompt_from_xml(tool_name: str) -> Optional[str]:
15
+ """Load and return the base prompt from XML file.
16
+
17
+ Args:
18
+ tool_name: Name of the tool (e.g., 'grep', 'glob')
19
+
20
+ Returns:
21
+ str: The loaded prompt from XML or None if not found
22
+ """
23
+ try:
24
+ prompt_file = Path(__file__).parent / "prompts" / f"{tool_name}_prompt.xml"
25
+ if prompt_file.exists():
26
+ tree = ET.parse(prompt_file)
27
+ root = tree.getroot()
28
+ description = root.find("description")
29
+ if description is not None:
30
+ return description.text.strip()
31
+ except Exception as e:
32
+ logger.warning(f"Failed to load XML prompt for {tool_name}: {e}")
33
+ return None
34
+
35
+
36
+ @lru_cache(maxsize=32)
37
+ def load_parameters_schema_from_xml(tool_name: str) -> Optional[Dict[str, Any]]:
38
+ """Load and return the parameters schema from XML file.
39
+
40
+ Args:
41
+ tool_name: Name of the tool (e.g., 'grep', 'glob')
42
+
43
+ Returns:
44
+ Dict containing the JSON schema for tool parameters or None if not found
45
+ """
46
+ try:
47
+ prompt_file = Path(__file__).parent / "prompts" / f"{tool_name}_prompt.xml"
48
+ if prompt_file.exists():
49
+ tree = ET.parse(prompt_file)
50
+ root = tree.getroot()
51
+ parameters = root.find("parameters")
52
+ if parameters is not None:
53
+ schema: Dict[str, Any] = {"type": "object", "properties": {}, "required": []}
54
+ required_fields: List[str] = []
55
+
56
+ for param in parameters.findall("parameter"):
57
+ name = param.get("name")
58
+ required = param.get("required", "false").lower() == "true"
59
+ param_type = param.find("type")
60
+ description = param.find("description")
61
+
62
+ if name and param_type is not None:
63
+ prop = {
64
+ "type": param_type.text.strip(),
65
+ "description": description.text.strip()
66
+ if description is not None
67
+ else "",
68
+ }
69
+
70
+ # Add enum values if present
71
+ enums = param.findall("enum")
72
+ if enums:
73
+ prop["enum"] = [e.text.strip() for e in enums]
74
+
75
+ schema["properties"][name] = prop
76
+ if required:
77
+ required_fields.append(name)
78
+
79
+ schema["required"] = required_fields
80
+ return schema
81
+ except Exception as e:
82
+ logger.warning(f"Failed to load parameters from XML for {tool_name}: {e}")
83
+ return None
@@ -0,0 +1,9 @@
1
+ """
2
+ Module: tunacode.tutorial
3
+
4
+ Tutorial system for TunaCode onboarding and user guidance.
5
+ """
6
+
7
+ from .manager import TutorialManager
8
+
9
+ __all__ = ["TutorialManager"]
@@ -0,0 +1,98 @@
1
+ """
2
+ Module: tunacode.tutorial.content
3
+
4
+ Tutorial content definitions and step configurations.
5
+ """
6
+
7
+ from typing import Dict, List
8
+
9
+ # Tutorial step content library
10
+ TUTORIAL_CONTENT: Dict[str, Dict[str, str]] = {
11
+ "welcome": {
12
+ "title": "🎯 Welcome to TunaCode!",
13
+ "content": """TunaCode is your AI-powered development assistant.
14
+
15
+ In this quick tutorial, you'll learn how to:
16
+ • Chat with AI about your code
17
+ • Use commands to control TunaCode
18
+ • Work with files and projects
19
+ • Get help when you need it
20
+
21
+ This tutorial takes about 2-3 minutes. Ready to start?""",
22
+ "action": "Press Enter to continue...",
23
+ },
24
+ "basic_chat": {
25
+ "title": "💬 Basic AI Chat",
26
+ "content": """The core of TunaCode is natural conversation with AI.
27
+
28
+ You can ask questions like:
29
+ • "How do I implement a binary search in Python?"
30
+ • "Review this function and suggest improvements"
31
+ • "Help me debug this error message"
32
+ • "Explain what this code does"
33
+
34
+ Just type your question naturally - no special syntax needed!""",
35
+ "action": "Try asking: 'What can you help me with?'",
36
+ },
37
+ "file_operations": {
38
+ "title": "📁 Working with Files",
39
+ "content": """TunaCode can read, create, and modify files in your project.
40
+
41
+ Useful commands:
42
+ • Reference files with @filename.py
43
+ • Use /read to explicitly read files
44
+ • Ask to create or modify files
45
+ • Get help with /help
46
+
47
+ TunaCode understands your project structure and can work across multiple files.""",
48
+ "action": "Try: 'Read the current directory structure'",
49
+ },
50
+ "commands": {
51
+ "title": "⚙️ TunaCode Commands",
52
+ "content": """Commands start with / and give you control over TunaCode:
53
+
54
+ Essential commands:
55
+ • /help - Show all available commands
56
+ • /model - Switch AI models
57
+ • /clear - Clear conversation history
58
+ • /exit - Exit TunaCode
59
+
60
+ System commands:
61
+ • !command - Run shell commands
62
+ • /streaming - Toggle streaming responses""",
63
+ "action": "Try typing: /help",
64
+ },
65
+ "best_practices": {
66
+ "title": "✨ Best Practices",
67
+ "content": """To get the most out of TunaCode:
68
+
69
+ 🎯 Be specific: "Fix the bug in login.py line 42" vs "fix my code"
70
+ 📁 Use file references: "@app.py" to include files in context
71
+ 🔄 Break down large tasks: Ask for step-by-step guidance
72
+ 💬 Ask follow-up questions: TunaCode remembers your conversation
73
+ 🚀 Experiment: Try different prompts to see what works best
74
+
75
+ Remember: TunaCode is here to help you code faster and better!""",
76
+ "action": "Press Enter to complete the tutorial...",
77
+ },
78
+ "completion": {
79
+ "title": "🎉 Tutorial Complete!",
80
+ "content": """Congratulations! You're ready to use TunaCode.
81
+
82
+ Quick recap:
83
+ ✅ Chat naturally with AI about code
84
+ ✅ Use @ to reference files
85
+ ✅ Try /help for commands
86
+ ✅ Ask specific questions for better results
87
+
88
+ 🚀 Ready to start coding? Just ask TunaCode anything!
89
+
90
+ Need help later? Use /quickstart to review this tutorial anytime.""",
91
+ "action": "Press Enter to start using TunaCode...",
92
+ },
93
+ }
94
+
95
+
96
+ def get_tutorial_steps() -> List[str]:
97
+ """Get the ordered list of tutorial step IDs."""
98
+ return ["welcome", "basic_chat", "file_operations", "commands", "best_practices", "completion"]
@@ -0,0 +1,182 @@
1
+ """
2
+ Module: tunacode.tutorial.manager
3
+
4
+ Tutorial manager for orchestrating the TunaCode onboarding experience.
5
+ """
6
+
7
+ import logging
8
+
9
+ from ..types import StateManager
10
+ from ..ui import console as ui
11
+ from .steps import (
12
+ create_tutorial_steps,
13
+ is_first_time_user,
14
+ is_tutorial_completed,
15
+ is_tutorial_declined,
16
+ load_tutorial_progress,
17
+ mark_tutorial_completed,
18
+ mark_tutorial_declined,
19
+ save_tutorial_progress,
20
+ )
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+
25
+ class TutorialManager:
26
+ """Manages the tutorial experience for TunaCode users."""
27
+
28
+ def __init__(self, state_manager: StateManager):
29
+ self.state_manager = state_manager
30
+ self.steps = [
31
+ "welcome",
32
+ "basic_chat",
33
+ "file_operations",
34
+ "commands",
35
+ "best_practices",
36
+ "completion",
37
+ ]
38
+
39
+ async def should_offer_tutorial(self) -> bool:
40
+ """Determine if we should offer the tutorial to the user."""
41
+ # Don't offer if already completed or declined
42
+ if is_tutorial_completed(self.state_manager):
43
+ return False
44
+
45
+ if is_tutorial_declined(self.state_manager):
46
+ return False
47
+
48
+ # Check if tutorial is enabled in settings
49
+ settings = self.state_manager.session.user_config.get("settings", {})
50
+ tutorial_enabled = settings.get("enable_tutorial", True)
51
+
52
+ if not tutorial_enabled:
53
+ return False
54
+
55
+ # Only offer to first-time users (installed within last 7 days)
56
+ if not is_first_time_user(self.state_manager):
57
+ return False
58
+
59
+ # Check if this is a fresh session (no significant interaction yet)
60
+ message_count = len(self.state_manager.session.messages)
61
+
62
+ # Offer tutorial if user has minimal interaction history
63
+ return message_count < 3
64
+
65
+ async def offer_tutorial(self) -> bool:
66
+ """Offer the tutorial to the user and return whether they accepted."""
67
+ await ui.panel(
68
+ "🎯 Welcome to TunaCode!",
69
+ "Would you like a quick 2-minute tutorial to get started?\n"
70
+ "This will help you learn the basics and start coding faster.",
71
+ border_style="cyan",
72
+ )
73
+
74
+ choice = await ui.input(
75
+ "tutorial_offer",
76
+ pretext=" → Start tutorial? [Y/n]: ",
77
+ state_manager=self.state_manager,
78
+ )
79
+
80
+ choice = choice.strip().lower()
81
+
82
+ if choice in ["n", "no", "false"]:
83
+ await ui.muted("Tutorial skipped. Use [green]/quickstart[/green] anytime to start it!")
84
+ # Mark as declined so we don't ask again
85
+ mark_tutorial_declined(self.state_manager)
86
+
87
+ # Save the configuration to persist the declined status
88
+ try:
89
+ from ..utils import user_configuration
90
+
91
+ user_configuration.save_config(self.state_manager)
92
+ except Exception as e:
93
+ logger.warning(f"Failed to save tutorial declined status: {e}")
94
+
95
+ return False
96
+
97
+ return True
98
+
99
+ async def run_tutorial(self, resume: bool = False) -> bool:
100
+ """
101
+ Run the complete tutorial experience.
102
+
103
+ Args:
104
+ resume: If True, resume from saved progress
105
+
106
+ Returns:
107
+ True if tutorial completed successfully, False if cancelled
108
+ """
109
+ try:
110
+ from .content import TUTORIAL_CONTENT
111
+
112
+ # Load progress or start from beginning
113
+ current_step = load_tutorial_progress(self.state_manager) if resume else 0
114
+ steps = create_tutorial_steps()
115
+
116
+ await ui.line()
117
+ await ui.info(f"🎯 Starting TunaCode Tutorial ({len(steps)} steps)")
118
+ await ui.line()
119
+
120
+ while current_step < len(steps):
121
+ step_id = steps[current_step]
122
+ step_content = TUTORIAL_CONTENT.get(step_id, {})
123
+
124
+ if not step_content:
125
+ logger.warning(f"Missing content for tutorial step: {step_id}")
126
+ current_step += 1
127
+ continue
128
+
129
+ # Display step content
130
+ await ui.panel(
131
+ f"Step {current_step + 1}/{len(steps)}: {step_content.get('title', step_id)}",
132
+ step_content.get("content", ""),
133
+ border_style="cyan",
134
+ )
135
+
136
+ # Get user input for progression
137
+ action_text = step_content.get("action", "Press Enter to continue...")
138
+ try:
139
+ user_input = await ui.input(
140
+ f"tutorial_step_{current_step}",
141
+ pretext=f" → {action_text} ",
142
+ state_manager=self.state_manager,
143
+ )
144
+
145
+ # Allow users to exit tutorial early
146
+ if user_input.lower() in ["quit", "exit", "skip"]:
147
+ await ui.info(
148
+ "Tutorial cancelled. Use [green]/quickstart[/green] to restart anytime!"
149
+ )
150
+ return False
151
+
152
+ except Exception as e:
153
+ logger.warning(f"Tutorial interrupted: {e}")
154
+ # Save progress before exiting
155
+ save_tutorial_progress(self.state_manager, current_step)
156
+ await ui.info("Tutorial paused. Use [green]/quickstart[/green] to resume!")
157
+ return False
158
+
159
+ current_step += 1
160
+ save_tutorial_progress(self.state_manager, current_step)
161
+
162
+ # Tutorial completed successfully
163
+ mark_tutorial_completed(self.state_manager)
164
+
165
+ # Save the completion status
166
+ try:
167
+ from ..utils import user_configuration
168
+
169
+ user_configuration.save_config(self.state_manager)
170
+ except Exception as e:
171
+ logger.warning(f"Failed to save tutorial completion status: {e}")
172
+
173
+ await ui.line()
174
+ await ui.success("🎉 Tutorial completed! You're ready to use TunaCode.")
175
+ await ui.line()
176
+
177
+ return True
178
+
179
+ except Exception as e:
180
+ logger.error(f"Tutorial failed: {e}")
181
+ await ui.error(f"Tutorial encountered an error: {e}")
182
+ return False
@@ -0,0 +1,124 @@
1
+ """
2
+ Module: tunacode.tutorial.steps
3
+
4
+ Tutorial step management and progress tracking functionality.
5
+ """
6
+
7
+ import logging
8
+ from dataclasses import dataclass
9
+ from typing import List, Optional
10
+
11
+ from ..types import StateManager
12
+ from .content import get_tutorial_steps
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ @dataclass
18
+ class TutorialStepResult:
19
+ """Result of executing a tutorial step."""
20
+
21
+ completed: bool
22
+ should_continue: bool
23
+ user_input: Optional[str] = None
24
+
25
+
26
+ def create_tutorial_steps() -> List[str]:
27
+ """Create and return the list of tutorial steps."""
28
+ return get_tutorial_steps()
29
+
30
+
31
+ def get_tutorial_completion_key() -> str:
32
+ """Get the config key for storing tutorial completion status."""
33
+ return "tutorial_completed"
34
+
35
+
36
+ def get_tutorial_progress_key() -> str:
37
+ """Get the config key for storing tutorial progress."""
38
+ return "tutorial_progress"
39
+
40
+
41
+ def mark_tutorial_completed(state_manager: StateManager) -> None:
42
+ """Mark the tutorial as completed in user config."""
43
+ if "settings" not in state_manager.session.user_config:
44
+ state_manager.session.user_config["settings"] = {}
45
+
46
+ state_manager.session.user_config["settings"][get_tutorial_completion_key()] = True
47
+
48
+ # Clear any existing progress since it's now completed
49
+ if get_tutorial_progress_key() in state_manager.session.user_config["settings"]:
50
+ del state_manager.session.user_config["settings"][get_tutorial_progress_key()]
51
+
52
+
53
+ def save_tutorial_progress(state_manager: StateManager, current_step: int) -> None:
54
+ """Save tutorial progress to user config."""
55
+ if "settings" not in state_manager.session.user_config:
56
+ state_manager.session.user_config["settings"] = {}
57
+
58
+ state_manager.session.user_config["settings"][get_tutorial_progress_key()] = current_step
59
+
60
+
61
+ def load_tutorial_progress(state_manager: StateManager) -> int:
62
+ """Load tutorial progress from user config."""
63
+ settings = state_manager.session.user_config.get("settings", {})
64
+ return settings.get(get_tutorial_progress_key(), 0)
65
+
66
+
67
+ def clear_tutorial_progress(state_manager: StateManager) -> None:
68
+ """Clear tutorial progress from user config."""
69
+ if "settings" not in state_manager.session.user_config:
70
+ return
71
+
72
+ if get_tutorial_progress_key() in state_manager.session.user_config["settings"]:
73
+ del state_manager.session.user_config["settings"][get_tutorial_progress_key()]
74
+
75
+
76
+ def is_tutorial_completed(state_manager: StateManager) -> bool:
77
+ """Check if the tutorial has been completed."""
78
+ settings = state_manager.session.user_config.get("settings", {})
79
+ return settings.get(get_tutorial_completion_key(), False)
80
+
81
+
82
+ def get_tutorial_declined_key() -> str:
83
+ """Get the config key for storing tutorial declined status."""
84
+ return "tutorial_declined"
85
+
86
+
87
+ def mark_tutorial_declined(state_manager: StateManager) -> None:
88
+ """Mark the tutorial as declined in user config."""
89
+ if "settings" not in state_manager.session.user_config:
90
+ state_manager.session.user_config["settings"] = {}
91
+
92
+ state_manager.session.user_config["settings"][get_tutorial_declined_key()] = True
93
+
94
+ # Clear any existing progress since it was declined
95
+ if get_tutorial_progress_key() in state_manager.session.user_config["settings"]:
96
+ del state_manager.session.user_config["settings"][get_tutorial_progress_key()]
97
+
98
+
99
+ def is_tutorial_declined(state_manager: StateManager) -> bool:
100
+ """Check if the tutorial has been declined."""
101
+ settings = state_manager.session.user_config.get("settings", {})
102
+ return settings.get(get_tutorial_declined_key(), False)
103
+
104
+
105
+ def is_first_time_user(state_manager: StateManager) -> bool:
106
+ """Check if this is a first-time user based on installation date."""
107
+ from datetime import datetime, timedelta
108
+
109
+ settings = state_manager.session.user_config.get("settings", {})
110
+ installation_date_str = settings.get("first_installation_date")
111
+
112
+ if not installation_date_str:
113
+ # No installation date means legacy user, treat as experienced
114
+ return False
115
+
116
+ try:
117
+ installation_date = datetime.fromisoformat(installation_date_str)
118
+ now = datetime.now()
119
+
120
+ # Consider first-time if installed within last 7 days
121
+ return (now - installation_date) <= timedelta(days=7)
122
+ except (ValueError, TypeError):
123
+ # Invalid date format, treat as experienced user
124
+ return False
tunacode/types.py CHANGED
@@ -6,7 +6,9 @@ used throughout the TunaCode codebase.
6
6
  """
7
7
 
8
8
  from dataclasses import dataclass, field
9
- from datetime import datetime
9
+
10
+ # Plan types will be defined below
11
+ from enum import Enum
10
12
  from pathlib import Path
11
13
  from typing import (
12
14
  Any,
@@ -14,39 +16,20 @@ from typing import (
14
16
  Callable,
15
17
  Dict,
16
18
  List,
17
- Literal,
18
19
  Optional,
19
20
  Protocol,
20
21
  Tuple,
21
22
  Union,
22
23
  )
23
24
 
24
- # Try to import pydantic-ai types if available
25
- try:
26
- from pydantic_ai import Agent
27
- from pydantic_ai.messages import ModelRequest, ToolReturnPart
28
-
29
- PydanticAgent = Agent
30
- MessagePart = Union[ToolReturnPart, Any]
31
- ModelRequest = ModelRequest # type: ignore[misc]
32
- ModelResponse = Any
33
- except ImportError:
34
- # Fallback if pydantic-ai is not available
35
- PydanticAgent = Any
36
- MessagePart = Any # type: ignore[misc]
37
- ModelRequest = Any
38
- ModelResponse = Any # type: ignore[misc]
25
+ # Import pydantic-ai types (required dependency)
26
+ from pydantic_ai import Agent
27
+ from pydantic_ai.messages import ModelRequest, ToolReturnPart
39
28
 
40
-
41
- @dataclass
42
- class TodoItem:
43
- id: str
44
- content: str
45
- status: Literal["pending", "in_progress", "completed"]
46
- priority: Literal["high", "medium", "low"]
47
- created_at: datetime
48
- completed_at: Optional[datetime] = None
49
- tags: list[str] = field(default_factory=list)
29
+ PydanticAgent = Agent
30
+ MessagePart = Union[ToolReturnPart, Any]
31
+ ModelRequest = ModelRequest # type: ignore[misc]
32
+ ModelResponse = Any
50
33
 
51
34
 
52
35
  # =============================================================================
@@ -126,6 +109,7 @@ class ToolConfirmationResponse:
126
109
  approved: bool
127
110
  skip_future: bool = False
128
111
  abort: bool = False
112
+ instructions: str = ""
129
113
 
130
114
 
131
115
  # =============================================================================
@@ -192,6 +176,15 @@ class SimpleResult:
192
176
  output: str
193
177
 
194
178
 
179
+ class AgentState(Enum):
180
+ """Agent loop states for enhanced completion detection."""
181
+
182
+ USER_INPUT = "user_input" # Initial: user prompt received
183
+ ASSISTANT = "assistant" # Reasoning/deciding phase
184
+ TOOL_EXECUTION = "tool_execution" # Tool execution phase
185
+ RESPONSE = "response" # Handling results, may complete or loop
186
+
187
+
195
188
  # =============================================================================
196
189
  # Session and State Types
197
190
  # =============================================================================