gitarsenal-cli 1.9.64 ā 1.9.66
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.
- package/.venv_status.json +1 -1
- package/bin/gitarsenal.js +24 -0
- package/kill_claude/.claude/settings.local.json +9 -0
- package/kill_claude/README.md +265 -0
- package/kill_claude/__pycache__/bash_output_tool.cpython-313.pyc +0 -0
- package/kill_claude/__pycache__/bash_tool.cpython-313.pyc +0 -0
- package/kill_claude/__pycache__/claude_code_agent.cpython-313.pyc +0 -0
- package/kill_claude/__pycache__/edit_tool.cpython-313.pyc +0 -0
- package/kill_claude/__pycache__/exit_plan_mode_tool.cpython-313.pyc +0 -0
- package/kill_claude/__pycache__/glob_tool.cpython-313.pyc +0 -0
- package/kill_claude/__pycache__/grep_tool.cpython-313.pyc +0 -0
- package/kill_claude/__pycache__/kill_bash_tool.cpython-313.pyc +0 -0
- package/kill_claude/__pycache__/ls_tool.cpython-313.pyc +0 -0
- package/kill_claude/__pycache__/multiedit_tool.cpython-313.pyc +0 -0
- package/kill_claude/__pycache__/notebook_edit_tool.cpython-313.pyc +0 -0
- package/kill_claude/__pycache__/read_tool.cpython-313.pyc +0 -0
- package/kill_claude/__pycache__/task_tool.cpython-313.pyc +0 -0
- package/kill_claude/__pycache__/todo_write_tool.cpython-313.pyc +0 -0
- package/kill_claude/__pycache__/web_fetch_tool.cpython-313.pyc +0 -0
- package/kill_claude/__pycache__/web_search_tool.cpython-313.pyc +0 -0
- package/kill_claude/__pycache__/write_tool.cpython-313.pyc +0 -0
- package/kill_claude/claude_code_agent.py +848 -0
- package/kill_claude/prompts/claude-code-agent-prompts.md +65 -0
- package/kill_claude/prompts/claude-code-environment-context.md +100 -0
- package/kill_claude/prompts/claude-code-git-workflows.md +151 -0
- package/kill_claude/prompts/claude-code-hook-system.md +94 -0
- package/kill_claude/prompts/claude-code-response-formatting.md +79 -0
- package/kill_claude/prompts/claude-code-security-constraints.md +87 -0
- package/kill_claude/prompts/claude-code-system-prompt.md +136 -0
- package/kill_claude/prompts/claude-code-system-reminders.md +50 -0
- package/kill_claude/prompts/claude-code-task-workflows.md +114 -0
- package/kill_claude/prompts/claude-code-thinking-mode-prompts.md +39 -0
- package/kill_claude/prompts/claude-code-tool-prompts.md +339 -0
- package/kill_claude/prompts/claude-code-tool-usage-policies.md +87 -0
- package/kill_claude/requirements.txt +1 -0
- package/kill_claude/tools/__init__.py +1 -0
- package/kill_claude/tools/__pycache__/bash_output_tool.cpython-313.pyc +0 -0
- package/kill_claude/tools/__pycache__/bash_tool.cpython-313.pyc +0 -0
- package/kill_claude/tools/__pycache__/edit_tool.cpython-313.pyc +0 -0
- package/kill_claude/tools/__pycache__/exit_plan_mode_tool.cpython-313.pyc +0 -0
- package/kill_claude/tools/__pycache__/glob_tool.cpython-313.pyc +0 -0
- package/kill_claude/tools/__pycache__/grep_tool.cpython-313.pyc +0 -0
- package/kill_claude/tools/__pycache__/kill_bash_tool.cpython-313.pyc +0 -0
- package/kill_claude/tools/__pycache__/ls_tool.cpython-313.pyc +0 -0
- package/kill_claude/tools/__pycache__/multiedit_tool.cpython-313.pyc +0 -0
- package/kill_claude/tools/__pycache__/notebook_edit_tool.cpython-313.pyc +0 -0
- package/kill_claude/tools/__pycache__/read_tool.cpython-313.pyc +0 -0
- package/kill_claude/tools/__pycache__/task_tool.cpython-313.pyc +0 -0
- package/kill_claude/tools/__pycache__/todo_write_tool.cpython-313.pyc +0 -0
- package/kill_claude/tools/__pycache__/web_fetch_tool.cpython-313.pyc +0 -0
- package/kill_claude/tools/__pycache__/web_search_tool.cpython-313.pyc +0 -0
- package/kill_claude/tools/__pycache__/write_tool.cpython-313.pyc +0 -0
- package/kill_claude/tools/bash_output_tool.py +47 -0
- package/kill_claude/tools/bash_tool.py +79 -0
- package/kill_claude/tools/edit_tool.py +60 -0
- package/kill_claude/tools/exit_plan_mode_tool.py +42 -0
- package/kill_claude/tools/glob_tool.py +46 -0
- package/kill_claude/tools/grep_tool.py +105 -0
- package/kill_claude/tools/kill_bash_tool.py +38 -0
- package/kill_claude/tools/ls_tool.py +44 -0
- package/kill_claude/tools/multiedit_tool.py +111 -0
- package/kill_claude/tools/notebook_edit_tool.py +64 -0
- package/kill_claude/tools/read_tool.py +61 -0
- package/kill_claude/tools/task_tool.py +67 -0
- package/kill_claude/tools/todo_write_tool.py +114 -0
- package/kill_claude/tools/web_fetch_tool.py +55 -0
- package/kill_claude/tools/web_search_tool.py +58 -0
- package/kill_claude/tools/write_tool.py +46 -0
- package/lib/sandbox.js +3 -0
- package/package.json +1 -1
- package/python/test_modalSandboxScript.py +176 -301
- package/python/gitarsenal.py +0 -488
|
@@ -0,0 +1,848 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Claude Code Agent - Anthropic API Integration
|
|
4
|
+
|
|
5
|
+
This is the main file that replicates Claude Code's functionality by using the Anthropic API
|
|
6
|
+
with proper tool orchestration and system prompt integration.
|
|
7
|
+
|
|
8
|
+
Features:
|
|
9
|
+
- Real Anthropic API integration using claude-3-5-sonnet-20241022
|
|
10
|
+
- Dynamic loading of tool implementations from tools/ directory
|
|
11
|
+
- System prompt integration from prompts/ directory
|
|
12
|
+
- Tool orchestration with intelligent selection and execution
|
|
13
|
+
- Task management with TodoWrite integration
|
|
14
|
+
- Interactive CLI mode similar to Claude Code
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import os
|
|
18
|
+
import sys
|
|
19
|
+
import json
|
|
20
|
+
import re
|
|
21
|
+
import glob
|
|
22
|
+
import importlib.util
|
|
23
|
+
from typing import List, Dict, Any, Optional, Union
|
|
24
|
+
from dataclasses import dataclass
|
|
25
|
+
from enum import Enum
|
|
26
|
+
import anthropic
|
|
27
|
+
|
|
28
|
+
def load_tool_modules():
|
|
29
|
+
"""Load all tool modules from the tools directory."""
|
|
30
|
+
tools_dir = os.path.join(os.path.dirname(__file__), 'tools')
|
|
31
|
+
tool_modules = {}
|
|
32
|
+
|
|
33
|
+
for tool_file in glob.glob(os.path.join(tools_dir, '*_tool.py')):
|
|
34
|
+
if os.path.basename(tool_file) == '__init__.py':
|
|
35
|
+
continue
|
|
36
|
+
|
|
37
|
+
module_name = os.path.basename(tool_file)[:-3] # Remove .py
|
|
38
|
+
spec = importlib.util.spec_from_file_location(module_name, tool_file)
|
|
39
|
+
if spec and spec.loader:
|
|
40
|
+
module = importlib.util.module_from_spec(spec)
|
|
41
|
+
spec.loader.exec_module(module)
|
|
42
|
+
|
|
43
|
+
# Find tool class in module
|
|
44
|
+
for attr_name in dir(module):
|
|
45
|
+
attr = getattr(module, attr_name)
|
|
46
|
+
if (hasattr(attr, 'name') and hasattr(attr, 'schema') and
|
|
47
|
+
callable(getattr(attr, 'schema', None))):
|
|
48
|
+
tool_modules[attr.name] = attr
|
|
49
|
+
break
|
|
50
|
+
|
|
51
|
+
return tool_modules
|
|
52
|
+
|
|
53
|
+
def load_system_prompts():
|
|
54
|
+
"""Load all markdown files from prompts directory."""
|
|
55
|
+
prompts_dir = os.path.join(os.path.dirname(__file__), 'prompts')
|
|
56
|
+
prompt_content = {}
|
|
57
|
+
|
|
58
|
+
for md_file in glob.glob(os.path.join(prompts_dir, '*.md')):
|
|
59
|
+
filename = os.path.basename(md_file)
|
|
60
|
+
try:
|
|
61
|
+
with open(md_file, 'r', encoding='utf-8') as f:
|
|
62
|
+
content = f.read()
|
|
63
|
+
prompt_content[filename] = content
|
|
64
|
+
except Exception as e:
|
|
65
|
+
print(f"Warning: Could not load {filename}: {e}")
|
|
66
|
+
|
|
67
|
+
return prompt_content
|
|
68
|
+
|
|
69
|
+
# Load tools and prompts dynamically
|
|
70
|
+
TOOL_MODULES = load_tool_modules()
|
|
71
|
+
PROMPT_CONTENT = load_system_prompts()
|
|
72
|
+
|
|
73
|
+
# Generate tool schemas from loaded modules
|
|
74
|
+
TOOL_SCHEMAS = []
|
|
75
|
+
for tool_name, tool_class in TOOL_MODULES.items():
|
|
76
|
+
schema = tool_class.schema()
|
|
77
|
+
TOOL_SCHEMAS.append({
|
|
78
|
+
"name": tool_name,
|
|
79
|
+
"description": getattr(tool_class, '__doc__', f"Tool: {tool_name}"),
|
|
80
|
+
"input_schema": schema
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
# Combine all prompt content into system prompt
|
|
84
|
+
def build_system_prompt():
|
|
85
|
+
"""Build the complete system prompt from all markdown files."""
|
|
86
|
+
prompt_parts = []
|
|
87
|
+
|
|
88
|
+
# Start with the main system prompt
|
|
89
|
+
if 'claude-code-system-prompt.md' in PROMPT_CONTENT:
|
|
90
|
+
prompt_parts.append(PROMPT_CONTENT['claude-code-system-prompt.md'])
|
|
91
|
+
|
|
92
|
+
# Add other prompt files in a logical order
|
|
93
|
+
prompt_order = [
|
|
94
|
+
'claude-code-security-constraints.md',
|
|
95
|
+
'claude-code-response-formatting.md',
|
|
96
|
+
'claude-code-task-workflows.md',
|
|
97
|
+
'claude-code-tool-usage-policies.md',
|
|
98
|
+
'claude-code-tool-prompts.md',
|
|
99
|
+
'claude-code-git-workflows.md',
|
|
100
|
+
'claude-code-hook-system.md',
|
|
101
|
+
'claude-code-environment-context.md',
|
|
102
|
+
'claude-code-agent-prompts.md',
|
|
103
|
+
'claude-code-system-reminders.md',
|
|
104
|
+
'claude-code-thinking-mode-prompts.md'
|
|
105
|
+
]
|
|
106
|
+
|
|
107
|
+
for filename in prompt_order:
|
|
108
|
+
if filename in PROMPT_CONTENT:
|
|
109
|
+
prompt_parts.append(f"\n\n# {filename.replace('.md', '').replace('-', ' ').title()}\n")
|
|
110
|
+
prompt_parts.append(PROMPT_CONTENT[filename])
|
|
111
|
+
|
|
112
|
+
return '\n'.join(prompt_parts)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class QueryIntent(Enum):
|
|
116
|
+
"""Different types of user intents that can be handled."""
|
|
117
|
+
FILE_OPERATION = "file_operation"
|
|
118
|
+
SEARCH = "search"
|
|
119
|
+
CODE_EXECUTION = "code_execution"
|
|
120
|
+
WEB_ACCESS = "web_access"
|
|
121
|
+
TASK_MANAGEMENT = "task_management"
|
|
122
|
+
PLANNING = "planning"
|
|
123
|
+
INFORMATION = "information"
|
|
124
|
+
COMPLEX_TASK = "complex_task"
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@dataclass
|
|
128
|
+
class UserQuery:
|
|
129
|
+
"""Represents a parsed user query with intent and parameters."""
|
|
130
|
+
text: str
|
|
131
|
+
intent: QueryIntent
|
|
132
|
+
confidence: float
|
|
133
|
+
extracted_params: Dict[str, Any]
|
|
134
|
+
suggested_tools: List[str]
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class ClaudeCodeAgent:
|
|
138
|
+
"""
|
|
139
|
+
Main agent that orchestrates all tools using the Anthropic API.
|
|
140
|
+
|
|
141
|
+
This class replicates Claude Code's behavior by:
|
|
142
|
+
1. Using the actual Anthropic API with claude-3-5-sonnet-20241022
|
|
143
|
+
2. Implementing all tool schemas exactly as Claude Code uses them
|
|
144
|
+
3. Using the proper system prompt for agent control
|
|
145
|
+
4. Managing tasks with TodoWrite integration
|
|
146
|
+
5. Providing an interactive CLI interface
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
def __init__(self, api_key: Optional[str] = None):
|
|
150
|
+
"""Initialize the Claude Code Agent with Anthropic API integration."""
|
|
151
|
+
# Get API key from parameter or environment
|
|
152
|
+
self.api_key = api_key or os.getenv('ANTHROPIC_API_KEY')
|
|
153
|
+
if not self.api_key:
|
|
154
|
+
raise ValueError(
|
|
155
|
+
"Anthropic API key is required. Set ANTHROPIC_API_KEY environment variable "
|
|
156
|
+
"or pass api_key parameter."
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
# Initialize Anthropic client
|
|
160
|
+
self.client = anthropic.Anthropic(api_key=self.api_key)
|
|
161
|
+
|
|
162
|
+
# Environment context
|
|
163
|
+
self.working_dir = os.getcwd()
|
|
164
|
+
self.platform = os.uname().sysname.lower() if hasattr(os, 'uname') else 'unknown'
|
|
165
|
+
self.os_version = os.uname().release if hasattr(os, 'uname') else 'unknown'
|
|
166
|
+
|
|
167
|
+
# Check if git repo
|
|
168
|
+
self.is_git_repo = os.path.exists(os.path.join(self.working_dir, '.git'))
|
|
169
|
+
|
|
170
|
+
# Current date
|
|
171
|
+
from datetime import datetime
|
|
172
|
+
self.today_date = datetime.now().strftime('%Y-%m-%d')
|
|
173
|
+
|
|
174
|
+
# Conversation state
|
|
175
|
+
self.conversation_history = []
|
|
176
|
+
self.todo_list = []
|
|
177
|
+
|
|
178
|
+
# Tool usage patterns for query parsing
|
|
179
|
+
self.tool_patterns = {
|
|
180
|
+
# File operations
|
|
181
|
+
r'\b(read|open|show|view|cat)\s+(.+\.(py|js|ts|md|txt|json|yaml|yml|html|css))\b': 'Read',
|
|
182
|
+
r'\b(write|create|make)\s+(file|script)\b': 'Write',
|
|
183
|
+
r'\b(edit|modify|change|update|replace)\s+(.+)\b': 'Edit',
|
|
184
|
+
r'\b(list|ls|show)\s+(files|directory|dir)\b': 'LS',
|
|
185
|
+
|
|
186
|
+
# Search operations
|
|
187
|
+
r'\b(find|search|grep|look\s+for)\s+(.+)\b': 'Grep',
|
|
188
|
+
r'\b(find|search)\s+files?\s+(.+)\b': 'Glob',
|
|
189
|
+
|
|
190
|
+
# Code execution
|
|
191
|
+
r'\b(run|execute|bash|shell|command)\s+(.+)\b': 'Bash',
|
|
192
|
+
r'\b(npm|pip|git|python|node|cargo)\s+(.+)\b': 'Bash',
|
|
193
|
+
|
|
194
|
+
# Web access
|
|
195
|
+
r'\b(fetch|get|download|web|url)\s+(.+)\b': 'WebFetch',
|
|
196
|
+
r'\b(search\s+web|google|search\s+for)\s+(.+)\b': 'WebSearch',
|
|
197
|
+
|
|
198
|
+
# Task management
|
|
199
|
+
r'\b(todo|task|plan|organize)\b': 'TodoWrite',
|
|
200
|
+
r'\b(help\s+me|implement|build|create)\s+(.+)\b': 'Task',
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
def get_system_prompt(self) -> str:
|
|
204
|
+
"""Get the system prompt with environment context filled in."""
|
|
205
|
+
base_prompt = build_system_prompt()
|
|
206
|
+
|
|
207
|
+
# Add environment context
|
|
208
|
+
env_context = f"""
|
|
209
|
+
|
|
210
|
+
# Environment Information
|
|
211
|
+
<env>
|
|
212
|
+
Working directory: {self.working_dir}
|
|
213
|
+
Is directory a git repo: {"Yes" if self.is_git_repo else "No"}
|
|
214
|
+
Platform: {self.platform}
|
|
215
|
+
OS Version: {self.os_version}
|
|
216
|
+
Today's date: {self.today_date}
|
|
217
|
+
</env>
|
|
218
|
+
|
|
219
|
+
# Available Tools
|
|
220
|
+
The following {len(TOOL_SCHEMAS)} tools are loaded and available:
|
|
221
|
+
{', '.join([tool['name'] for tool in TOOL_SCHEMAS])}
|
|
222
|
+
"""
|
|
223
|
+
|
|
224
|
+
return base_prompt + env_context
|
|
225
|
+
|
|
226
|
+
def parse_query(self, query: str) -> UserQuery:
|
|
227
|
+
"""
|
|
228
|
+
Parse user query to understand intent and extract parameters.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
query: The user's input query
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
UserQuery object with parsed information
|
|
235
|
+
"""
|
|
236
|
+
query_lower = query.lower().strip()
|
|
237
|
+
|
|
238
|
+
# Determine intent based on keywords and patterns
|
|
239
|
+
intent = QueryIntent.INFORMATION # default
|
|
240
|
+
confidence = 0.5
|
|
241
|
+
extracted_params = {}
|
|
242
|
+
suggested_tools = []
|
|
243
|
+
|
|
244
|
+
# Check for specific patterns
|
|
245
|
+
for pattern, tool in self.tool_patterns.items():
|
|
246
|
+
if re.search(pattern, query_lower):
|
|
247
|
+
suggested_tools.append(tool)
|
|
248
|
+
confidence = 0.8
|
|
249
|
+
|
|
250
|
+
# Extract parameters from the match
|
|
251
|
+
match = re.search(pattern, query_lower)
|
|
252
|
+
if match and len(match.groups()) > 0:
|
|
253
|
+
# Get the last capturing group that has content
|
|
254
|
+
for i in range(len(match.groups()), 0, -1):
|
|
255
|
+
if match.group(i):
|
|
256
|
+
extracted_params['target'] = match.group(i).strip()
|
|
257
|
+
break
|
|
258
|
+
|
|
259
|
+
# Determine primary intent
|
|
260
|
+
if any(tool in ['Read', 'Write', 'Edit', 'LS', 'Glob'] for tool in suggested_tools):
|
|
261
|
+
intent = QueryIntent.FILE_OPERATION
|
|
262
|
+
elif any(tool in ['Grep', 'WebSearch'] for tool in suggested_tools):
|
|
263
|
+
intent = QueryIntent.SEARCH
|
|
264
|
+
elif 'Bash' in suggested_tools:
|
|
265
|
+
intent = QueryIntent.CODE_EXECUTION
|
|
266
|
+
elif any(tool in ['WebFetch', 'WebSearch'] for tool in suggested_tools):
|
|
267
|
+
intent = QueryIntent.WEB_ACCESS
|
|
268
|
+
elif 'TodoWrite' in suggested_tools:
|
|
269
|
+
intent = QueryIntent.TASK_MANAGEMENT
|
|
270
|
+
elif 'Task' in suggested_tools:
|
|
271
|
+
intent = QueryIntent.COMPLEX_TASK
|
|
272
|
+
|
|
273
|
+
# Look for complex multi-step queries
|
|
274
|
+
complex_indicators = ['implement', 'build', 'create', 'develop', 'setup', 'configure']
|
|
275
|
+
if any(indicator in query_lower for indicator in complex_indicators):
|
|
276
|
+
intent = QueryIntent.COMPLEX_TASK
|
|
277
|
+
confidence = 0.9
|
|
278
|
+
suggested_tools = ['TodoWrite', 'Task'] + suggested_tools
|
|
279
|
+
|
|
280
|
+
return UserQuery(
|
|
281
|
+
text=query,
|
|
282
|
+
intent=intent,
|
|
283
|
+
confidence=confidence,
|
|
284
|
+
extracted_params=extracted_params,
|
|
285
|
+
suggested_tools=list(set(suggested_tools)) # Remove duplicates
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
def should_use_todo_list(self, query: UserQuery) -> bool:
|
|
289
|
+
"""
|
|
290
|
+
Determine if the query requires using TodoWrite for task management.
|
|
291
|
+
|
|
292
|
+
Based on Claude Code's criteria:
|
|
293
|
+
- Complex multi-step tasks (3+ steps)
|
|
294
|
+
- Non-trivial and complex tasks
|
|
295
|
+
- User explicitly requests todo list
|
|
296
|
+
- Multiple tasks provided
|
|
297
|
+
"""
|
|
298
|
+
complex_keywords = [
|
|
299
|
+
'implement', 'build', 'create', 'develop', 'setup', 'configure',
|
|
300
|
+
'refactor', 'optimize', 'migrate', 'integrate', 'deploy'
|
|
301
|
+
]
|
|
302
|
+
|
|
303
|
+
multi_step_indicators = [
|
|
304
|
+
'and then', 'after that', 'next', 'also', 'additionally',
|
|
305
|
+
'first', 'second', 'then', 'finally'
|
|
306
|
+
]
|
|
307
|
+
|
|
308
|
+
# Check for complexity
|
|
309
|
+
has_complex_keywords = any(keyword in query.text.lower() for keyword in complex_keywords)
|
|
310
|
+
has_multi_step = any(indicator in query.text.lower() for indicator in multi_step_indicators)
|
|
311
|
+
has_multiple_sentences = len(query.text.split('.')) > 2
|
|
312
|
+
explicit_todo = 'todo' in query.text.lower() or 'task' in query.text.lower()
|
|
313
|
+
|
|
314
|
+
return (has_complex_keywords or has_multi_step or
|
|
315
|
+
has_multiple_sentences or explicit_todo or
|
|
316
|
+
query.intent == QueryIntent.COMPLEX_TASK)
|
|
317
|
+
|
|
318
|
+
def call_api(self, user_message: str, use_tools: bool = True) -> str:
|
|
319
|
+
"""
|
|
320
|
+
Make an API call to Claude with proper tool handling - matches Claude Code exactly.
|
|
321
|
+
|
|
322
|
+
Args:
|
|
323
|
+
user_message: The user's message/query
|
|
324
|
+
use_tools: Whether to include tools in the API call
|
|
325
|
+
|
|
326
|
+
Returns:
|
|
327
|
+
Claude's response text
|
|
328
|
+
"""
|
|
329
|
+
try:
|
|
330
|
+
# Start conversation with user message
|
|
331
|
+
messages = self.conversation_history + [
|
|
332
|
+
{"role": "user", "content": user_message}
|
|
333
|
+
]
|
|
334
|
+
|
|
335
|
+
# Continue conversation until Claude stops making tool calls
|
|
336
|
+
final_response_text = ""
|
|
337
|
+
|
|
338
|
+
while True:
|
|
339
|
+
# Make API call
|
|
340
|
+
call_params = {
|
|
341
|
+
"model": "claude-sonnet-4-20250514",
|
|
342
|
+
"max_tokens": 4096,
|
|
343
|
+
"system": self.get_system_prompt(),
|
|
344
|
+
"messages": messages
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if use_tools:
|
|
348
|
+
call_params["tools"] = TOOL_SCHEMAS
|
|
349
|
+
|
|
350
|
+
response = self.client.messages.create(**call_params)
|
|
351
|
+
|
|
352
|
+
# Extract response content and tool calls
|
|
353
|
+
response_text = ""
|
|
354
|
+
tool_calls = []
|
|
355
|
+
|
|
356
|
+
for content_block in response.content:
|
|
357
|
+
if content_block.type == "text":
|
|
358
|
+
response_text += content_block.text
|
|
359
|
+
elif content_block.type == "tool_use":
|
|
360
|
+
tool_calls.append(content_block)
|
|
361
|
+
|
|
362
|
+
# Add response text to final output
|
|
363
|
+
if response_text.strip():
|
|
364
|
+
final_response_text += response_text
|
|
365
|
+
|
|
366
|
+
# Update conversation with assistant response
|
|
367
|
+
messages.append({"role": "assistant", "content": response.content})
|
|
368
|
+
|
|
369
|
+
# If no tool calls, we're done
|
|
370
|
+
if not tool_calls:
|
|
371
|
+
break
|
|
372
|
+
|
|
373
|
+
# Execute tool calls
|
|
374
|
+
if tool_calls:
|
|
375
|
+
print(f"š¤ Claude requested {len(tool_calls)} tool call(s)")
|
|
376
|
+
tool_results = []
|
|
377
|
+
|
|
378
|
+
for i, tool_call in enumerate(tool_calls, 1):
|
|
379
|
+
print(f"--- Tool Call {i}/{len(tool_calls)} ---")
|
|
380
|
+
result = self.execute_tool_call(tool_call)
|
|
381
|
+
|
|
382
|
+
# Display the tool result
|
|
383
|
+
if result and result.strip():
|
|
384
|
+
print(f"š Tool Result:")
|
|
385
|
+
print(result)
|
|
386
|
+
print() # Empty line for readability
|
|
387
|
+
else:
|
|
388
|
+
print("š Tool Result: (empty or no output)")
|
|
389
|
+
print()
|
|
390
|
+
|
|
391
|
+
tool_results.append({
|
|
392
|
+
"tool_use_id": tool_call.id,
|
|
393
|
+
"type": "tool_result",
|
|
394
|
+
"content": str(result)
|
|
395
|
+
})
|
|
396
|
+
|
|
397
|
+
# Add tool results to conversation
|
|
398
|
+
messages.append({
|
|
399
|
+
"role": "user",
|
|
400
|
+
"content": tool_results
|
|
401
|
+
})
|
|
402
|
+
|
|
403
|
+
# Update conversation history
|
|
404
|
+
self.conversation_history = messages
|
|
405
|
+
|
|
406
|
+
return final_response_text.strip()
|
|
407
|
+
|
|
408
|
+
except Exception as e:
|
|
409
|
+
return f"Error calling Anthropic API: {str(e)}"
|
|
410
|
+
|
|
411
|
+
def execute_tool_call(self, tool_call) -> str:
|
|
412
|
+
"""
|
|
413
|
+
Execute a tool call using the loaded tool implementations.
|
|
414
|
+
|
|
415
|
+
Args:
|
|
416
|
+
tool_call: The tool call object from Claude
|
|
417
|
+
|
|
418
|
+
Returns:
|
|
419
|
+
Tool execution result as string
|
|
420
|
+
"""
|
|
421
|
+
tool_name = tool_call.name
|
|
422
|
+
tool_input = tool_call.input
|
|
423
|
+
|
|
424
|
+
# Print tool usage information
|
|
425
|
+
print(f"š ļø Using tool: {tool_name}")
|
|
426
|
+
if tool_input:
|
|
427
|
+
# Show key parameters (truncate long values)
|
|
428
|
+
params_display = []
|
|
429
|
+
for key, value in tool_input.items():
|
|
430
|
+
if isinstance(value, str) and len(value) > 50:
|
|
431
|
+
params_display.append(f"{key}='{value[:50]}...'")
|
|
432
|
+
else:
|
|
433
|
+
params_display.append(f"{key}={repr(value)}")
|
|
434
|
+
print(f" Parameters: {', '.join(params_display)}")
|
|
435
|
+
print() # Empty line for readability
|
|
436
|
+
|
|
437
|
+
try:
|
|
438
|
+
# Always use built-in implementations
|
|
439
|
+
result = self._execute_builtin_tool(tool_name, tool_input)
|
|
440
|
+
print(f"ā
Tool {tool_name} completed successfully\n")
|
|
441
|
+
return result if result is not None else ""
|
|
442
|
+
|
|
443
|
+
except Exception as e:
|
|
444
|
+
print(f"ā Tool {tool_name} failed: {str(e)}\n")
|
|
445
|
+
return f"Error executing {tool_name}: {str(e)}"
|
|
446
|
+
|
|
447
|
+
def _execute_builtin_tool(self, tool_name: str, tool_input: Dict[str, Any]) -> str:
|
|
448
|
+
"""Execute built-in tool implementations."""
|
|
449
|
+
if tool_name == "Read":
|
|
450
|
+
return self._read_file(tool_input.get("file_path", ""),
|
|
451
|
+
tool_input.get("limit"),
|
|
452
|
+
tool_input.get("offset"))
|
|
453
|
+
elif tool_name == "LS":
|
|
454
|
+
return self._list_directory(tool_input.get("path", ""),
|
|
455
|
+
tool_input.get("ignore", []))
|
|
456
|
+
elif tool_name == "Bash":
|
|
457
|
+
return self._execute_command(tool_input.get("command", ""),
|
|
458
|
+
tool_input.get("timeout", 30000))
|
|
459
|
+
elif tool_name == "TodoWrite":
|
|
460
|
+
return self._update_todos(tool_input.get("todos", []))
|
|
461
|
+
elif tool_name == "Write":
|
|
462
|
+
return self._write_file(tool_input.get("file_path", ""),
|
|
463
|
+
tool_input.get("content", ""))
|
|
464
|
+
elif tool_name == "Edit":
|
|
465
|
+
return self._edit_file(tool_input.get("file_path", ""),
|
|
466
|
+
tool_input.get("old_string", ""),
|
|
467
|
+
tool_input.get("new_string", ""),
|
|
468
|
+
tool_input.get("replace_all", False))
|
|
469
|
+
elif tool_name == "Glob":
|
|
470
|
+
return self._glob_files(tool_input.get("pattern", ""),
|
|
471
|
+
tool_input.get("path"))
|
|
472
|
+
elif tool_name == "Grep":
|
|
473
|
+
return self._grep_search(tool_input)
|
|
474
|
+
elif tool_name == "Task":
|
|
475
|
+
return self._handle_task(tool_input)
|
|
476
|
+
elif tool_name == "BashOutput":
|
|
477
|
+
return self._get_bash_output(tool_input)
|
|
478
|
+
elif tool_name == "KillBash":
|
|
479
|
+
return self._kill_bash(tool_input)
|
|
480
|
+
else:
|
|
481
|
+
return f"Tool {tool_name} not implemented"
|
|
482
|
+
|
|
483
|
+
def _handle_task(self, tool_input: Dict[str, Any]) -> str:
|
|
484
|
+
"""Handle Task tool - delegate to another agent instance."""
|
|
485
|
+
description = tool_input.get("description", "")
|
|
486
|
+
prompt = tool_input.get("prompt", "")
|
|
487
|
+
subagent_type = tool_input.get("subagent_type", "general-purpose")
|
|
488
|
+
|
|
489
|
+
# For now, just return a message indicating task delegation
|
|
490
|
+
return f"Task '{description}' has been delegated to {subagent_type} subagent. Prompt: {prompt[:100]}..."
|
|
491
|
+
|
|
492
|
+
def _get_bash_output(self, tool_input: Dict[str, Any]) -> str:
|
|
493
|
+
"""Handle BashOutput tool."""
|
|
494
|
+
bash_id = tool_input.get("bash_id", "")
|
|
495
|
+
return f"No background bash sessions are currently running (ID: {bash_id})"
|
|
496
|
+
|
|
497
|
+
def _kill_bash(self, tool_input: Dict[str, Any]) -> str:
|
|
498
|
+
"""Handle KillBash tool."""
|
|
499
|
+
shell_id = tool_input.get("shell_id", "")
|
|
500
|
+
return f"No bash session found with ID: {shell_id}"
|
|
501
|
+
|
|
502
|
+
def _read_file(self, file_path: str, limit: Optional[int] = None, offset: Optional[int] = None) -> str:
|
|
503
|
+
"""Read file with Claude Code formatting."""
|
|
504
|
+
try:
|
|
505
|
+
if not os.path.exists(file_path):
|
|
506
|
+
return f"File not found: {file_path}"
|
|
507
|
+
|
|
508
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
509
|
+
lines = f.readlines()
|
|
510
|
+
|
|
511
|
+
# Apply offset and limit
|
|
512
|
+
start_idx = (offset or 1) - 1
|
|
513
|
+
end_idx = start_idx + (limit or len(lines))
|
|
514
|
+
lines = lines[start_idx:end_idx]
|
|
515
|
+
|
|
516
|
+
# Format with line numbers like Claude Code
|
|
517
|
+
formatted_lines = []
|
|
518
|
+
for i, line in enumerate(lines):
|
|
519
|
+
line_num = start_idx + i + 1
|
|
520
|
+
# Remove trailing newline and truncate if too long
|
|
521
|
+
clean_line = line.rstrip('\n')
|
|
522
|
+
if len(clean_line) > 2000:
|
|
523
|
+
clean_line = clean_line[:2000] + "..."
|
|
524
|
+
formatted_lines.append(f"{line_num:>5}ā{clean_line}")
|
|
525
|
+
|
|
526
|
+
return '\n'.join(formatted_lines)
|
|
527
|
+
except Exception as e:
|
|
528
|
+
return f"Error reading file: {str(e)}"
|
|
529
|
+
|
|
530
|
+
def _list_directory(self, path: str, ignore: List[str] = None) -> str:
|
|
531
|
+
"""List directory contents with Claude Code formatting."""
|
|
532
|
+
try:
|
|
533
|
+
if not os.path.exists(path):
|
|
534
|
+
return f"Directory not found: {path}"
|
|
535
|
+
if not os.path.isdir(path):
|
|
536
|
+
return f"Not a directory: {path}"
|
|
537
|
+
|
|
538
|
+
items = []
|
|
539
|
+
all_items = os.listdir(path)
|
|
540
|
+
|
|
541
|
+
for item in sorted(all_items):
|
|
542
|
+
if ignore:
|
|
543
|
+
import fnmatch
|
|
544
|
+
if any(fnmatch.fnmatch(item, pattern) for pattern in ignore):
|
|
545
|
+
continue
|
|
546
|
+
|
|
547
|
+
item_path = os.path.join(path, item)
|
|
548
|
+
if os.path.isdir(item_path):
|
|
549
|
+
items.append(f" - {item}/")
|
|
550
|
+
else:
|
|
551
|
+
items.append(f" - {item}")
|
|
552
|
+
|
|
553
|
+
result = f"- {path}/\n" + '\n'.join(items)
|
|
554
|
+
return result
|
|
555
|
+
|
|
556
|
+
except Exception as e:
|
|
557
|
+
return f"Error listing directory: {str(e)}"
|
|
558
|
+
|
|
559
|
+
def _execute_command(self, command: str, timeout_ms: int = 30000) -> str:
|
|
560
|
+
"""Execute bash command."""
|
|
561
|
+
import subprocess
|
|
562
|
+
try:
|
|
563
|
+
timeout_sec = timeout_ms / 1000.0
|
|
564
|
+
|
|
565
|
+
result = subprocess.run(
|
|
566
|
+
command,
|
|
567
|
+
shell=True,
|
|
568
|
+
capture_output=True,
|
|
569
|
+
text=True,
|
|
570
|
+
timeout=timeout_sec,
|
|
571
|
+
cwd=self.working_dir
|
|
572
|
+
)
|
|
573
|
+
|
|
574
|
+
output_parts = []
|
|
575
|
+
|
|
576
|
+
# Add stdout if present
|
|
577
|
+
if result.stdout and result.stdout.strip():
|
|
578
|
+
output_parts.append(result.stdout.strip())
|
|
579
|
+
|
|
580
|
+
# Add stderr if present
|
|
581
|
+
if result.stderr and result.stderr.strip():
|
|
582
|
+
output_parts.append(result.stderr.strip())
|
|
583
|
+
|
|
584
|
+
# If command failed, include exit code
|
|
585
|
+
if result.returncode != 0:
|
|
586
|
+
if not output_parts:
|
|
587
|
+
output_parts.append(f"Command failed with exit code {result.returncode}")
|
|
588
|
+
else:
|
|
589
|
+
output_parts.insert(0, f"Exit code: {result.returncode}")
|
|
590
|
+
|
|
591
|
+
# If no output at all but success, indicate success
|
|
592
|
+
if not output_parts and result.returncode == 0:
|
|
593
|
+
output_parts.append("<system>Tool ran without output or errors</system>")
|
|
594
|
+
|
|
595
|
+
return "\n".join(output_parts)
|
|
596
|
+
|
|
597
|
+
except subprocess.TimeoutExpired:
|
|
598
|
+
return f"Command timed out after {timeout_sec} seconds"
|
|
599
|
+
except Exception as e:
|
|
600
|
+
return f"Error executing command: {str(e)}"
|
|
601
|
+
|
|
602
|
+
def _update_todos(self, todos: List[Dict]) -> str:
|
|
603
|
+
"""Update todo list."""
|
|
604
|
+
self.todo_list = todos
|
|
605
|
+
return f"Todos have been modified successfully. Ensure that you continue to use the todo list to track your progress. Please proceed with the current tasks if applicable"
|
|
606
|
+
|
|
607
|
+
def _write_file(self, file_path: str, content: str) -> str:
|
|
608
|
+
"""Write content to file."""
|
|
609
|
+
try:
|
|
610
|
+
os.makedirs(os.path.dirname(file_path), exist_ok=True)
|
|
611
|
+
with open(file_path, 'w', encoding='utf-8') as f:
|
|
612
|
+
f.write(content)
|
|
613
|
+
return f"File created successfully at: {file_path}"
|
|
614
|
+
except Exception as e:
|
|
615
|
+
return f"Error writing file: {str(e)}"
|
|
616
|
+
|
|
617
|
+
def _edit_file(self, file_path: str, old_string: str, new_string: str, replace_all: bool = False) -> str:
|
|
618
|
+
"""Edit file by replacing strings."""
|
|
619
|
+
try:
|
|
620
|
+
if not os.path.exists(file_path):
|
|
621
|
+
return f"File not found: {file_path}"
|
|
622
|
+
|
|
623
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
624
|
+
content = f.read()
|
|
625
|
+
|
|
626
|
+
if old_string not in content:
|
|
627
|
+
return f"String not found in file: {old_string}"
|
|
628
|
+
|
|
629
|
+
if replace_all:
|
|
630
|
+
new_content = content.replace(old_string, new_string)
|
|
631
|
+
count = content.count(old_string)
|
|
632
|
+
else:
|
|
633
|
+
new_content = content.replace(old_string, new_string, 1)
|
|
634
|
+
count = 1
|
|
635
|
+
|
|
636
|
+
with open(file_path, 'w', encoding='utf-8') as f:
|
|
637
|
+
f.write(new_content)
|
|
638
|
+
|
|
639
|
+
return f"The file {file_path} has been updated. Here's the result of running `cat -n` on a snippet of the edited file:\n{self._read_file(file_path, 10, 1)}"
|
|
640
|
+
except Exception as e:
|
|
641
|
+
return f"Error editing file: {str(e)}"
|
|
642
|
+
|
|
643
|
+
def _glob_files(self, pattern: str, path: str = None) -> str:
|
|
644
|
+
"""Find files matching glob pattern."""
|
|
645
|
+
try:
|
|
646
|
+
import glob as glob_module
|
|
647
|
+
search_path = os.path.join(path or self.working_dir, pattern)
|
|
648
|
+
matches = glob_module.glob(search_path, recursive=True)
|
|
649
|
+
|
|
650
|
+
if not matches:
|
|
651
|
+
return f"No files found matching pattern: {pattern}"
|
|
652
|
+
|
|
653
|
+
# Sort by modification time (most recent first)
|
|
654
|
+
matches.sort(key=lambda x: os.path.getmtime(x), reverse=True)
|
|
655
|
+
return '\n'.join(matches[:100]) # Limit to first 100 matches
|
|
656
|
+
except Exception as e:
|
|
657
|
+
return f"Error finding files: {str(e)}"
|
|
658
|
+
|
|
659
|
+
def _grep_search(self, tool_input: Dict[str, Any]) -> str:
|
|
660
|
+
"""Search for pattern in files using basic implementation."""
|
|
661
|
+
try:
|
|
662
|
+
pattern = tool_input.get('pattern', '')
|
|
663
|
+
path = tool_input.get('path', self.working_dir)
|
|
664
|
+
case_insensitive = tool_input.get('-i', False)
|
|
665
|
+
output_mode = tool_input.get('output_mode', 'files_with_matches')
|
|
666
|
+
|
|
667
|
+
import re as regex
|
|
668
|
+
flags = regex.IGNORECASE if case_insensitive else 0
|
|
669
|
+
compiled_pattern = regex.compile(pattern, flags)
|
|
670
|
+
|
|
671
|
+
matches = []
|
|
672
|
+
search_files = []
|
|
673
|
+
|
|
674
|
+
# Collect files to search
|
|
675
|
+
if os.path.isfile(path):
|
|
676
|
+
search_files = [path]
|
|
677
|
+
else:
|
|
678
|
+
for root, dirs, files in os.walk(path):
|
|
679
|
+
for file in files:
|
|
680
|
+
if file.endswith(('.py', '.js', '.ts', '.md', '.txt', '.json', '.yaml', '.yml')):
|
|
681
|
+
search_files.append(os.path.join(root, file))
|
|
682
|
+
|
|
683
|
+
for file_path in search_files:
|
|
684
|
+
try:
|
|
685
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
686
|
+
content = f.read()
|
|
687
|
+
if compiled_pattern.search(content):
|
|
688
|
+
matches.append(file_path)
|
|
689
|
+
except:
|
|
690
|
+
continue
|
|
691
|
+
|
|
692
|
+
if output_mode == 'files_with_matches':
|
|
693
|
+
return '\n'.join(matches) if matches else "No matches found"
|
|
694
|
+
else:
|
|
695
|
+
return f"Found {len(matches)} matching files"
|
|
696
|
+
except Exception as e:
|
|
697
|
+
return f"Error searching: {str(e)}"
|
|
698
|
+
|
|
699
|
+
def process_query(self, user_input: str) -> str:
|
|
700
|
+
"""
|
|
701
|
+
Main method to process user queries using the Anthropic API.
|
|
702
|
+
|
|
703
|
+
Args:
|
|
704
|
+
user_input: The user's query string
|
|
705
|
+
|
|
706
|
+
Returns:
|
|
707
|
+
Claude's response
|
|
708
|
+
"""
|
|
709
|
+
if not user_input.strip():
|
|
710
|
+
return "Please provide a query or command."
|
|
711
|
+
|
|
712
|
+
# Parse the query to understand intent
|
|
713
|
+
query = self.parse_query(user_input)
|
|
714
|
+
|
|
715
|
+
# Call the Anthropic API with the full system prompt and tools
|
|
716
|
+
response = self.call_api(user_input, use_tools=True)
|
|
717
|
+
|
|
718
|
+
return response
|
|
719
|
+
|
|
720
|
+
def interactive_mode(self):
|
|
721
|
+
"""
|
|
722
|
+
Run the agent in interactive mode, similar to Claude Code's CLI.
|
|
723
|
+
"""
|
|
724
|
+
print("š¤ Claude Code Agent - Interactive Mode")
|
|
725
|
+
print("Powered by Anthropic API with claude-sonnet-4-20250514")
|
|
726
|
+
print("Type 'help' for available commands, 'exit' to quit")
|
|
727
|
+
print("-" * 60)
|
|
728
|
+
|
|
729
|
+
while True:
|
|
730
|
+
try:
|
|
731
|
+
user_input = input("\nš¤ You: ").strip()
|
|
732
|
+
|
|
733
|
+
if user_input.lower() in ['exit', 'quit', 'bye']:
|
|
734
|
+
print("š Goodbye!")
|
|
735
|
+
break
|
|
736
|
+
elif user_input.lower() == 'help':
|
|
737
|
+
self.show_help()
|
|
738
|
+
continue
|
|
739
|
+
elif user_input.lower() == 'status':
|
|
740
|
+
self.show_status()
|
|
741
|
+
continue
|
|
742
|
+
elif not user_input:
|
|
743
|
+
continue
|
|
744
|
+
|
|
745
|
+
print("š¤ Claude Code Agent:")
|
|
746
|
+
response = self.process_query(user_input)
|
|
747
|
+
print(response)
|
|
748
|
+
|
|
749
|
+
except KeyboardInterrupt:
|
|
750
|
+
print("\n\nš Goodbye!")
|
|
751
|
+
break
|
|
752
|
+
except Exception as e:
|
|
753
|
+
print(f"ā Error: {str(e)}")
|
|
754
|
+
|
|
755
|
+
def show_help(self):
|
|
756
|
+
"""Show help information."""
|
|
757
|
+
help_text = """
|
|
758
|
+
Available commands and capabilities:
|
|
759
|
+
|
|
760
|
+
š File Operations:
|
|
761
|
+
- read <file> : Read file contents
|
|
762
|
+
- list [directory] : List files and directories
|
|
763
|
+
- find <pattern> : Find files matching pattern
|
|
764
|
+
- edit <file> : Edit file contents
|
|
765
|
+
|
|
766
|
+
š Search:
|
|
767
|
+
- search <term> : Search in files
|
|
768
|
+
- web search <term> : Search the web
|
|
769
|
+
|
|
770
|
+
āļø Code Execution:
|
|
771
|
+
- run <command> : Execute bash commands
|
|
772
|
+
- npm/pip/git commands : Execute package manager commands
|
|
773
|
+
|
|
774
|
+
š Web Access:
|
|
775
|
+
- fetch <url> : Fetch content from URL
|
|
776
|
+
- web search <query> : Search the web
|
|
777
|
+
|
|
778
|
+
š Task Management:
|
|
779
|
+
- Complex tasks automatically create todo lists
|
|
780
|
+
- Multi-step operations are tracked
|
|
781
|
+
|
|
782
|
+
š” Examples:
|
|
783
|
+
- "read main.py"
|
|
784
|
+
- "search for TODO comments"
|
|
785
|
+
- "run pytest tests/"
|
|
786
|
+
- "implement user authentication system"
|
|
787
|
+
- "web search latest Python features 2024"
|
|
788
|
+
|
|
789
|
+
Special commands:
|
|
790
|
+
- help : Show this help
|
|
791
|
+
- status : Show current status
|
|
792
|
+
- exit : Quit the agent
|
|
793
|
+
|
|
794
|
+
This agent uses the real Anthropic API with claude-sonnet-4-20250514
|
|
795
|
+
and implements all Claude Code tools with proper system prompts.
|
|
796
|
+
"""
|
|
797
|
+
print(help_text)
|
|
798
|
+
|
|
799
|
+
def show_status(self):
|
|
800
|
+
"""Show current status."""
|
|
801
|
+
print(f"""
|
|
802
|
+
š Claude Code Agent Status:
|
|
803
|
+
|
|
804
|
+
š§ Configuration:
|
|
805
|
+
- API Key: {'ā
Set' if self.api_key else 'ā Missing'}
|
|
806
|
+
- Model: claude-sonnet-4-20250514
|
|
807
|
+
- Working Directory: {self.working_dir}
|
|
808
|
+
- Git Repository: {'Yes' if self.is_git_repo else 'No'}
|
|
809
|
+
|
|
810
|
+
š Session:
|
|
811
|
+
- Conversation History: {len(self.conversation_history)} messages
|
|
812
|
+
- Todo Items: {len(self.todo_list)} items
|
|
813
|
+
|
|
814
|
+
š Loaded Prompts: {len(PROMPT_CONTENT)} markdown files
|
|
815
|
+
|
|
816
|
+
š ļø Available Tools: {len(TOOL_SCHEMAS)} tools loaded from modules
|
|
817
|
+
- {', '.join([tool['name'] for tool in TOOL_SCHEMAS])}
|
|
818
|
+
""")
|
|
819
|
+
|
|
820
|
+
|
|
821
|
+
def main():
|
|
822
|
+
"""Main entry point for the Claude Code Agent."""
|
|
823
|
+
try:
|
|
824
|
+
agent = ClaudeCodeAgent()
|
|
825
|
+
|
|
826
|
+
if len(sys.argv) > 1:
|
|
827
|
+
# Process single query from command line
|
|
828
|
+
query = ' '.join(sys.argv[1:])
|
|
829
|
+
response = agent.process_query(query)
|
|
830
|
+
print(response)
|
|
831
|
+
else:
|
|
832
|
+
# Interactive mode
|
|
833
|
+
agent.interactive_mode()
|
|
834
|
+
|
|
835
|
+
except ValueError as e:
|
|
836
|
+
print(f"ā Configuration Error: {str(e)}")
|
|
837
|
+
print("\nTo fix this:")
|
|
838
|
+
print("1. Get your Anthropic API key from: https://console.anthropic.com/")
|
|
839
|
+
print("2. Set environment variable: export ANTHROPIC_API_KEY=your_key_here")
|
|
840
|
+
print("3. Or pass it as parameter: ClaudeCodeAgent(api_key='your_key')")
|
|
841
|
+
sys.exit(1)
|
|
842
|
+
except Exception as e:
|
|
843
|
+
print(f"ā Error: {str(e)}")
|
|
844
|
+
sys.exit(1)
|
|
845
|
+
|
|
846
|
+
|
|
847
|
+
if __name__ == "__main__":
|
|
848
|
+
main()
|