tunacode-cli 0.0.50__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.
- tunacode/cli/commands/base.py +2 -2
- tunacode/cli/commands/implementations/__init__.py +7 -1
- tunacode/cli/commands/implementations/conversation.py +1 -1
- tunacode/cli/commands/implementations/debug.py +1 -1
- tunacode/cli/commands/implementations/development.py +4 -1
- tunacode/cli/commands/implementations/template.py +132 -0
- tunacode/cli/commands/registry.py +28 -1
- tunacode/cli/commands/template_shortcut.py +93 -0
- tunacode/cli/main.py +6 -0
- tunacode/cli/repl.py +29 -174
- tunacode/cli/repl_components/__init__.py +10 -0
- tunacode/cli/repl_components/command_parser.py +34 -0
- tunacode/cli/repl_components/error_recovery.py +88 -0
- tunacode/cli/repl_components/output_display.py +33 -0
- tunacode/cli/repl_components/tool_executor.py +84 -0
- tunacode/configuration/defaults.py +2 -2
- tunacode/configuration/settings.py +11 -14
- tunacode/constants.py +57 -23
- tunacode/context.py +0 -14
- tunacode/core/agents/agent_components/__init__.py +27 -0
- tunacode/core/agents/agent_components/agent_config.py +109 -0
- tunacode/core/agents/agent_components/json_tool_parser.py +109 -0
- tunacode/core/agents/agent_components/message_handler.py +100 -0
- tunacode/core/agents/agent_components/node_processor.py +480 -0
- tunacode/core/agents/agent_components/response_state.py +13 -0
- tunacode/core/agents/agent_components/result_wrapper.py +50 -0
- tunacode/core/agents/agent_components/task_completion.py +28 -0
- tunacode/core/agents/agent_components/tool_buffer.py +24 -0
- tunacode/core/agents/agent_components/tool_executor.py +49 -0
- tunacode/core/agents/main.py +421 -778
- tunacode/core/agents/utils.py +42 -2
- tunacode/core/background/manager.py +3 -3
- tunacode/core/logging/__init__.py +4 -3
- tunacode/core/logging/config.py +29 -16
- tunacode/core/logging/formatters.py +1 -1
- tunacode/core/logging/handlers.py +41 -7
- tunacode/core/setup/__init__.py +2 -0
- tunacode/core/setup/agent_setup.py +2 -2
- tunacode/core/setup/base.py +2 -2
- tunacode/core/setup/config_setup.py +10 -6
- tunacode/core/setup/git_safety_setup.py +13 -2
- tunacode/core/setup/template_setup.py +75 -0
- tunacode/core/state.py +13 -2
- tunacode/core/token_usage/api_response_parser.py +6 -2
- tunacode/core/token_usage/usage_tracker.py +37 -7
- tunacode/core/tool_handler.py +24 -1
- tunacode/prompts/system.md +289 -4
- tunacode/setup.py +2 -0
- tunacode/templates/__init__.py +9 -0
- tunacode/templates/loader.py +210 -0
- tunacode/tools/glob.py +3 -3
- tunacode/tools/grep.py +26 -276
- tunacode/tools/grep_components/__init__.py +9 -0
- tunacode/tools/grep_components/file_filter.py +93 -0
- tunacode/tools/grep_components/pattern_matcher.py +152 -0
- tunacode/tools/grep_components/result_formatter.py +45 -0
- tunacode/tools/grep_components/search_result.py +35 -0
- tunacode/tools/todo.py +27 -21
- tunacode/types.py +19 -4
- tunacode/ui/completers.py +6 -1
- tunacode/ui/decorators.py +2 -2
- tunacode/ui/keybindings.py +1 -1
- tunacode/ui/panels.py +13 -5
- tunacode/ui/prompt_manager.py +1 -1
- tunacode/ui/tool_ui.py +8 -2
- tunacode/utils/bm25.py +4 -4
- tunacode/utils/file_utils.py +2 -2
- tunacode/utils/message_utils.py +3 -1
- tunacode/utils/system.py +0 -4
- tunacode/utils/text_utils.py +1 -1
- tunacode/utils/token_counter.py +2 -2
- {tunacode_cli-0.0.50.dist-info → tunacode_cli-0.0.53.dist-info}/METADATA +146 -1
- tunacode_cli-0.0.53.dist-info/RECORD +123 -0
- {tunacode_cli-0.0.50.dist-info → tunacode_cli-0.0.53.dist-info}/top_level.txt +0 -1
- api/auth.py +0 -13
- api/users.py +0 -8
- tunacode/core/recursive/__init__.py +0 -18
- tunacode/core/recursive/aggregator.py +0 -467
- tunacode/core/recursive/budget.py +0 -414
- tunacode/core/recursive/decomposer.py +0 -398
- tunacode/core/recursive/executor.py +0 -470
- tunacode/core/recursive/hierarchy.py +0 -488
- tunacode/ui/recursive_progress.py +0 -380
- tunacode_cli-0.0.50.dist-info/RECORD +0 -107
- {tunacode_cli-0.0.50.dist-info → tunacode_cli-0.0.53.dist-info}/WHEEL +0 -0
- {tunacode_cli-0.0.50.dist-info → tunacode_cli-0.0.53.dist-info}/entry_points.txt +0 -0
- {tunacode_cli-0.0.50.dist-info → tunacode_cli-0.0.53.dist-info}/licenses/LICENSE +0 -0
tunacode/prompts/system.md
CHANGED
|
@@ -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,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)
|