tunacode-cli 0.0.55__py3-none-any.whl → 0.0.56__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/implementations/plan.py +50 -0
- tunacode/cli/commands/registry.py +3 -0
- tunacode/cli/repl.py +335 -1
- tunacode/cli/repl_components/output_display.py +18 -1
- tunacode/cli/repl_components/tool_executor.py +9 -0
- tunacode/constants.py +4 -2
- tunacode/core/agents/agent_components/agent_config.py +134 -7
- tunacode/core/agents/agent_components/node_processor.py +44 -42
- tunacode/core/state.py +44 -0
- tunacode/core/tool_handler.py +20 -0
- tunacode/tools/exit_plan_mode.py +191 -0
- tunacode/tools/present_plan.py +208 -0
- tunacode/types.py +57 -0
- tunacode/ui/input.py +13 -2
- tunacode/ui/keybindings.py +24 -4
- tunacode/ui/panels.py +23 -0
- tunacode/ui/prompt_manager.py +19 -2
- tunacode/ui/tool_ui.py +3 -2
- tunacode/utils/message_utils.py +14 -4
- {tunacode_cli-0.0.55.dist-info → tunacode_cli-0.0.56.dist-info}/METADATA +4 -3
- {tunacode_cli-0.0.55.dist-info → tunacode_cli-0.0.56.dist-info}/RECORD +25 -22
- {tunacode_cli-0.0.55.dist-info → tunacode_cli-0.0.56.dist-info}/WHEEL +0 -0
- {tunacode_cli-0.0.55.dist-info → tunacode_cli-0.0.56.dist-info}/entry_points.txt +0 -0
- {tunacode_cli-0.0.55.dist-info → tunacode_cli-0.0.56.dist-info}/licenses/LICENSE +0 -0
- {tunacode_cli-0.0.55.dist-info → tunacode_cli-0.0.56.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"""Tool for presenting a structured plan and exiting plan mode."""
|
|
2
|
+
|
|
3
|
+
from typing import List, Optional
|
|
4
|
+
|
|
5
|
+
from tunacode.tools.base import BaseTool
|
|
6
|
+
from tunacode.ui import console as ui
|
|
7
|
+
from tunacode.types import ToolResult, PlanDoc, PlanPhase
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class PresentPlanTool(BaseTool):
|
|
11
|
+
"""Present a structured implementation plan and request user approval."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, state_manager, ui_logger=None):
|
|
14
|
+
"""Initialize the present plan tool.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
state_manager: StateManager instance for controlling plan mode state
|
|
18
|
+
ui_logger: UI logger instance for displaying messages
|
|
19
|
+
"""
|
|
20
|
+
super().__init__(ui_logger)
|
|
21
|
+
self.state_manager = state_manager
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def tool_name(self) -> str:
|
|
25
|
+
return "present_plan"
|
|
26
|
+
|
|
27
|
+
async def _execute(
|
|
28
|
+
self,
|
|
29
|
+
title: str,
|
|
30
|
+
overview: str,
|
|
31
|
+
steps: List[str],
|
|
32
|
+
files_to_modify: List[str] = None,
|
|
33
|
+
files_to_create: List[str] = None,
|
|
34
|
+
risks: List[str] = None,
|
|
35
|
+
tests: List[str] = None,
|
|
36
|
+
rollback: Optional[str] = None,
|
|
37
|
+
open_questions: List[str] = None,
|
|
38
|
+
success_criteria: List[str] = None,
|
|
39
|
+
references: List[str] = None,
|
|
40
|
+
) -> ToolResult:
|
|
41
|
+
"""Present the implementation plan for user approval."""
|
|
42
|
+
|
|
43
|
+
# Create PlanDoc from parameters
|
|
44
|
+
plan_doc = PlanDoc(
|
|
45
|
+
title=title,
|
|
46
|
+
overview=overview,
|
|
47
|
+
steps=steps,
|
|
48
|
+
files_to_modify=files_to_modify or [],
|
|
49
|
+
files_to_create=files_to_create or [],
|
|
50
|
+
risks=risks or [],
|
|
51
|
+
tests=tests or [],
|
|
52
|
+
rollback=rollback,
|
|
53
|
+
open_questions=open_questions or [],
|
|
54
|
+
success_criteria=success_criteria or [],
|
|
55
|
+
references=references or []
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
# Validate the plan
|
|
59
|
+
is_valid, missing_sections = plan_doc.validate()
|
|
60
|
+
if not is_valid:
|
|
61
|
+
return f"❌ Plan incomplete. Missing sections: {', '.join(missing_sections)}. Continue researching and refining your plan."
|
|
62
|
+
|
|
63
|
+
# Set plan phase to PLAN_READY and store the plan
|
|
64
|
+
# The REPL will handle displaying the plan when it detects PLAN_READY phase
|
|
65
|
+
self.state_manager.session.plan_phase = PlanPhase.PLAN_READY
|
|
66
|
+
self.state_manager.session.current_plan = plan_doc
|
|
67
|
+
|
|
68
|
+
return "Plan ready for review. The system will now present it to the user for approval."
|
|
69
|
+
|
|
70
|
+
async def _present_plan(self, plan_doc: PlanDoc) -> None:
|
|
71
|
+
"""Present the plan in a formatted way."""
|
|
72
|
+
output = []
|
|
73
|
+
output.append("")
|
|
74
|
+
output.append("╭─────────────────────────────────────────────────────────╮")
|
|
75
|
+
output.append("│ 📋 IMPLEMENTATION PLAN │")
|
|
76
|
+
output.append("╰─────────────────────────────────────────────────────────╯")
|
|
77
|
+
output.append("")
|
|
78
|
+
output.append(f"🎯 **{plan_doc.title}**")
|
|
79
|
+
output.append("")
|
|
80
|
+
|
|
81
|
+
if plan_doc.overview:
|
|
82
|
+
output.append(f"📝 **Overview:** {plan_doc.overview}")
|
|
83
|
+
output.append("")
|
|
84
|
+
|
|
85
|
+
# Files section
|
|
86
|
+
if plan_doc.files_to_modify:
|
|
87
|
+
output.append("📝 **Files to Modify:**")
|
|
88
|
+
for f in plan_doc.files_to_modify:
|
|
89
|
+
output.append(f" • {f}")
|
|
90
|
+
output.append("")
|
|
91
|
+
|
|
92
|
+
if plan_doc.files_to_create:
|
|
93
|
+
output.append("📄 **Files to Create:**")
|
|
94
|
+
for f in plan_doc.files_to_create:
|
|
95
|
+
output.append(f" • {f}")
|
|
96
|
+
output.append("")
|
|
97
|
+
|
|
98
|
+
# Implementation steps
|
|
99
|
+
output.append("🔧 **Implementation Steps:**")
|
|
100
|
+
for i, step in enumerate(plan_doc.steps, 1):
|
|
101
|
+
output.append(f" {i}. {step}")
|
|
102
|
+
output.append("")
|
|
103
|
+
|
|
104
|
+
# Testing approach
|
|
105
|
+
if plan_doc.tests:
|
|
106
|
+
output.append("🧪 **Testing Approach:**")
|
|
107
|
+
for test in plan_doc.tests:
|
|
108
|
+
output.append(f" • {test}")
|
|
109
|
+
output.append("")
|
|
110
|
+
|
|
111
|
+
# Success criteria
|
|
112
|
+
if plan_doc.success_criteria:
|
|
113
|
+
output.append("✅ **Success Criteria:**")
|
|
114
|
+
for criteria in plan_doc.success_criteria:
|
|
115
|
+
output.append(f" • {criteria}")
|
|
116
|
+
output.append("")
|
|
117
|
+
|
|
118
|
+
# Risks and considerations
|
|
119
|
+
if plan_doc.risks:
|
|
120
|
+
output.append("⚠️ **Risks & Considerations:**")
|
|
121
|
+
for risk in plan_doc.risks:
|
|
122
|
+
output.append(f" • {risk}")
|
|
123
|
+
output.append("")
|
|
124
|
+
|
|
125
|
+
# Open questions
|
|
126
|
+
if plan_doc.open_questions:
|
|
127
|
+
output.append("❓ **Open Questions:**")
|
|
128
|
+
for question in plan_doc.open_questions:
|
|
129
|
+
output.append(f" • {question}")
|
|
130
|
+
output.append("")
|
|
131
|
+
|
|
132
|
+
# References
|
|
133
|
+
if plan_doc.references:
|
|
134
|
+
output.append("📚 **References:**")
|
|
135
|
+
for ref in plan_doc.references:
|
|
136
|
+
output.append(f" • {ref}")
|
|
137
|
+
output.append("")
|
|
138
|
+
|
|
139
|
+
# Rollback plan
|
|
140
|
+
if plan_doc.rollback:
|
|
141
|
+
output.append(f"🔄 **Rollback Plan:** {plan_doc.rollback}")
|
|
142
|
+
output.append("")
|
|
143
|
+
|
|
144
|
+
# Print everything at once
|
|
145
|
+
await ui.info("\n".join(output))
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def create_present_plan_tool(state_manager):
|
|
149
|
+
"""
|
|
150
|
+
Factory function to create present_plan tool with the correct state manager.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
state_manager: The StateManager instance to use
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
Callable: The present_plan function bound to the provided state manager
|
|
157
|
+
"""
|
|
158
|
+
async def present_plan(
|
|
159
|
+
title: str,
|
|
160
|
+
overview: str,
|
|
161
|
+
steps: List[str],
|
|
162
|
+
files_to_modify: List[str] = None,
|
|
163
|
+
files_to_create: List[str] = None,
|
|
164
|
+
risks: List[str] = None,
|
|
165
|
+
tests: List[str] = None,
|
|
166
|
+
rollback: Optional[str] = None,
|
|
167
|
+
open_questions: List[str] = None,
|
|
168
|
+
success_criteria: List[str] = None,
|
|
169
|
+
references: List[str] = None,
|
|
170
|
+
) -> str:
|
|
171
|
+
"""
|
|
172
|
+
Present a structured implementation plan for user approval.
|
|
173
|
+
|
|
174
|
+
This tool should ONLY be called when you have a complete, well-researched plan.
|
|
175
|
+
All required sections must be filled out before calling this tool.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
title: Brief, descriptive title for the implementation plan
|
|
179
|
+
overview: High-level summary of what needs to be implemented and why
|
|
180
|
+
steps: Ordered list of specific implementation steps (required)
|
|
181
|
+
files_to_modify: List of existing files that need to be modified
|
|
182
|
+
files_to_create: List of new files that need to be created
|
|
183
|
+
risks: Potential risks, challenges, or considerations
|
|
184
|
+
tests: Testing approach and test cases to validate implementation
|
|
185
|
+
rollback: Plan for reverting changes if needed
|
|
186
|
+
open_questions: Any remaining questions or uncertainties
|
|
187
|
+
success_criteria: Specific criteria for considering the task complete
|
|
188
|
+
references: External resources, documentation, or research sources
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
str: Status message about plan presentation
|
|
192
|
+
"""
|
|
193
|
+
tool = PresentPlanTool(state_manager=state_manager)
|
|
194
|
+
return await tool._execute(
|
|
195
|
+
title=title,
|
|
196
|
+
overview=overview,
|
|
197
|
+
steps=steps,
|
|
198
|
+
files_to_modify=files_to_modify,
|
|
199
|
+
files_to_create=files_to_create,
|
|
200
|
+
risks=risks,
|
|
201
|
+
tests=tests,
|
|
202
|
+
rollback=rollback,
|
|
203
|
+
open_questions=open_questions,
|
|
204
|
+
success_criteria=success_criteria,
|
|
205
|
+
references=references,
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
return present_plan
|
tunacode/types.py
CHANGED
|
@@ -21,6 +21,9 @@ from typing import (
|
|
|
21
21
|
Union,
|
|
22
22
|
)
|
|
23
23
|
|
|
24
|
+
# Plan types will be defined below
|
|
25
|
+
from enum import Enum
|
|
26
|
+
|
|
24
27
|
# Try to import pydantic-ai types if available
|
|
25
28
|
try:
|
|
26
29
|
from pydantic_ai import Agent
|
|
@@ -192,6 +195,60 @@ class SimpleResult:
|
|
|
192
195
|
output: str
|
|
193
196
|
|
|
194
197
|
|
|
198
|
+
# =============================================================================
|
|
199
|
+
# Plan Types
|
|
200
|
+
# =============================================================================
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
class PlanPhase(Enum):
|
|
204
|
+
"""Plan Mode phases."""
|
|
205
|
+
PLANNING_RESEARCH = "research"
|
|
206
|
+
PLANNING_DRAFT = "draft"
|
|
207
|
+
PLAN_READY = "ready"
|
|
208
|
+
REVIEW_DECISION = "review"
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
@dataclass
|
|
212
|
+
class PlanDoc:
|
|
213
|
+
"""Structured plan document with all required sections."""
|
|
214
|
+
|
|
215
|
+
# Required sections
|
|
216
|
+
title: str
|
|
217
|
+
overview: str
|
|
218
|
+
steps: List[str]
|
|
219
|
+
files_to_modify: List[str]
|
|
220
|
+
files_to_create: List[str]
|
|
221
|
+
|
|
222
|
+
# Optional but recommended sections
|
|
223
|
+
risks: List[str] = field(default_factory=list)
|
|
224
|
+
tests: List[str] = field(default_factory=list)
|
|
225
|
+
rollback: Optional[str] = None
|
|
226
|
+
open_questions: List[str] = field(default_factory=list)
|
|
227
|
+
success_criteria: List[str] = field(default_factory=list)
|
|
228
|
+
references: List[str] = field(default_factory=list)
|
|
229
|
+
|
|
230
|
+
def validate(self) -> Tuple[bool, List[str]]:
|
|
231
|
+
"""
|
|
232
|
+
Validate the plan document.
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
tuple: (is_valid, list_of_missing_sections)
|
|
236
|
+
"""
|
|
237
|
+
missing = []
|
|
238
|
+
|
|
239
|
+
# Check required fields
|
|
240
|
+
if not self.title or not self.title.strip():
|
|
241
|
+
missing.append("title")
|
|
242
|
+
if not self.overview or not self.overview.strip():
|
|
243
|
+
missing.append("overview")
|
|
244
|
+
if not self.steps:
|
|
245
|
+
missing.append("steps")
|
|
246
|
+
if not self.files_to_modify and not self.files_to_create:
|
|
247
|
+
missing.append("files_to_modify or files_to_create")
|
|
248
|
+
|
|
249
|
+
return len(missing) == 0, missing
|
|
250
|
+
|
|
251
|
+
|
|
195
252
|
# =============================================================================
|
|
196
253
|
# Session and State Types
|
|
197
254
|
# =============================================================================
|
tunacode/ui/input.py
CHANGED
|
@@ -76,19 +76,28 @@ async def multiline_input(
|
|
|
76
76
|
) -> str:
|
|
77
77
|
"""Get multiline input from the user with @file completion and highlighting."""
|
|
78
78
|
kb = create_key_bindings(state_manager)
|
|
79
|
+
|
|
80
|
+
# Clear any residual terminal output
|
|
81
|
+
import sys
|
|
82
|
+
sys.stdout.flush()
|
|
83
|
+
|
|
84
|
+
# Full placeholder with all keyboard shortcuts
|
|
79
85
|
placeholder = formatted_text(
|
|
80
86
|
(
|
|
81
87
|
"<darkgrey>"
|
|
82
88
|
"<bold>Enter</bold> to submit • "
|
|
83
89
|
"<bold>Esc + Enter</bold> for new line • "
|
|
84
90
|
"<bold>Esc twice</bold> to cancel • "
|
|
91
|
+
"<bold>Shift + Tab</bold> toggle plan mode • "
|
|
85
92
|
"<bold>/help</bold> for commands"
|
|
86
93
|
"</darkgrey>"
|
|
87
94
|
)
|
|
88
95
|
)
|
|
89
|
-
|
|
96
|
+
|
|
97
|
+
# Display input area (Plan Mode indicator is handled dynamically in prompt manager)
|
|
98
|
+
result = await input(
|
|
90
99
|
"multiline",
|
|
91
|
-
pretext="> ",
|
|
100
|
+
pretext="> ",
|
|
92
101
|
key_bindings=kb,
|
|
93
102
|
multiline=True,
|
|
94
103
|
placeholder=placeholder,
|
|
@@ -96,3 +105,5 @@ async def multiline_input(
|
|
|
96
105
|
lexer=FileReferenceLexer(),
|
|
97
106
|
state_manager=state_manager,
|
|
98
107
|
)
|
|
108
|
+
|
|
109
|
+
return result
|
tunacode/ui/keybindings.py
CHANGED
|
@@ -30,8 +30,8 @@ def create_key_bindings(state_manager: StateManager = None) -> KeyBindings:
|
|
|
30
30
|
|
|
31
31
|
@kb.add("escape")
|
|
32
32
|
def _escape(event):
|
|
33
|
-
"""Handle ESC key -
|
|
34
|
-
logger.debug("ESC key pressed -
|
|
33
|
+
"""Handle ESC key - trigger Ctrl+C behavior."""
|
|
34
|
+
logger.debug("ESC key pressed - simulating Ctrl+C")
|
|
35
35
|
|
|
36
36
|
# Cancel any active task if present
|
|
37
37
|
if state_manager and hasattr(state_manager.session, "current_task"):
|
|
@@ -44,7 +44,27 @@ def create_key_bindings(state_manager: StateManager = None) -> KeyBindings:
|
|
|
44
44
|
except Exception as e:
|
|
45
45
|
logger.debug(f"Failed to cancel task: {e}")
|
|
46
46
|
|
|
47
|
-
#
|
|
48
|
-
|
|
47
|
+
# Trigger the same behavior as Ctrl+C by sending the signal
|
|
48
|
+
import os
|
|
49
|
+
import signal
|
|
50
|
+
os.kill(os.getpid(), signal.SIGINT)
|
|
51
|
+
|
|
52
|
+
@kb.add("s-tab") # shift+tab
|
|
53
|
+
def _toggle_plan_mode(event):
|
|
54
|
+
"""Toggle between Plan Mode and normal mode."""
|
|
55
|
+
if state_manager:
|
|
56
|
+
# Toggle the state
|
|
57
|
+
if state_manager.is_plan_mode():
|
|
58
|
+
state_manager.exit_plan_mode()
|
|
59
|
+
logger.debug("Toggled to normal mode via Shift+Tab")
|
|
60
|
+
else:
|
|
61
|
+
state_manager.enter_plan_mode()
|
|
62
|
+
logger.debug("Toggled to Plan Mode via Shift+Tab")
|
|
63
|
+
|
|
64
|
+
# Clear the current buffer and refresh the display
|
|
65
|
+
event.current_buffer.reset()
|
|
66
|
+
|
|
67
|
+
# Force a refresh of the application without exiting
|
|
68
|
+
event.app.invalidate()
|
|
49
69
|
|
|
50
70
|
return kb
|
tunacode/ui/panels.py
CHANGED
|
@@ -170,6 +170,18 @@ class StreamingAgentPanel:
|
|
|
170
170
|
# Defensive: some providers may yield None chunks intermittently
|
|
171
171
|
if content_chunk is None:
|
|
172
172
|
content_chunk = ""
|
|
173
|
+
|
|
174
|
+
# Filter out plan mode system prompts and tool definitions from streaming
|
|
175
|
+
if any(phrase in str(content_chunk) for phrase in [
|
|
176
|
+
"🔧 PLAN MODE",
|
|
177
|
+
"TOOL EXECUTION ONLY",
|
|
178
|
+
"planning assistant that ONLY communicates",
|
|
179
|
+
"namespace functions {",
|
|
180
|
+
"namespace multi_tool_use {",
|
|
181
|
+
"You are trained on data up to"
|
|
182
|
+
]):
|
|
183
|
+
return
|
|
184
|
+
|
|
173
185
|
# Ensure type safety for concatenation
|
|
174
186
|
self.content = (self.content or "") + str(content_chunk)
|
|
175
187
|
|
|
@@ -182,6 +194,17 @@ class StreamingAgentPanel:
|
|
|
182
194
|
|
|
183
195
|
async def set_content(self, content: str):
|
|
184
196
|
"""Set the complete content (overwrites previous)."""
|
|
197
|
+
# Filter out plan mode system prompts and tool definitions
|
|
198
|
+
if any(phrase in str(content) for phrase in [
|
|
199
|
+
"🔧 PLAN MODE",
|
|
200
|
+
"TOOL EXECUTION ONLY",
|
|
201
|
+
"planning assistant that ONLY communicates",
|
|
202
|
+
"namespace functions {",
|
|
203
|
+
"namespace multi_tool_use {",
|
|
204
|
+
"You are trained on data up to"
|
|
205
|
+
]):
|
|
206
|
+
return
|
|
207
|
+
|
|
185
208
|
self.content = content
|
|
186
209
|
if self.live:
|
|
187
210
|
self.live.update(self._create_panel())
|
tunacode/ui/prompt_manager.py
CHANGED
|
@@ -100,15 +100,32 @@ class PromptManager:
|
|
|
100
100
|
"""
|
|
101
101
|
session = self.get_session(session_key, config)
|
|
102
102
|
|
|
103
|
-
# Create a custom prompt that changes based on input
|
|
103
|
+
# Create a custom prompt that changes based on input and plan mode
|
|
104
104
|
def get_prompt():
|
|
105
|
+
# Start with the base prompt
|
|
106
|
+
base_prompt = prompt
|
|
107
|
+
|
|
108
|
+
# Add Plan Mode indicator if active
|
|
109
|
+
if (self.state_manager and
|
|
110
|
+
self.state_manager.is_plan_mode() and
|
|
111
|
+
"PLAN MODE ON" not in base_prompt):
|
|
112
|
+
base_prompt = '<style fg="#40E0D0"><bold>⏸ PLAN MODE ON</bold></style>\n' + base_prompt
|
|
113
|
+
elif (self.state_manager and
|
|
114
|
+
not self.state_manager.is_plan_mode() and
|
|
115
|
+
("⏸" in base_prompt or "PLAN MODE ON" in base_prompt)):
|
|
116
|
+
# Remove plan mode indicator if no longer in plan mode
|
|
117
|
+
lines = base_prompt.split("\n")
|
|
118
|
+
if len(lines) > 1 and ("⏸" in lines[0] or "PLAN MODE ON" in lines[0]):
|
|
119
|
+
base_prompt = "\n".join(lines[1:])
|
|
120
|
+
|
|
105
121
|
# Check if current buffer starts with "!"
|
|
106
122
|
if hasattr(session.app, "current_buffer") and session.app.current_buffer:
|
|
107
123
|
text = session.app.current_buffer.text
|
|
108
124
|
if text.startswith("!"):
|
|
109
125
|
# Use bright yellow background with black text for high visibility
|
|
110
126
|
return HTML('<style bg="#ffcc00" fg="black"><b> ◆ BASH MODE ◆ </b></style> ')
|
|
111
|
-
|
|
127
|
+
|
|
128
|
+
return HTML(base_prompt) if isinstance(base_prompt, str) else base_prompt
|
|
112
129
|
|
|
113
130
|
try:
|
|
114
131
|
# Get user input with dynamic prompt
|
tunacode/ui/tool_ui.py
CHANGED
|
@@ -76,8 +76,9 @@ class ToolUI:
|
|
|
76
76
|
|
|
77
77
|
# Show file content on write_file
|
|
78
78
|
elif tool_name == TOOL_WRITE_FILE:
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
lang = ext_to_lang(args["filepath"])
|
|
80
|
+
code_block = f"```{lang}\n{args['content']}\n```"
|
|
81
|
+
return code_block
|
|
81
82
|
|
|
82
83
|
# Default to showing key and value on new line
|
|
83
84
|
content = ""
|
tunacode/utils/message_utils.py
CHANGED
|
@@ -9,11 +9,21 @@ def get_message_content(message: Any) -> str:
|
|
|
9
9
|
return message
|
|
10
10
|
if isinstance(message, dict):
|
|
11
11
|
if "content" in message:
|
|
12
|
-
|
|
12
|
+
content = message["content"]
|
|
13
|
+
# Handle nested content structures
|
|
14
|
+
if isinstance(content, list):
|
|
15
|
+
return " ".join(get_message_content(item) for item in content)
|
|
16
|
+
return str(content)
|
|
13
17
|
if "thought" in message:
|
|
14
|
-
return message["thought"]
|
|
18
|
+
return str(message["thought"])
|
|
15
19
|
if hasattr(message, "content"):
|
|
16
|
-
|
|
20
|
+
content = message.content
|
|
21
|
+
if isinstance(content, list):
|
|
22
|
+
return " ".join(get_message_content(item) for item in content)
|
|
23
|
+
return str(content)
|
|
17
24
|
if hasattr(message, "parts"):
|
|
18
|
-
|
|
25
|
+
parts = message.parts
|
|
26
|
+
if isinstance(parts, list):
|
|
27
|
+
return " ".join(get_message_content(part) for part in parts)
|
|
28
|
+
return str(parts)
|
|
19
29
|
return ""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tunacode-cli
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.56
|
|
4
4
|
Summary: Your agentic CLI developer.
|
|
5
5
|
Author-email: larock22 <noreply@github.com>
|
|
6
6
|
License: MIT
|
|
@@ -39,6 +39,7 @@ Requires-Dist: vulture>=2.7; extra == "dev"
|
|
|
39
39
|
Requires-Dist: unimport>=1.0.0; extra == "dev"
|
|
40
40
|
Requires-Dist: autoflake>=2.0.0; extra == "dev"
|
|
41
41
|
Requires-Dist: dead>=1.5.0; extra == "dev"
|
|
42
|
+
Requires-Dist: hatch>=1.6.0; extra == "dev"
|
|
42
43
|
Dynamic: license-file
|
|
43
44
|
|
|
44
45
|
# TunaCode CLI
|
|
@@ -106,7 +107,7 @@ tunacode --model "anthropic:claude-3.5-sonnet" --key "sk-ant-your-anthropic-key"
|
|
|
106
107
|
tunacode --model "openrouter:openai/gpt-4o" --key "sk-or-your-openrouter-key"
|
|
107
108
|
```
|
|
108
109
|
|
|
109
|
-
Your config is saved to `~/.config/tunacode.json` (edit directly with `nvim ~/.config/tunacode.json`)
|
|
110
|
+
Your config is saved to `~/.config/tunacode.json`. This file stores your API keys, model preferences, and runtime settings like `max_iterations` (default: 40) and `context_window_size`. You can edit it directly with `nvim ~/.config/tunacode.json` or see [the complete configuration example](documentation/configuration/config-file-example.md) for all available options.
|
|
110
111
|
|
|
111
112
|
### Recommended Models
|
|
112
113
|
|
|
@@ -243,7 +244,7 @@ tunacode --model "anthropic:claude-3.5-sonnet" --key "sk-ant-your-anthropic-key"
|
|
|
243
244
|
tunacode --model "openrouter:openai/gpt-4o" --key "sk-or-your-openrouter-key"
|
|
244
245
|
```
|
|
245
246
|
|
|
246
|
-
Your config is saved to `~/.config/tunacode.json` (edit directly with `nvim ~/.config/tunacode.json`)
|
|
247
|
+
Your config is saved to `~/.config/tunacode.json`. This file stores your API keys, model preferences, and runtime settings like `max_iterations` (default: 40) and `context_window_size`. You can edit it directly with `nvim ~/.config/tunacode.json` or see [the complete configuration example](documentation/configuration/config-file-example.md) for all available options.
|
|
247
248
|
|
|
248
249
|
### Recommended Models
|
|
249
250
|
|
|
@@ -1,47 +1,48 @@
|
|
|
1
1
|
tunacode/__init__.py,sha256=yUul8igNYMfUrHnYfioIGAqvrH8b5BKiO_pt1wVnmd0,119
|
|
2
|
-
tunacode/constants.py,sha256=
|
|
2
|
+
tunacode/constants.py,sha256=7expbYlpKFgNIAF-zkeUWQ4HBw6PQb1Lnvk0u85qleo,6077
|
|
3
3
|
tunacode/context.py,sha256=YtfRjUiqsSkk2k9Nn_pjb_m-AXyh6XcOBOJWtFI0wVw,2405
|
|
4
4
|
tunacode/exceptions.py,sha256=oDO1SVKOgjcKIylwqdbqh_g5my4roU5mB9Nv4n_Vb0s,3877
|
|
5
5
|
tunacode/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
6
|
tunacode/setup.py,sha256=a5S-uGkVYoBTvH9nsqMBgAFoH4CILOgfKvgS30qGnoU,1978
|
|
7
|
-
tunacode/types.py,sha256=
|
|
7
|
+
tunacode/types.py,sha256=9cprg2Lc9vkzRkiGQlwHwLXO09tZYaob12s6PJHxc3Y,10196
|
|
8
8
|
tunacode/cli/__init__.py,sha256=zgs0UbAck8hfvhYsWhWOfBe5oK09ug2De1r4RuQZREA,55
|
|
9
9
|
tunacode/cli/main.py,sha256=4MF4nGEU-CB83ckIl85AY-015EeUJHrE_UdbuSa9wCU,2968
|
|
10
|
-
tunacode/cli/repl.py,sha256=
|
|
10
|
+
tunacode/cli/repl.py,sha256=RVx1D3s2t9-Ur1q_-voyknFOQn4URlJExR9yceSjW8g,30745
|
|
11
11
|
tunacode/cli/commands/__init__.py,sha256=7rPwGkdYbPUgf__cPVX9oQUJOPQ18MdxDT_I64crdak,1740
|
|
12
12
|
tunacode/cli/commands/base.py,sha256=Ge_lNQA-GDfcb1Ap1oznCH3UrifBiHH3bA9DNL-tCDw,2519
|
|
13
|
-
tunacode/cli/commands/registry.py,sha256=
|
|
13
|
+
tunacode/cli/commands/registry.py,sha256=OOZG51x1DTFfjIzStxBVxAOb6SOsUSh2bfIOsKY3Z1Q,9515
|
|
14
14
|
tunacode/cli/commands/template_shortcut.py,sha256=ApYTPkDVBRaLxa7rWaPrsGcJdkR7eg09k18KyTjYg_E,3447
|
|
15
15
|
tunacode/cli/commands/implementations/__init__.py,sha256=DHjQm1f14OV1go0ZyqacFa3yfnGWH7LZFbZVVQZ5Iw0,1011
|
|
16
16
|
tunacode/cli/commands/implementations/conversation.py,sha256=ZijCNaRi1p5v1Q-IaVHtU2_BripSW3JCVKTtqFkOUjg,4676
|
|
17
17
|
tunacode/cli/commands/implementations/debug.py,sha256=ornvceGF4GbJd2OJXnnT9i9KpHBAMJUYNs9wNhzViGM,6764
|
|
18
18
|
tunacode/cli/commands/implementations/development.py,sha256=I8jHgYY3VgjTU8its0D0ysruuVqKbNTBur0JjPIUIZA,2844
|
|
19
19
|
tunacode/cli/commands/implementations/model.py,sha256=uthx6IX9KwgwywNTDklkJpqCbaTX9h1_p-eVmqL73WQ,2245
|
|
20
|
+
tunacode/cli/commands/implementations/plan.py,sha256=gFMcwoz9glLctvkyuNMqIkxoK6RAZA4f7JLQOBtgtqk,1865
|
|
20
21
|
tunacode/cli/commands/implementations/system.py,sha256=2cGw5iCJO3aNhXTFF28CgAIyLgslvHmpfyL2ZHVB6oQ,7903
|
|
21
22
|
tunacode/cli/commands/implementations/template.py,sha256=YeFOjbKKfPswPCHPvlDUwXvg6J0MesyAyVsujiIgPbU,5482
|
|
22
23
|
tunacode/cli/commands/implementations/todo.py,sha256=Dtz5bgcuK2VXGPWEBBZQgnWUMYkRXNzTGf_qkVPLF2U,8125
|
|
23
24
|
tunacode/cli/repl_components/__init__.py,sha256=5ZjPJ3yUvZ5x6Vg9EYJ03-tdxfEEdmfradCmwSlVY3E,334
|
|
24
25
|
tunacode/cli/repl_components/command_parser.py,sha256=SuDRP-nt8Sq5klI4-tXkllN_4nVzji5VJ-dQvbxDmDw,880
|
|
25
26
|
tunacode/cli/repl_components/error_recovery.py,sha256=yPoWXzuMi6B_Pwlm1wnFIJV55lJEOChvHGDca4XtoVI,3064
|
|
26
|
-
tunacode/cli/repl_components/output_display.py,sha256=
|
|
27
|
-
tunacode/cli/repl_components/tool_executor.py,sha256=
|
|
27
|
+
tunacode/cli/repl_components/output_display.py,sha256=ry-F2fG8m_oqfWt8lhvzeEhq0XgCI_Ed4H4plibfHJ8,1437
|
|
28
|
+
tunacode/cli/repl_components/tool_executor.py,sha256=1E8kL0eCP9f3wx2dZtNOFY0x7_B7eHdK9zYFpc11C0Y,3754
|
|
28
29
|
tunacode/configuration/__init__.py,sha256=MbVXy8bGu0yKehzgdgZ_mfWlYGvIdb1dY2Ly75nfuPE,17
|
|
29
30
|
tunacode/configuration/defaults.py,sha256=5TUeSqMTeA7kW7gz9hND_H4s9Key0_khPvc6mNFMlZc,838
|
|
30
31
|
tunacode/configuration/models.py,sha256=buH8ZquvcYI3OQBDIZeJ08cu00rSCeNABtUwl3VQS0E,4103
|
|
31
32
|
tunacode/configuration/settings.py,sha256=9wtIWBlLhW_ZBlLx-GA4XDfVZyGj2Gs6Zk49vk-nHq0,1047
|
|
32
33
|
tunacode/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
33
34
|
tunacode/core/code_index.py,sha256=jgAx3lSWP_DwnyiP5Jkm1YvX4JJyI4teMzlNrJSpEOA,15661
|
|
34
|
-
tunacode/core/state.py,sha256=
|
|
35
|
-
tunacode/core/tool_handler.py,sha256=
|
|
35
|
+
tunacode/core/state.py,sha256=IDCoHOWn40BBAXnd2h8OCpMtQRpt2d95KUgX8vkHu6M,7897
|
|
36
|
+
tunacode/core/tool_handler.py,sha256=A9wbnSpS0DsyqcOOS57I6txiuc74HEZ5mlc4qkRIgK0,3658
|
|
36
37
|
tunacode/core/agents/__init__.py,sha256=UUJiPYb91arwziSpjd7vIk7XNGA_4HQbsOIbskSqevA,149
|
|
37
38
|
tunacode/core/agents/main.py,sha256=myz_K2lxqYH8pQdbw8n8bai8F40Mqfj-kTLagPR1dP4,18253
|
|
38
39
|
tunacode/core/agents/utils.py,sha256=dJsdbvWs48vxQpwAtUjJLMj7_huv12Mx3E2CEgwoK94,14467
|
|
39
40
|
tunacode/core/agents/agent_components/__init__.py,sha256=CL4XH47T6v_iYy7xCPYjyiEFNOFnkcKwbTuKw6IjKTs,1474
|
|
40
|
-
tunacode/core/agents/agent_components/agent_config.py,sha256=
|
|
41
|
+
tunacode/core/agents/agent_components/agent_config.py,sha256=MwYGFvUGxgENU_IOy76EECA1TXc4Jc4-jeZctxZ8RSE,9664
|
|
41
42
|
tunacode/core/agents/agent_components/agent_helpers.py,sha256=G3zF5GPRzBhA3yOcsXf8gar892ackGDcwFk9wM6FA9s,8119
|
|
42
43
|
tunacode/core/agents/agent_components/json_tool_parser.py,sha256=HuyNT0rs-ppx_gLAI2e0XMVGbR_F0WXZfP3sx38VoMg,3447
|
|
43
44
|
tunacode/core/agents/agent_components/message_handler.py,sha256=KJGOtb9VhumgZpxxwO45HrKLhU9_MwuoWRsSQwJviNU,3704
|
|
44
|
-
tunacode/core/agents/agent_components/node_processor.py,sha256=
|
|
45
|
+
tunacode/core/agents/agent_components/node_processor.py,sha256=77XxHjBI6hFmC_c4hWguVjmHGr5o-8KA3aWHxIL6gPg,20736
|
|
45
46
|
tunacode/core/agents/agent_components/response_state.py,sha256=_C2loLeyZHMFHwjGny4h0dI02UoFJcJAVaabkh9H9JQ,343
|
|
46
47
|
tunacode/core/agents/agent_components/result_wrapper.py,sha256=9CFK0wpsfZx2WT4PBHfkSv22GxL1gAQuUYVMlmYtCJU,1761
|
|
47
48
|
tunacode/core/agents/agent_components/task_completion.py,sha256=BSnjNEFbxlzgzcXdjdTVGeCr1RpCiAaEp_D7f5FXa5Q,819
|
|
@@ -75,9 +76,11 @@ tunacode/templates/loader.py,sha256=6_Dk4jX47_GKUAWxlHG2Mzkl9lkXFUOiAdlcJqb6rBA,
|
|
|
75
76
|
tunacode/tools/__init__.py,sha256=ECBuUWWF1JjHW42CCceaPKgVTQyuljbz3RlhuA2fe2s,314
|
|
76
77
|
tunacode/tools/base.py,sha256=DhlanZZZxU2JJaBOwwyGFKMUcoCWR_CzLuwVeSXC0Go,7297
|
|
77
78
|
tunacode/tools/bash.py,sha256=mgZqugfDFevZ4BETuUv90pzXvtq7qKGUGFiuDxzmytk,8766
|
|
79
|
+
tunacode/tools/exit_plan_mode.py,sha256=UCtoqBPlT7auP2VJ1_KDsPIB9FatylqwQdD7cU4kz2g,7683
|
|
78
80
|
tunacode/tools/glob.py,sha256=mQwVGC8dfvzwzUOeTikfnHhExnLcYnGQwDoHOWrt_fE,10342
|
|
79
81
|
tunacode/tools/grep.py,sha256=SfpLazOMbizganPO16w7v6iHRcUn0cD6-6lyUwrl-3U,17834
|
|
80
82
|
tunacode/tools/list_dir.py,sha256=1kNqzYCNlcA5rqXIEVqcjQy6QxlLZLj5AG6YIECfwio,7217
|
|
83
|
+
tunacode/tools/present_plan.py,sha256=dxHlFMIeiSfxuXSkox7znw4YsrteyXswWNHpcbDz8XU,7988
|
|
81
84
|
tunacode/tools/read_file.py,sha256=z2omev9xzj4-0GG9mRssD13rj-Aa1c-pszFi2Z7Hxvk,3268
|
|
82
85
|
tunacode/tools/read_file_async_poc.py,sha256=2v2ckLQlwahgPGWGdE2c5Es37B35Y7zWdseZwT46E1E,6453
|
|
83
86
|
tunacode/tools/run_command.py,sha256=7UvXjFQI1Av4vceXx48MbQCTrsFNj4PlygTAAhNDYIA,4376
|
|
@@ -94,15 +97,15 @@ tunacode/ui/completers.py,sha256=18f1Im5210-b-qNKyCoOMnSjW99FXNoF0DtgRvEWTm0,490
|
|
|
94
97
|
tunacode/ui/console.py,sha256=HfE30vUy8ebXCobP7psFNJc17-dvH6APChg2tbi7aTw,2632
|
|
95
98
|
tunacode/ui/constants.py,sha256=A76B_KpM8jCuBYRg4cPmhi8_j6LLyWttO7_jjv47r3w,421
|
|
96
99
|
tunacode/ui/decorators.py,sha256=jJDNztO8MyX_IG1nqXAL8-sQUFjaAzBnc5BsM3ioX24,1955
|
|
97
|
-
tunacode/ui/input.py,sha256=
|
|
98
|
-
tunacode/ui/keybindings.py,sha256=
|
|
100
|
+
tunacode/ui/input.py,sha256=xnxpprr5K6Asd6vezvVhStnQEdKeY7f7pwo849X8UeE,3305
|
|
101
|
+
tunacode/ui/keybindings.py,sha256=vyETOZiEuWnnCsrd_bmnlbon6qLD0fK6llG9oU500zY,2373
|
|
99
102
|
tunacode/ui/lexers.py,sha256=tmg4ic1enyTRLzanN5QPP7D_0n12YjX_8ZhsffzhXA4,1340
|
|
100
103
|
tunacode/ui/logging_compat.py,sha256=5v6lcjVaG1CxdY1Zm9FAGr9H7Sy-tP6ihGfhP-5YvAY,1406
|
|
101
104
|
tunacode/ui/output.py,sha256=C2LHKAZxBySsIfk9saJ-jZrsZBE7f3WeP-RHpn5TChQ,6808
|
|
102
|
-
tunacode/ui/panels.py,sha256=
|
|
103
|
-
tunacode/ui/prompt_manager.py,sha256=
|
|
105
|
+
tunacode/ui/panels.py,sha256=0U63lISyrqE7FMOllaPx_Cp9hRGIR6Ra2NdGTCCR2oE,12381
|
|
106
|
+
tunacode/ui/prompt_manager.py,sha256=ENT2eqdOO5Hlf4Ga7kXaPVS4QNjxD2E7NQN6AmHQRPM,5522
|
|
104
107
|
tunacode/ui/tool_descriptions.py,sha256=vk61JPIXy7gHNfJ--77maXgK6WwNwxqY47QYsw_a2uw,4126
|
|
105
|
-
tunacode/ui/tool_ui.py,sha256=
|
|
108
|
+
tunacode/ui/tool_ui.py,sha256=MVmBLXx6OTJVFLl58SpoW0KoStOrbAY9sc6XXMKgWtQ,7216
|
|
106
109
|
tunacode/ui/utils.py,sha256=yvoCTz8AOdRfV0XIqUX3sgg88g_wntV9yhnQP6WzAVs,114
|
|
107
110
|
tunacode/ui/validators.py,sha256=MMIMT1I2v0l2jIy-gxX_4GSApvUTi8XWIOACr_dmoBA,758
|
|
108
111
|
tunacode/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -110,7 +113,7 @@ tunacode/utils/bm25.py,sha256=fd59YQXovC8rXwZrdoqIAfFrLn_WCVjzCh0pkU22APE,1966
|
|
|
110
113
|
tunacode/utils/diff_utils.py,sha256=V9QqQ0q4MfabVTnWptF3IXDp3estnfOKcJtDe_Sj14I,2372
|
|
111
114
|
tunacode/utils/file_utils.py,sha256=84g-MQRzmBI2aG_CuXsDl2OhvvWoSL7YdL5Kz_UKSwk,979
|
|
112
115
|
tunacode/utils/import_cache.py,sha256=q_xjJbtju05YbFopLDSkIo1hOtCx3DOTl3GQE5FFDgs,295
|
|
113
|
-
tunacode/utils/message_utils.py,sha256=
|
|
116
|
+
tunacode/utils/message_utils.py,sha256=V4MrZZPmwO22_MVGupMqtE5ltQEBwaSIqGD5LEb_bLw,1050
|
|
114
117
|
tunacode/utils/retry.py,sha256=AHdUzY6m-mwlT4OPXdtWWMAafL_NeS7JAMORGyM8c5k,4931
|
|
115
118
|
tunacode/utils/ripgrep.py,sha256=AXUs2FFt0A7KBV996deS8wreIlUzKOlAHJmwrcAr4No,583
|
|
116
119
|
tunacode/utils/security.py,sha256=i3eGKg4o-qY2S_ObTlEaHO93q14iBfiPXR5O7srHn58,6579
|
|
@@ -118,9 +121,9 @@ tunacode/utils/system.py,sha256=J8KqJ4ZqQrNSnM5rrJxPeMk9z2xQQp6dWtI1SKBY1-0,1112
|
|
|
118
121
|
tunacode/utils/text_utils.py,sha256=HAwlT4QMy41hr53cDbbNeNo05MI461TpI9b_xdIv8EY,7288
|
|
119
122
|
tunacode/utils/token_counter.py,sha256=dmFuqVz4ywGFdLfAi5Mg9bAGf8v87Ek-mHU-R3fsYjI,2711
|
|
120
123
|
tunacode/utils/user_configuration.py,sha256=Ilz8dpGVJDBE2iLWHAPT0xR8D51VRKV3kIbsAz8Bboc,3275
|
|
121
|
-
tunacode_cli-0.0.
|
|
122
|
-
tunacode_cli-0.0.
|
|
123
|
-
tunacode_cli-0.0.
|
|
124
|
-
tunacode_cli-0.0.
|
|
125
|
-
tunacode_cli-0.0.
|
|
126
|
-
tunacode_cli-0.0.
|
|
124
|
+
tunacode_cli-0.0.56.dist-info/licenses/LICENSE,sha256=Btzdu2kIoMbdSp6OyCLupB1aRgpTCJ_szMimgEnpkkE,1056
|
|
125
|
+
tunacode_cli-0.0.56.dist-info/METADATA,sha256=92Fa9XfK5XiYYOwiHlKuWHbnFkb0cGX1tAVOGwLalXQ,10773
|
|
126
|
+
tunacode_cli-0.0.56.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
127
|
+
tunacode_cli-0.0.56.dist-info/entry_points.txt,sha256=hbkytikj4dGu6rizPuAd_DGUPBGF191RTnhr9wdhORY,51
|
|
128
|
+
tunacode_cli-0.0.56.dist-info/top_level.txt,sha256=lKy2P6BWNi5XSA4DHFvyjQ14V26lDZctwdmhEJrxQbU,9
|
|
129
|
+
tunacode_cli-0.0.56.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|