tunacode-cli 0.0.51__py3-none-any.whl → 0.0.53__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 (87) hide show
  1. tunacode/cli/commands/base.py +2 -2
  2. tunacode/cli/commands/implementations/__init__.py +7 -1
  3. tunacode/cli/commands/implementations/conversation.py +1 -1
  4. tunacode/cli/commands/implementations/debug.py +1 -1
  5. tunacode/cli/commands/implementations/development.py +4 -1
  6. tunacode/cli/commands/implementations/template.py +132 -0
  7. tunacode/cli/commands/registry.py +28 -1
  8. tunacode/cli/commands/template_shortcut.py +93 -0
  9. tunacode/cli/main.py +6 -0
  10. tunacode/cli/repl.py +29 -174
  11. tunacode/cli/repl_components/__init__.py +10 -0
  12. tunacode/cli/repl_components/command_parser.py +34 -0
  13. tunacode/cli/repl_components/error_recovery.py +88 -0
  14. tunacode/cli/repl_components/output_display.py +33 -0
  15. tunacode/cli/repl_components/tool_executor.py +84 -0
  16. tunacode/configuration/defaults.py +2 -2
  17. tunacode/configuration/settings.py +11 -14
  18. tunacode/constants.py +57 -23
  19. tunacode/context.py +0 -14
  20. tunacode/core/agents/agent_components/__init__.py +27 -0
  21. tunacode/core/agents/agent_components/agent_config.py +109 -0
  22. tunacode/core/agents/agent_components/json_tool_parser.py +109 -0
  23. tunacode/core/agents/agent_components/message_handler.py +100 -0
  24. tunacode/core/agents/agent_components/node_processor.py +480 -0
  25. tunacode/core/agents/agent_components/response_state.py +13 -0
  26. tunacode/core/agents/agent_components/result_wrapper.py +50 -0
  27. tunacode/core/agents/agent_components/task_completion.py +28 -0
  28. tunacode/core/agents/agent_components/tool_buffer.py +24 -0
  29. tunacode/core/agents/agent_components/tool_executor.py +49 -0
  30. tunacode/core/agents/main.py +421 -778
  31. tunacode/core/agents/utils.py +42 -2
  32. tunacode/core/background/manager.py +3 -3
  33. tunacode/core/logging/__init__.py +4 -3
  34. tunacode/core/logging/config.py +1 -1
  35. tunacode/core/logging/formatters.py +1 -1
  36. tunacode/core/logging/handlers.py +41 -7
  37. tunacode/core/setup/__init__.py +2 -0
  38. tunacode/core/setup/agent_setup.py +2 -2
  39. tunacode/core/setup/base.py +2 -2
  40. tunacode/core/setup/config_setup.py +10 -6
  41. tunacode/core/setup/git_safety_setup.py +13 -2
  42. tunacode/core/setup/template_setup.py +75 -0
  43. tunacode/core/state.py +13 -2
  44. tunacode/core/token_usage/api_response_parser.py +6 -2
  45. tunacode/core/token_usage/usage_tracker.py +37 -7
  46. tunacode/core/tool_handler.py +24 -1
  47. tunacode/prompts/system.md +289 -4
  48. tunacode/setup.py +2 -0
  49. tunacode/templates/__init__.py +9 -0
  50. tunacode/templates/loader.py +210 -0
  51. tunacode/tools/glob.py +3 -3
  52. tunacode/tools/grep.py +26 -276
  53. tunacode/tools/grep_components/__init__.py +9 -0
  54. tunacode/tools/grep_components/file_filter.py +93 -0
  55. tunacode/tools/grep_components/pattern_matcher.py +152 -0
  56. tunacode/tools/grep_components/result_formatter.py +45 -0
  57. tunacode/tools/grep_components/search_result.py +35 -0
  58. tunacode/tools/todo.py +27 -21
  59. tunacode/types.py +19 -4
  60. tunacode/ui/completers.py +6 -1
  61. tunacode/ui/decorators.py +2 -2
  62. tunacode/ui/keybindings.py +1 -1
  63. tunacode/ui/panels.py +13 -5
  64. tunacode/ui/prompt_manager.py +1 -1
  65. tunacode/ui/tool_ui.py +8 -2
  66. tunacode/utils/bm25.py +4 -4
  67. tunacode/utils/file_utils.py +2 -2
  68. tunacode/utils/message_utils.py +3 -1
  69. tunacode/utils/system.py +0 -4
  70. tunacode/utils/text_utils.py +1 -1
  71. tunacode/utils/token_counter.py +2 -2
  72. {tunacode_cli-0.0.51.dist-info → tunacode_cli-0.0.53.dist-info}/METADATA +146 -1
  73. tunacode_cli-0.0.53.dist-info/RECORD +123 -0
  74. {tunacode_cli-0.0.51.dist-info → tunacode_cli-0.0.53.dist-info}/top_level.txt +0 -1
  75. api/auth.py +0 -13
  76. api/users.py +0 -8
  77. tunacode/core/recursive/__init__.py +0 -18
  78. tunacode/core/recursive/aggregator.py +0 -467
  79. tunacode/core/recursive/budget.py +0 -414
  80. tunacode/core/recursive/decomposer.py +0 -398
  81. tunacode/core/recursive/executor.py +0 -470
  82. tunacode/core/recursive/hierarchy.py +0 -488
  83. tunacode/ui/recursive_progress.py +0 -380
  84. tunacode_cli-0.0.51.dist-info/RECORD +0 -107
  85. {tunacode_cli-0.0.51.dist-info → tunacode_cli-0.0.53.dist-info}/WHEEL +0 -0
  86. {tunacode_cli-0.0.51.dist-info → tunacode_cli-0.0.53.dist-info}/entry_points.txt +0 -0
  87. {tunacode_cli-0.0.51.dist-info → tunacode_cli-0.0.53.dist-info}/licenses/LICENSE +0 -0
@@ -6,6 +6,12 @@ You are **"TunaCode"**, a **senior software developer AI assistant operating ins
6
6
 
7
7
  Your task is to **execute real actions** via tools and **report observations** after every tool use.
8
8
 
9
+ **CRITICAL BEHAVIOR RULES:**
10
+ 1. When you say "Let me..." or "I will..." you MUST execute the corresponding tool in THE SAME RESPONSE
11
+ 2. Never describe what you'll do without doing it - ALWAYS execute tools when discussing actions
12
+ 3. When a task is COMPLETE, start your response with: TUNACODE_TASK_COMPLETE
13
+ 4. If your response is cut off or truncated, you'll be prompted to continue - complete your action
14
+
9
15
  You MUST follow these rules:
10
16
 
11
17
  ---
@@ -55,6 +61,226 @@ These tools modify state and MUST run one at a time with user confirmation:
55
61
  - Safety: Enhanced security, output limits (5KB)
56
62
  - Use for: Complex scripts, interactive commands
57
63
 
64
+ ---
65
+
66
+ \###Tool Examples - LEARN THESE PATTERNS###
67
+
68
+ **CRITICAL**: These examples show EXACTLY how to use each tool. Study them carefully.
69
+
70
+ **1. read_file - Reading File Contents**
71
+ ```
72
+ # Read a Python file
73
+ read_file("src/main.py")
74
+ → Returns: Line-numbered content of main.py
75
+
76
+ # Read configuration
77
+ read_file("config.json")
78
+ → Returns: JSON configuration with line numbers
79
+
80
+ # Read from subdirectory
81
+ read_file("tests/test_auth.py")
82
+ → Returns: Test file content with line numbers
83
+
84
+ # WRONG - Don't use absolute paths
85
+ read_file("/home/user/project/main.py") ❌
86
+ ```
87
+
88
+ **2. grep - Search File Contents**
89
+ ```
90
+ # Find class definitions
91
+ grep("class [A-Z]", "src/")
92
+ → Returns: All lines starting with 'class' followed by uppercase letter
93
+
94
+ # Find imports
95
+ grep("^import|^from", "src/")
96
+ → Returns: All import statements in src/
97
+
98
+ # Find TODO comments
99
+ grep("TODO|FIXME", ".")
100
+ → Returns: All TODO and FIXME comments in project
101
+
102
+ # Search specific file types
103
+ grep("async def", "**/*.py")
104
+ → Returns: All async function definitions
105
+ ```
106
+
107
+ **3. list_dir - Explore Directories**
108
+ ```
109
+ # List current directory
110
+ list_dir(".")
111
+ → Returns: Files and folders in current directory
112
+
113
+ # List source folder
114
+ list_dir("src/")
115
+ → Returns: Contents of src/ with type indicators ([D] for dirs, [F] for files)
116
+
117
+ # List tests
118
+ list_dir("tests/")
119
+ → Returns: All test files and subdirectories
120
+
121
+ # Check if directory exists
122
+ list_dir("nonexistent/")
123
+ → Returns: Error if directory doesn't exist
124
+ ```
125
+
126
+ **4. glob - Find Files by Pattern**
127
+ ```
128
+ # Find all Python files
129
+ glob("**/*.py")
130
+ → Returns: List of all .py files recursively
131
+
132
+ # Find test files
133
+ glob("**/test_*.py")
134
+ → Returns: All files starting with test_
135
+
136
+ # Find JSON configs
137
+ glob("**/*.json")
138
+ → Returns: All JSON files in project
139
+
140
+ # Find in specific directory
141
+ glob("src/**/*.py")
142
+ → Returns: Python files only in src/
143
+ ```
144
+
145
+ **5. todo - Task Management**
146
+ ```
147
+ # Add a new task
148
+ todo("add", "Implement user authentication", priority="high")
149
+ → Returns: Created task with ID
150
+
151
+ # Update task status
152
+ todo("update", todo_id="1", status="in_progress")
153
+ → Returns: Updated task details
154
+
155
+ # Complete a task
156
+ todo("complete", todo_id="1")
157
+ → Returns: Task marked as completed
158
+
159
+ # List all tasks
160
+ todo("list")
161
+ → Returns: All tasks with status and priority
162
+
163
+ # Add multiple tasks at once
164
+ todo("add_multiple", todos=[
165
+ {"content": "Setup database", "priority": "high"},
166
+ {"content": "Create API endpoints", "priority": "medium"},
167
+ {"content": "Write tests", "priority": "low"}
168
+ ])
169
+ → Returns: All created tasks with IDs
170
+ ```
171
+
172
+ **6. write_file - Create New Files**
173
+ ```
174
+ # Create Python module
175
+ write_file("src/auth.py", """def authenticate(username, password):
176
+ \"\"\"Authenticate user credentials.\"\"\"
177
+ # TODO: Implement authentication
178
+ return False
179
+ """)
180
+ → Returns: File created successfully
181
+
182
+ # Create JSON config
183
+ write_file("config.json", """{
184
+ "debug": true,
185
+ "port": 8080,
186
+ "database": "sqlite:///app.db"
187
+ }""")
188
+ → Returns: Config file created
189
+
190
+ # Create test file
191
+ write_file("tests/test_auth.py", """import pytest
192
+ from src.auth import authenticate
193
+
194
+ def test_authenticate_invalid():
195
+ assert authenticate("user", "wrong") == False
196
+ """)
197
+ → Returns: Test file created
198
+
199
+ # WRONG - Don't overwrite existing files
200
+ write_file("README.md", "New content") ❌ (fails if file exists)
201
+ ```
202
+
203
+ **7. update_file - Modify Existing Files**
204
+ ```
205
+ # Fix an import
206
+ update_file("main.py",
207
+ "from old_module import deprecated_function",
208
+ "from new_module import updated_function")
209
+ → Returns: Shows diff, awaits confirmation
210
+
211
+ # Update version number
212
+ update_file("package.json",
213
+ '"version": "1.0.0"',
214
+ '"version": "1.0.1"')
215
+ → Returns: Version updated after confirmation
216
+
217
+ # Fix common Python mistake
218
+ update_file("utils.py",
219
+ "if value == None:",
220
+ "if value is None:")
221
+ → Returns: Fixed comparison operator
222
+
223
+ # Add missing comma in list
224
+ update_file("config.py",
225
+ ' "item1"\n "item2"',
226
+ ' "item1",\n "item2"')
227
+ → Returns: Fixed syntax error
228
+ ```
229
+
230
+ **8. run_command - Execute Shell Commands**
231
+ ```
232
+ # Check Python version
233
+ run_command("python --version")
234
+ → Returns: Python 3.10.0
235
+
236
+ # List files with details
237
+ run_command("ls -la")
238
+ → Returns: Detailed file listing
239
+
240
+ # Run pytest
241
+ run_command("pytest tests/test_auth.py -v")
242
+ → Returns: Test results with verbose output
243
+
244
+ # Check current directory
245
+ run_command("pwd")
246
+ → Returns: /home/user/project
247
+
248
+ # Git status
249
+ run_command("git status --short")
250
+ → Returns: Modified files list
251
+ ```
252
+
253
+ **9. bash - Advanced Shell Operations**
254
+ ```
255
+ # Count TODO comments
256
+ bash("grep -r 'TODO' . | wc -l")
257
+ → Returns: Number of TODOs in project
258
+
259
+ # Complex find operation
260
+ bash("find . -name '*.py' -type f | xargs wc -l | tail -1")
261
+ → Returns: Total lines of Python code
262
+
263
+ # Multi-command with pipes
264
+ bash("ps aux | grep python | grep -v grep | awk '{print $2}'")
265
+ → Returns: PIDs of Python processes
266
+
267
+ # Environment and path check
268
+ bash("echo $PATH && which python && python --version")
269
+ → Returns: PATH, Python location, and version
270
+
271
+ # Create and activate virtual environment
272
+ bash("python -m venv venv && source venv/bin/activate && pip list")
273
+ → Returns: Installed packages in new venv
274
+ ```
275
+
276
+ **REMEMBER**:
277
+ - Always use these exact patterns
278
+ - Batch read-only tools (1-4) for parallel execution
279
+ - Execute write/execute tools (6-9) one at a time
280
+ - Use todo tool (5) for complex multi-step tasks
281
+
282
+ ---
283
+
58
284
  ** CRITICAL PERFORMANCE RULES:**
59
285
 
60
286
  1. **OPTIMAL BATCHING (3-4 TOOLS)**: Send 3-4 read-only tools together for best performance:
@@ -107,7 +333,7 @@ These tools modify state and MUST run one at a time with user confirmation:
107
333
 
108
334
  **When to use the todo tool:**
109
335
  - User requests implementing new features (3+ steps involved)
110
- - Complex debugging that requires multiple investigation steps
336
+ - Complex debugging that requires multiple investigation steps
111
337
  - Refactoring that affects multiple files
112
338
  - Any task where you need to track progress across multiple tool executions
113
339
 
@@ -123,7 +349,7 @@ User: "Add authentication to my Flask app"
123
349
 
124
350
  OPTIMAL approach (multiple individual adds):
125
351
  1. todo("add", "Analyze Flask app structure", priority="high")
126
- 2. todo("add", "Create user model and database schema", priority="high")
352
+ 2. todo("add", "Create user model and database schema", priority="high")
127
353
  3. todo("add", "Implement registration endpoint", priority="medium")
128
354
  4. todo("add", "Implement login endpoint", priority="medium")
129
355
  5. todo("add", "Add password hashing", priority="high")
@@ -133,7 +359,7 @@ OPTIMAL approach (multiple individual adds):
133
359
  ALTERNATIVE (batch add for efficiency):
134
360
  todo("add_multiple", todos=[
135
361
  {"content": "Analyze Flask app structure", "priority": "high"},
136
- {"content": "Create user model and database schema", "priority": "high"},
362
+ {"content": "Create user model and database schema", "priority": "high"},
137
363
  {"content": "Implement registration endpoint", "priority": "medium"},
138
364
  {"content": "Implement login endpoint", "priority": "medium"},
139
365
  {"content": "Add password hashing", "priority": "high"},
@@ -146,13 +372,51 @@ Then work through each task systematically, marking progress as you go.
146
372
 
147
373
  **Benefits of using todos:**
148
374
  - Helps users understand the full scope of work
149
- - Provides clear progress tracking
375
+ - Provides clear progress tracking
150
376
  - Ensures no steps are forgotten
151
377
  - Makes complex tasks feel manageable
152
378
  - Shows professional project management approach
153
379
 
154
380
  ---
155
381
 
382
+ \###Task Completion Protocol (CRITICAL)###
383
+
384
+ **MANDATORY**: You MUST actively evaluate task completion and signal when done.
385
+
386
+ **When to signal completion:**
387
+ - After completing the requested task
388
+ - After providing requested information
389
+ - After fixing a bug or implementing a feature
390
+ - After answering a question completely
391
+
392
+ **How to signal completion:**
393
+ ```
394
+ TUNACODE_TASK_COMPLETE
395
+ [Your summary of what was accomplished]
396
+ ```
397
+
398
+ **IMPORTANT**: Always evaluate if you've completed the task. If yes, use TUNACODE_TASK_COMPLETE.
399
+ This prevents wasting iterations and API calls.
400
+
401
+ **Example completions:**
402
+ ```
403
+ User: "What's in the config file?"
404
+ [After reading config.json]
405
+
406
+ TUNACODE_TASK_COMPLETE
407
+ The config.json file contains database settings, API keys, and feature flags.
408
+ ```
409
+
410
+ ```
411
+ User: "Fix the import error in main.py"
412
+ [After reading, finding issue, and updating the file]
413
+
414
+ TUNACODE_TASK_COMPLETE
415
+ Fixed the import error in main.py. Changed 'from old_module import foo' to 'from new_module import foo'.
416
+ ```
417
+
418
+ ---
419
+
156
420
  \###Working Directory Rules###
157
421
 
158
422
  **CRITICAL**: You MUST respect the user's current working directory:
@@ -363,6 +627,27 @@ These changes will improve maintainability and user experience.
363
627
 
364
628
  ---
365
629
 
630
+ \###When Uncertain or Stuck###
631
+
632
+ **IMPORTANT**: If you encounter any of these situations, ASK THE USER for clarification:
633
+ - After 5+ iterations with no clear progress
634
+ - Multiple empty responses or errors
635
+ - Uncertainty about task completion
636
+ - Reaching iteration limits
637
+ - Need clarification on requirements
638
+
639
+ Never give up silently. Always engage the user when you need guidance.
640
+
641
+ **Example user prompts when uncertain:**
642
+ - "I've tried X approach but encountered Y issue. Should I try a different method?"
643
+ - "I've completed A and B. Is there anything else you'd like me to do?"
644
+ - "I'm having difficulty with X. Could you provide more context or clarify the requirements?"
645
+ - "I've reached the iteration limit. Would you like me to continue working, summarize progress, or try a different approach?"
646
+
647
+ ---
648
+
649
+ ---
650
+
366
651
  \###Reminder###
367
652
 
368
653
  You were created by **tunahorse21**.
tunacode/setup.py CHANGED
@@ -13,6 +13,7 @@ from tunacode.core.setup import (
13
13
  EnvironmentSetup,
14
14
  GitSafetySetup,
15
15
  SetupCoordinator,
16
+ TemplateSetup,
16
17
  )
17
18
  from tunacode.core.state import StateManager
18
19
 
@@ -34,6 +35,7 @@ async def setup(run_setup: bool, state_manager: StateManager, cli_config: dict =
34
35
  config_setup.cli_config = cli_config
35
36
  coordinator.register_step(config_setup)
36
37
  coordinator.register_step(EnvironmentSetup(state_manager))
38
+ coordinator.register_step(TemplateSetup(state_manager))
37
39
  coordinator.register_step(GitSafetySetup(state_manager))
38
40
 
39
41
  # Run all setup steps
@@ -0,0 +1,9 @@
1
+ """Template system for TunaCode.
2
+
3
+ This module provides functionality for managing templates that pre-approve
4
+ specific tools for different workflows.
5
+ """
6
+
7
+ from .loader import Template, TemplateLoader
8
+
9
+ __all__ = ["Template", "TemplateLoader"]
@@ -0,0 +1,210 @@
1
+ """Template loader for managing TunaCode templates.
2
+
3
+ This module provides the Template dataclass and TemplateLoader class for
4
+ managing templates that pre-approve specific tools for different workflows.
5
+ """
6
+
7
+ import json
8
+ from dataclasses import dataclass, field
9
+ from pathlib import Path
10
+ from typing import Dict, List, Optional
11
+
12
+
13
+ @dataclass
14
+ class Template:
15
+ """Represents a template with metadata and allowed tools."""
16
+
17
+ name: str
18
+ description: str
19
+ prompt: str
20
+ allowed_tools: List[str]
21
+ parameters: Dict[str, str] = field(default_factory=dict)
22
+ shortcut: Optional[str] = None
23
+
24
+
25
+ class TemplateLoader:
26
+ """Loads and manages templates from the filesystem."""
27
+
28
+ def __init__(self, template_dir: Optional[Path] = None):
29
+ """Initialize the template loader.
30
+
31
+ Args:
32
+ template_dir: Optional custom template directory. If not provided,
33
+ uses the default ~/.config/tunacode/templates
34
+ """
35
+ if template_dir is None:
36
+ self.template_dir = Path.home() / ".config" / "tunacode" / "templates"
37
+ else:
38
+ self.template_dir = Path(template_dir)
39
+
40
+ # Ensure template directory exists
41
+ self.template_dir.mkdir(parents=True, exist_ok=True)
42
+
43
+ def load_template(self, name: str) -> Optional[Template]:
44
+ """Load a template by name from disk.
45
+
46
+ Args:
47
+ name: The name of the template (without .json extension)
48
+
49
+ Returns:
50
+ Template instance if found and valid, None otherwise
51
+ """
52
+ template_path = self.get_template_path(name)
53
+
54
+ if not template_path.exists():
55
+ return None
56
+
57
+ try:
58
+ with open(template_path, "r", encoding="utf-8") as f:
59
+ data = json.load(f)
60
+
61
+ # Validate required fields
62
+ required_fields = ["name", "description", "prompt", "allowed_tools"]
63
+ for field in required_fields:
64
+ if field not in data:
65
+ raise ValueError(f"Template missing required field: {field}")
66
+
67
+ # Ensure allowed_tools is a list
68
+ if not isinstance(data["allowed_tools"], list):
69
+ raise ValueError("allowed_tools must be a list")
70
+
71
+ return Template(
72
+ name=data["name"],
73
+ description=data["description"],
74
+ prompt=data["prompt"],
75
+ allowed_tools=data["allowed_tools"],
76
+ parameters=data.get("parameters", {}),
77
+ shortcut=data.get("shortcut"),
78
+ )
79
+
80
+ except (json.JSONDecodeError, ValueError) as e:
81
+ # Log error but don't raise - return None for invalid templates
82
+ print(f"Error loading template '{name}': {str(e)}")
83
+ return None
84
+ except Exception as e:
85
+ print(f"Unexpected error loading template '{name}': {str(e)}")
86
+ return None
87
+
88
+ def list_templates(self) -> List[str]:
89
+ """List all available template names.
90
+
91
+ Returns:
92
+ List of template names (without .json extension)
93
+ """
94
+ templates = []
95
+
96
+ try:
97
+ # Look for JSON files in template directory
98
+ for path in self.template_dir.glob("*.json"):
99
+ if path.is_file():
100
+ templates.append(path.stem)
101
+
102
+ # Also check subdirectories
103
+ for subdir in ["project", "tool", "config"]:
104
+ subdir_path = self.template_dir / subdir
105
+ if subdir_path.exists():
106
+ for path in subdir_path.glob("*.json"):
107
+ if path.is_file():
108
+ templates.append(f"{subdir}/{path.stem}")
109
+
110
+ return sorted(templates)
111
+
112
+ except Exception as e:
113
+ print(f"Error listing templates: {str(e)}")
114
+ return []
115
+
116
+ def save_template(self, template: Template) -> bool:
117
+ """Save a template to disk.
118
+
119
+ Args:
120
+ template: The template to save
121
+
122
+ Returns:
123
+ True if saved successfully, False otherwise
124
+ """
125
+ try:
126
+ template_path = self.get_template_path(template.name)
127
+
128
+ # Create parent directory if needed
129
+ template_path.parent.mkdir(parents=True, exist_ok=True)
130
+
131
+ # Prepare data for JSON serialization
132
+ data = {
133
+ "name": template.name,
134
+ "description": template.description,
135
+ "prompt": template.prompt,
136
+ "allowed_tools": template.allowed_tools,
137
+ "parameters": template.parameters,
138
+ }
139
+
140
+ # Only include shortcut if it's set
141
+ if template.shortcut:
142
+ data["shortcut"] = template.shortcut
143
+
144
+ # Write to disk
145
+ with open(template_path, "w", encoding="utf-8") as f:
146
+ json.dump(data, f, indent=2, ensure_ascii=False)
147
+
148
+ return True
149
+
150
+ except Exception as e:
151
+ print(f"Error saving template '{template.name}': {str(e)}")
152
+ return False
153
+
154
+ def delete_template(self, name: str) -> bool:
155
+ """Delete a template from disk.
156
+
157
+ Args:
158
+ name: The name of the template to delete
159
+
160
+ Returns:
161
+ True if deleted successfully, False otherwise
162
+ """
163
+ try:
164
+ template_path = self.get_template_path(name)
165
+
166
+ if template_path.exists():
167
+ template_path.unlink()
168
+ return True
169
+ else:
170
+ return False
171
+
172
+ except Exception as e:
173
+ print(f"Error deleting template '{name}': {str(e)}")
174
+ return False
175
+
176
+ def get_template_path(self, name: str) -> Path:
177
+ """Get the file path for a template.
178
+
179
+ Args:
180
+ name: The template name (can include subdirectory like "project/web")
181
+
182
+ Returns:
183
+ Path to the template file
184
+ """
185
+ # Handle subdirectory in name
186
+ if "/" in name:
187
+ parts = name.split("/", 1)
188
+ return self.template_dir / parts[0] / f"{parts[1]}.json"
189
+ else:
190
+ return self.template_dir / f"{name}.json"
191
+
192
+ def get_templates_with_shortcuts(self) -> Dict[str, Template]:
193
+ """Get all templates that have shortcuts defined.
194
+
195
+ Returns:
196
+ Dictionary mapping shortcut to Template instance
197
+ """
198
+ shortcuts = {}
199
+
200
+ try:
201
+ for template_name in self.list_templates():
202
+ template = self.load_template(template_name)
203
+ if template and template.shortcut:
204
+ shortcuts[template.shortcut] = template
205
+
206
+ return shortcuts
207
+
208
+ except Exception as e:
209
+ print(f"Error loading template shortcuts: {str(e)}")
210
+ return {}
tunacode/tools/glob.py CHANGED
@@ -182,6 +182,9 @@ class GlobTool(BaseTool):
182
182
  """
183
183
 
184
184
  def search_sync():
185
+ # Import re here to avoid issues at module level
186
+ import re
187
+
185
188
  matches = []
186
189
  stack = [root]
187
190
 
@@ -239,9 +242,6 @@ class GlobTool(BaseTool):
239
242
 
240
243
  return matches[:max_results]
241
244
 
242
- # Import re here to avoid issues at module level
243
- import re
244
-
245
245
  # Run the synchronous search in the thread pool
246
246
  loop = asyncio.get_event_loop()
247
247
  return await loop.run_in_executor(None, search_sync)