gitarsenal-cli 1.9.65 → 1.9.67

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.
Files changed (72) hide show
  1. package/.venv_status.json +1 -1
  2. package/bin/gitarsenal.js +24 -0
  3. package/kill_claude/.claude/settings.local.json +9 -0
  4. package/kill_claude/README.md +265 -0
  5. package/kill_claude/__pycache__/bash_output_tool.cpython-313.pyc +0 -0
  6. package/kill_claude/__pycache__/bash_tool.cpython-313.pyc +0 -0
  7. package/kill_claude/__pycache__/claude_code_agent.cpython-313.pyc +0 -0
  8. package/kill_claude/__pycache__/edit_tool.cpython-313.pyc +0 -0
  9. package/kill_claude/__pycache__/exit_plan_mode_tool.cpython-313.pyc +0 -0
  10. package/kill_claude/__pycache__/glob_tool.cpython-313.pyc +0 -0
  11. package/kill_claude/__pycache__/grep_tool.cpython-313.pyc +0 -0
  12. package/kill_claude/__pycache__/kill_bash_tool.cpython-313.pyc +0 -0
  13. package/kill_claude/__pycache__/ls_tool.cpython-313.pyc +0 -0
  14. package/kill_claude/__pycache__/multiedit_tool.cpython-313.pyc +0 -0
  15. package/kill_claude/__pycache__/notebook_edit_tool.cpython-313.pyc +0 -0
  16. package/kill_claude/__pycache__/read_tool.cpython-313.pyc +0 -0
  17. package/kill_claude/__pycache__/task_tool.cpython-313.pyc +0 -0
  18. package/kill_claude/__pycache__/todo_write_tool.cpython-313.pyc +0 -0
  19. package/kill_claude/__pycache__/web_fetch_tool.cpython-313.pyc +0 -0
  20. package/kill_claude/__pycache__/web_search_tool.cpython-313.pyc +0 -0
  21. package/kill_claude/__pycache__/write_tool.cpython-313.pyc +0 -0
  22. package/kill_claude/claude_code_agent.py +848 -0
  23. package/kill_claude/prompts/claude-code-agent-prompts.md +65 -0
  24. package/kill_claude/prompts/claude-code-environment-context.md +100 -0
  25. package/kill_claude/prompts/claude-code-git-workflows.md +151 -0
  26. package/kill_claude/prompts/claude-code-hook-system.md +94 -0
  27. package/kill_claude/prompts/claude-code-response-formatting.md +79 -0
  28. package/kill_claude/prompts/claude-code-security-constraints.md +87 -0
  29. package/kill_claude/prompts/claude-code-system-prompt.md +136 -0
  30. package/kill_claude/prompts/claude-code-system-reminders.md +50 -0
  31. package/kill_claude/prompts/claude-code-task-workflows.md +114 -0
  32. package/kill_claude/prompts/claude-code-thinking-mode-prompts.md +39 -0
  33. package/kill_claude/prompts/claude-code-tool-prompts.md +339 -0
  34. package/kill_claude/prompts/claude-code-tool-usage-policies.md +87 -0
  35. package/kill_claude/requirements.txt +1 -0
  36. package/kill_claude/tools/__init__.py +1 -0
  37. package/kill_claude/tools/__pycache__/bash_output_tool.cpython-313.pyc +0 -0
  38. package/kill_claude/tools/__pycache__/bash_tool.cpython-313.pyc +0 -0
  39. package/kill_claude/tools/__pycache__/edit_tool.cpython-313.pyc +0 -0
  40. package/kill_claude/tools/__pycache__/exit_plan_mode_tool.cpython-313.pyc +0 -0
  41. package/kill_claude/tools/__pycache__/glob_tool.cpython-313.pyc +0 -0
  42. package/kill_claude/tools/__pycache__/grep_tool.cpython-313.pyc +0 -0
  43. package/kill_claude/tools/__pycache__/kill_bash_tool.cpython-313.pyc +0 -0
  44. package/kill_claude/tools/__pycache__/ls_tool.cpython-313.pyc +0 -0
  45. package/kill_claude/tools/__pycache__/multiedit_tool.cpython-313.pyc +0 -0
  46. package/kill_claude/tools/__pycache__/notebook_edit_tool.cpython-313.pyc +0 -0
  47. package/kill_claude/tools/__pycache__/read_tool.cpython-313.pyc +0 -0
  48. package/kill_claude/tools/__pycache__/task_tool.cpython-313.pyc +0 -0
  49. package/kill_claude/tools/__pycache__/todo_write_tool.cpython-313.pyc +0 -0
  50. package/kill_claude/tools/__pycache__/web_fetch_tool.cpython-313.pyc +0 -0
  51. package/kill_claude/tools/__pycache__/web_search_tool.cpython-313.pyc +0 -0
  52. package/kill_claude/tools/__pycache__/write_tool.cpython-313.pyc +0 -0
  53. package/kill_claude/tools/bash_output_tool.py +47 -0
  54. package/kill_claude/tools/bash_tool.py +79 -0
  55. package/kill_claude/tools/edit_tool.py +60 -0
  56. package/kill_claude/tools/exit_plan_mode_tool.py +42 -0
  57. package/kill_claude/tools/glob_tool.py +46 -0
  58. package/kill_claude/tools/grep_tool.py +105 -0
  59. package/kill_claude/tools/kill_bash_tool.py +38 -0
  60. package/kill_claude/tools/ls_tool.py +44 -0
  61. package/kill_claude/tools/multiedit_tool.py +111 -0
  62. package/kill_claude/tools/notebook_edit_tool.py +64 -0
  63. package/kill_claude/tools/read_tool.py +61 -0
  64. package/kill_claude/tools/task_tool.py +67 -0
  65. package/kill_claude/tools/todo_write_tool.py +114 -0
  66. package/kill_claude/tools/web_fetch_tool.py +55 -0
  67. package/kill_claude/tools/web_search_tool.py +58 -0
  68. package/kill_claude/tools/write_tool.py +46 -0
  69. package/lib/sandbox.js +3 -0
  70. package/package.json +1 -1
  71. package/python/test_modalSandboxScript.py +144 -295
  72. 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()