handycode 2.1.0__tar.gz → 2.1.2__tar.gz
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.
- {handycode-2.1.0 → handycode-2.1.2}/PKG-INFO +1 -1
- {handycode-2.1.0 → handycode-2.1.2}/handycode/__init__.py +1 -1
- {handycode-2.1.0 → handycode-2.1.2}/handycode/assistant.py +228 -231
- {handycode-2.1.0 → handycode-2.1.2}/handycode.egg-info/PKG-INFO +1 -1
- {handycode-2.1.0 → handycode-2.1.2}/setup.py +1 -1
- {handycode-2.1.0 → handycode-2.1.2}/LICENSE +0 -0
- {handycode-2.1.0 → handycode-2.1.2}/README.md +0 -0
- {handycode-2.1.0 → handycode-2.1.2}/handycode/__main__.py +0 -0
- {handycode-2.1.0 → handycode-2.1.2}/handycode/cli.py +0 -0
- {handycode-2.1.0 → handycode-2.1.2}/handycode/config.py +0 -0
- {handycode-2.1.0 → handycode-2.1.2}/handycode/file_manager.py +0 -0
- {handycode-2.1.0 → handycode-2.1.2}/handycode/logo.py +0 -0
- {handycode-2.1.0 → handycode-2.1.2}/handycode/main.py +0 -0
- {handycode-2.1.0 → handycode-2.1.2}/handycode/models.py +0 -0
- {handycode-2.1.0 → handycode-2.1.2}/handycode/project_templates.py +0 -0
- {handycode-2.1.0 → handycode-2.1.2}/handycode/security.py +0 -0
- {handycode-2.1.0 → handycode-2.1.2}/handycode/utils.py +0 -0
- {handycode-2.1.0 → handycode-2.1.2}/handycode.egg-info/SOURCES.txt +0 -0
- {handycode-2.1.0 → handycode-2.1.2}/handycode.egg-info/dependency_links.txt +0 -0
- {handycode-2.1.0 → handycode-2.1.2}/handycode.egg-info/entry_points.txt +0 -0
- {handycode-2.1.0 → handycode-2.1.2}/handycode.egg-info/requires.txt +0 -0
- {handycode-2.1.0 → handycode-2.1.2}/handycode.egg-info/top_level.txt +0 -0
- {handycode-2.1.0 → handycode-2.1.2}/setup.cfg +0 -0
|
@@ -8,7 +8,6 @@ import json
|
|
|
8
8
|
import sys
|
|
9
9
|
import atexit
|
|
10
10
|
import signal
|
|
11
|
-
import shutil
|
|
12
11
|
from pathlib import Path
|
|
13
12
|
from typing import List, Dict, Optional
|
|
14
13
|
from datetime import datetime
|
|
@@ -66,6 +65,7 @@ class HandyCode:
|
|
|
66
65
|
"files_created": [],
|
|
67
66
|
"files_modified": [],
|
|
68
67
|
"files_deleted": [],
|
|
68
|
+
"files_read": [],
|
|
69
69
|
"commands_executed": [],
|
|
70
70
|
"start_time": datetime.now()
|
|
71
71
|
}
|
|
@@ -75,48 +75,46 @@ class HandyCode:
|
|
|
75
75
|
self._interrupt_count = 0
|
|
76
76
|
|
|
77
77
|
def _build_project_context(self):
|
|
78
|
-
"
|
|
79
|
-
context
|
|
80
|
-
context += f"Working directory: {self.project_path}\n"
|
|
81
|
-
context += f"OS: {sys.platform}\n"
|
|
78
|
+
context = f"\n\n=== CURRENT PROJECT ===\n"
|
|
79
|
+
context += f"Directory: {self.project_path}\n"
|
|
82
80
|
|
|
83
81
|
try:
|
|
84
82
|
all_files = []
|
|
85
83
|
for ext in self.file_manager.allowed_extensions:
|
|
86
84
|
all_files.extend(self.project_path.rglob(f"*{ext}"))
|
|
87
|
-
|
|
88
85
|
all_files.extend(self.project_path.rglob("*"))
|
|
89
|
-
all_files = list(set(all_files))
|
|
90
|
-
|
|
91
|
-
files = [f for f in all_files if f.is_file()
|
|
92
|
-
and not any(ex in f.parts for ex in self.file_manager.excluded_dirs)]
|
|
93
|
-
|
|
94
|
-
context += f"\nFiles in project ({len(files)} total):\n"
|
|
95
86
|
|
|
96
|
-
|
|
87
|
+
seen = set()
|
|
88
|
+
files = []
|
|
89
|
+
for f in sorted(all_files):
|
|
90
|
+
if f.is_file() and f not in seen:
|
|
91
|
+
rel = str(f.relative_to(self.project_path))
|
|
92
|
+
if not any(ex in f.parts for ex in self.file_manager.excluded_dirs):
|
|
93
|
+
if not any(rel.startswith(ex) for ex in self.file_manager.excluded_dirs):
|
|
94
|
+
files.append(f)
|
|
95
|
+
seen.add(f)
|
|
96
|
+
|
|
97
|
+
context += f"\nFiles ({len(files)}):\n"
|
|
98
|
+
for file in files:
|
|
97
99
|
try:
|
|
98
100
|
rel_path = file.relative_to(self.project_path)
|
|
99
101
|
size = file.stat().st_size
|
|
100
|
-
context += f"
|
|
102
|
+
context += f" {rel_path} ({self._format_size(size)})\n"
|
|
101
103
|
except:
|
|
102
104
|
pass
|
|
103
105
|
|
|
104
|
-
context += f"\nFile contents
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
for file in sorted(files):
|
|
109
|
-
if total_size >= max_total:
|
|
106
|
+
context += f"\nFile contents:\n"
|
|
107
|
+
total = 0
|
|
108
|
+
for file in files:
|
|
109
|
+
if total > 50000:
|
|
110
110
|
break
|
|
111
|
-
|
|
112
111
|
try:
|
|
113
112
|
content = file.read_text(encoding='utf-8', errors='ignore')
|
|
114
113
|
if len(content) > 3000:
|
|
115
114
|
content = content[:3000] + "\n... (truncated)"
|
|
116
|
-
|
|
117
115
|
rel_path = file.relative_to(self.project_path)
|
|
118
116
|
context += f"\n=== {rel_path} ===\n{content}\n"
|
|
119
|
-
|
|
117
|
+
total += len(content)
|
|
120
118
|
except:
|
|
121
119
|
pass
|
|
122
120
|
|
|
@@ -128,35 +126,69 @@ class HandyCode:
|
|
|
128
126
|
def _format_size(self, size):
|
|
129
127
|
for unit in ['B', 'KB', 'MB', 'GB']:
|
|
130
128
|
if size < 1024:
|
|
131
|
-
return f"{size:.
|
|
129
|
+
return f"{size:.1f}{unit}"
|
|
132
130
|
size /= 1024
|
|
133
|
-
return f"{size:.
|
|
131
|
+
return f"{size:.1f}TB"
|
|
134
132
|
|
|
135
133
|
def _get_system_prompt(self):
|
|
136
|
-
return """You are HandyCode
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
134
|
+
return """You are HandyCode - a powerful AI assistant for file operations and coding.
|
|
135
|
+
|
|
136
|
+
FILE OPERATIONS - USE EXACT FORMAT:
|
|
137
|
+
To create a file, use [[CREATE:path/to/file]] followed by the complete file content on the next lines, then [[END]] to mark end of file:
|
|
138
|
+
[[CREATE:path/to/file.py]]
|
|
139
|
+
import os
|
|
140
|
+
|
|
141
|
+
def main():
|
|
142
|
+
print("Hello World")
|
|
143
|
+
|
|
144
|
+
if __name__ == "__main__":
|
|
145
|
+
main()
|
|
146
|
+
[[END]]
|
|
147
|
+
|
|
148
|
+
To modify a file, use [[MODIFY:path/to/file]] followed by the complete new content, then [[END]]:
|
|
149
|
+
[[MODIFY:path/to/file.py]]
|
|
150
|
+
new complete content here
|
|
151
|
+
[[END]]
|
|
152
|
+
|
|
153
|
+
To delete a file:
|
|
154
|
+
[[DELETE:path/to/file.py]]
|
|
155
|
+
|
|
156
|
+
To read a file:
|
|
157
|
+
[[READ:path/to/file.py]]
|
|
158
|
+
|
|
159
|
+
To list directory:
|
|
160
|
+
[[LIST:path/]]
|
|
161
|
+
|
|
162
|
+
To run a command (requires confirmation):
|
|
163
|
+
[[EXEC:python script.py]]
|
|
164
|
+
|
|
165
|
+
CRITICAL RULES:
|
|
166
|
+
1. ALWAYS put [[END]] after file content for CREATE and MODIFY
|
|
167
|
+
2. Show COMPLETE file content between [[CREATE/MODIFY:...]] and [[END]]
|
|
168
|
+
3. NEVER include comments or explanations INSIDE the file content
|
|
169
|
+
4. Only the actual code goes between [[CREATE:...]] and [[END]]
|
|
170
|
+
5. Explain what you're doing BEFORE the [[CREATE:...]] block
|
|
171
|
+
6. Do NOT put your explanations inside [[CREATE:...]] [[END]] blocks
|
|
172
|
+
7. Files are created/modified automatically without asking
|
|
173
|
+
8. Commands (EXEC) require user confirmation
|
|
174
|
+
|
|
175
|
+
Example of CORRECT format:
|
|
176
|
+
I'll create a Python script for you.
|
|
177
|
+
|
|
178
|
+
[[CREATE:hello.py]]
|
|
179
|
+
print("Hello World")
|
|
180
|
+
[[END]]
|
|
181
|
+
|
|
182
|
+
Now you can run it with: python hello.py
|
|
183
|
+
|
|
184
|
+
Example of WRONG format (DON'T DO THIS):
|
|
185
|
+
[[CREATE:hello.py]]
|
|
186
|
+
Here's your file:
|
|
187
|
+
print("Hello World")
|
|
188
|
+
This file prints hello
|
|
189
|
+
[[END]]
|
|
190
|
+
|
|
191
|
+
Respond in Russian. Write code in English."""
|
|
160
192
|
|
|
161
193
|
def _setup_readline(self):
|
|
162
194
|
if not HAS_READLINE:
|
|
@@ -180,11 +212,13 @@ Speak Russian. Write code in English."""
|
|
|
180
212
|
def reset_interrupt(self):
|
|
181
213
|
self._interrupt_count = 0
|
|
182
214
|
|
|
183
|
-
def
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
215
|
+
def _make_request_streaming(self, data):
|
|
216
|
+
if HAS_REQUESTS:
|
|
217
|
+
return self._make_request_streaming_requests(data)
|
|
218
|
+
else:
|
|
219
|
+
return self._make_request_urllib(data)
|
|
187
220
|
|
|
221
|
+
def _make_request_streaming_requests(self, data):
|
|
188
222
|
try:
|
|
189
223
|
response = requests.post(
|
|
190
224
|
self.api_url,
|
|
@@ -196,9 +230,9 @@ Speak Russian. Write code in English."""
|
|
|
196
230
|
timeout=120,
|
|
197
231
|
stream=True
|
|
198
232
|
)
|
|
233
|
+
response.raise_for_status()
|
|
199
234
|
|
|
200
235
|
full_response = ""
|
|
201
|
-
|
|
202
236
|
for line in response.iter_lines():
|
|
203
237
|
if line:
|
|
204
238
|
line = line.decode('utf-8')
|
|
@@ -212,37 +246,54 @@ Speak Russian. Write code in English."""
|
|
|
212
246
|
delta = chunk['choices'][0].get('delta', {})
|
|
213
247
|
content = delta.get('content', '')
|
|
214
248
|
if content:
|
|
249
|
+
# Показываем только если это не внутри кодового блока
|
|
215
250
|
print(content, end="", flush=True)
|
|
216
251
|
full_response += content
|
|
217
252
|
except:
|
|
218
253
|
continue
|
|
219
|
-
|
|
220
254
|
print()
|
|
221
255
|
return full_response
|
|
222
|
-
|
|
223
256
|
except Exception as e:
|
|
224
257
|
print_error(f"API Error: {e}")
|
|
225
258
|
return ""
|
|
226
259
|
|
|
227
|
-
def
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
260
|
+
def _make_request_urllib(self, data):
|
|
261
|
+
try:
|
|
262
|
+
json_data = json.dumps({**data, "stream": True}).encode('utf-8')
|
|
263
|
+
req = urllib.request.Request(
|
|
264
|
+
self.api_url,
|
|
265
|
+
data=json_data,
|
|
266
|
+
headers={
|
|
267
|
+
"Authorization": f"Bearer {self.api_key}",
|
|
268
|
+
"Content-Type": "application/json"
|
|
269
|
+
},
|
|
270
|
+
method='POST'
|
|
271
|
+
)
|
|
272
|
+
ctx = ssl.create_default_context()
|
|
273
|
+
|
|
274
|
+
full_response = ""
|
|
275
|
+
with urllib.request.urlopen(req, context=ctx, timeout=120) as response:
|
|
276
|
+
for line in response:
|
|
277
|
+
line = line.decode('utf-8').strip()
|
|
278
|
+
if line.startswith('data: '):
|
|
279
|
+
data_str = line[6:]
|
|
280
|
+
if data_str == '[DONE]':
|
|
281
|
+
break
|
|
282
|
+
try:
|
|
283
|
+
chunk = json.loads(data_str)
|
|
284
|
+
if 'choices' in chunk and chunk['choices']:
|
|
285
|
+
delta = chunk['choices'][0].get('delta', {})
|
|
286
|
+
content = delta.get('content', '')
|
|
287
|
+
if content:
|
|
288
|
+
print(content, end="", flush=True)
|
|
289
|
+
full_response += content
|
|
290
|
+
except:
|
|
291
|
+
continue
|
|
292
|
+
print()
|
|
293
|
+
return full_response
|
|
294
|
+
except Exception as e:
|
|
295
|
+
print_error(f"Request error: {e}")
|
|
296
|
+
return ""
|
|
246
297
|
|
|
247
298
|
def send_message(self, user_input):
|
|
248
299
|
if user_input.startswith('/'):
|
|
@@ -250,6 +301,9 @@ Speak Russian. Write code in English."""
|
|
|
250
301
|
|
|
251
302
|
self.conversation_history.append({"role": "user", "content": user_input})
|
|
252
303
|
|
|
304
|
+
if len(self.conversation_history) > 20:
|
|
305
|
+
self.conversation_history = [self.conversation_history[0]] + self.conversation_history[-19:]
|
|
306
|
+
|
|
253
307
|
payload = {
|
|
254
308
|
"model": self.current_model,
|
|
255
309
|
"messages": self.conversation_history,
|
|
@@ -259,14 +313,14 @@ Speak Russian. Write code in English."""
|
|
|
259
313
|
|
|
260
314
|
try:
|
|
261
315
|
print_info(f"\nDEEPSEEK:")
|
|
262
|
-
response = self.
|
|
316
|
+
response = self._make_request_streaming(payload)
|
|
263
317
|
|
|
264
318
|
if response:
|
|
265
319
|
self.conversation_history.append({"role": "assistant", "content": response})
|
|
266
320
|
|
|
267
321
|
actions = self._parse_actions(response)
|
|
268
322
|
if actions:
|
|
269
|
-
self.
|
|
323
|
+
self._execute_actions(actions)
|
|
270
324
|
|
|
271
325
|
self.stats["messages_sent"] += 1
|
|
272
326
|
return response
|
|
@@ -274,24 +328,46 @@ Speak Russian. Write code in English."""
|
|
|
274
328
|
return print_error(f"Error: {e}")
|
|
275
329
|
|
|
276
330
|
def _parse_actions(self, response):
|
|
277
|
-
"""Парсит все типы действий"""
|
|
278
331
|
actions = []
|
|
279
332
|
|
|
280
|
-
# CREATE
|
|
281
|
-
|
|
333
|
+
# CREATE с [[END]]
|
|
334
|
+
create_pattern = r'\[\[CREATE:(.+?)\]\](.*?)\[\[END\]\]'
|
|
335
|
+
for match in re.finditer(create_pattern, response, re.DOTALL):
|
|
282
336
|
path = match.group(1).strip()
|
|
283
337
|
content = match.group(2).strip()
|
|
284
|
-
|
|
285
|
-
content = re.sub(r'\n
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
338
|
+
# Убираем маркеры кода если есть
|
|
339
|
+
content = re.sub(r'^```[\w]*\n', '', content)
|
|
340
|
+
content = re.sub(r'\n```$', '', content)
|
|
341
|
+
if content:
|
|
342
|
+
actions.append({'type': 'create', 'path': path, 'content': content})
|
|
343
|
+
|
|
344
|
+
# CREATE без [[END]] (старый формат, берём до следующего [[ или конца)
|
|
345
|
+
if not any(a['type'] == 'create' for a in actions):
|
|
346
|
+
old_create = r'\[\[CREATE:(.+?)\]\](.*?)(?=\[\[|$)'
|
|
347
|
+
for match in re.finditer(old_create, response, re.DOTALL):
|
|
348
|
+
path = match.group(1).strip()
|
|
349
|
+
content = match.group(2).strip()
|
|
350
|
+
content = re.sub(r'^```[\w]*\n', '', content)
|
|
351
|
+
content = re.sub(r'\n```$', '', content)
|
|
352
|
+
# Убираем явно не-кодовые строки
|
|
353
|
+
lines = content.split('\n')
|
|
354
|
+
clean_lines = []
|
|
355
|
+
for line in lines:
|
|
356
|
+
if not line.startswith('Here') and not line.startswith('This file') and not line.startswith('Now you'):
|
|
357
|
+
clean_lines.append(line)
|
|
358
|
+
content = '\n'.join(clean_lines).strip()
|
|
359
|
+
if content:
|
|
360
|
+
actions.append({'type': 'create', 'path': path, 'content': content})
|
|
361
|
+
|
|
362
|
+
# MODIFY с [[END]]
|
|
363
|
+
modify_pattern = r'\[\[MODIFY:(.+?)\]\](.*?)\[\[END\]\]'
|
|
364
|
+
for match in re.finditer(modify_pattern, response, re.DOTALL):
|
|
290
365
|
path = match.group(1).strip()
|
|
291
366
|
content = match.group(2).strip()
|
|
292
|
-
content = re.sub(r'^```[\w]*\n
|
|
293
|
-
content = re.sub(r'\n
|
|
294
|
-
|
|
367
|
+
content = re.sub(r'^```[\w]*\n', '', content)
|
|
368
|
+
content = re.sub(r'\n```$', '', content)
|
|
369
|
+
if content:
|
|
370
|
+
actions.append({'type': 'modify', 'path': path, 'content': content})
|
|
295
371
|
|
|
296
372
|
# DELETE
|
|
297
373
|
for match in re.finditer(r'\[\[DELETE:(.+?)\]\]', response):
|
|
@@ -305,46 +381,50 @@ Speak Russian. Write code in English."""
|
|
|
305
381
|
for match in re.finditer(r'\[\[LIST:(.+?)\]\]', response):
|
|
306
382
|
actions.append({'type': 'list', 'path': match.group(1).strip()})
|
|
307
383
|
|
|
308
|
-
# MKDIR
|
|
309
|
-
for match in re.finditer(r'\[\[MKDIR:(.+?)\]\]', response):
|
|
310
|
-
actions.append({'type': 'mkdir', 'path': match.group(1).strip()})
|
|
311
|
-
|
|
312
|
-
# COPY
|
|
313
|
-
for match in re.finditer(r'\[\[COPY:(.+?)\]\]', response):
|
|
314
|
-
actions.append({'type': 'copy', 'path': match.group(1).strip()})
|
|
315
|
-
|
|
316
|
-
# MOVE
|
|
317
|
-
for match in re.finditer(r'\[\[MOVE:(.+?)\]\]', response):
|
|
318
|
-
actions.append({'type': 'move', 'path': match.group(1).strip()})
|
|
319
|
-
|
|
320
384
|
# EXEC
|
|
321
385
|
for match in re.finditer(r'\[\[EXEC:(.+?)\]\]', response):
|
|
322
386
|
actions.append({'type': 'exec', 'command': match.group(1).strip()})
|
|
323
387
|
|
|
324
388
|
return actions
|
|
325
389
|
|
|
326
|
-
def
|
|
327
|
-
"""Умное выполнение: файлы авто, команды с подтверждением"""
|
|
390
|
+
def _execute_actions(self, actions):
|
|
328
391
|
if not actions:
|
|
329
392
|
return
|
|
330
393
|
|
|
331
|
-
file_actions = [a for a in actions if a['type'] in ['create', 'modify', 'delete', '
|
|
394
|
+
file_actions = [a for a in actions if a['type'] in ['create', 'modify', 'delete', 'read', 'list']]
|
|
332
395
|
exec_actions = [a for a in actions if a['type'] == 'exec']
|
|
333
396
|
|
|
334
|
-
# Файловые операции
|
|
397
|
+
# Файловые операции - автоматически, показываем только информацию
|
|
335
398
|
if file_actions:
|
|
336
|
-
print_header("\
|
|
399
|
+
print_header("\nFILE OPERATIONS")
|
|
400
|
+
for i, action in enumerate(file_actions, 1):
|
|
401
|
+
if action['type'] == 'create':
|
|
402
|
+
lines = action['content'].count('\n') + 1
|
|
403
|
+
print(f" {i}. Created: {action['path']} ({lines} lines)")
|
|
404
|
+
elif action['type'] == 'modify':
|
|
405
|
+
lines = action['content'].count('\n') + 1
|
|
406
|
+
print(f" {i}. Modified: {action['path']} ({lines} lines)")
|
|
407
|
+
elif action['type'] == 'delete':
|
|
408
|
+
print(f" {i}. Deleted: {action['path']}")
|
|
409
|
+
elif action['type'] == 'read':
|
|
410
|
+
print(f" {i}. Read: {action['path']}")
|
|
411
|
+
elif action['type'] == 'list':
|
|
412
|
+
print(f" {i}. Listed: {action['path']}")
|
|
413
|
+
|
|
337
414
|
for action in file_actions:
|
|
338
415
|
self._execute_action(action)
|
|
339
416
|
|
|
340
|
-
# Команды требуют подтверждения
|
|
417
|
+
# Команды - требуют подтверждения
|
|
341
418
|
if exec_actions:
|
|
342
|
-
print_header("\nCOMMANDS
|
|
419
|
+
print_header("\nCOMMANDS (confirmation required)")
|
|
343
420
|
for i, action in enumerate(exec_actions, 1):
|
|
344
421
|
print(f" {i}. {action['command']}")
|
|
345
422
|
|
|
346
|
-
|
|
347
|
-
|
|
423
|
+
if self.auto_approve:
|
|
424
|
+
choice = 'A'
|
|
425
|
+
else:
|
|
426
|
+
print("\n[A] Execute all [S] Skip [C] Cancel")
|
|
427
|
+
choice = input("> ").strip().upper()
|
|
348
428
|
|
|
349
429
|
if choice == 'A':
|
|
350
430
|
for action in exec_actions:
|
|
@@ -353,151 +433,78 @@ Speak Russian. Write code in English."""
|
|
|
353
433
|
print_warning("Skipped")
|
|
354
434
|
elif choice == 'C':
|
|
355
435
|
print_warning("Cancelled")
|
|
356
|
-
elif choice.isdigit():
|
|
357
|
-
idx = int(choice) - 1
|
|
358
|
-
if 0 <= idx < len(exec_actions):
|
|
359
|
-
self._execute_action(exec_actions[idx])
|
|
360
436
|
|
|
361
437
|
def _execute_action(self, action):
|
|
362
|
-
"""Выполняет одно действие"""
|
|
363
438
|
try:
|
|
364
439
|
if action['type'] == 'create':
|
|
365
|
-
self.
|
|
366
|
-
|
|
440
|
+
if self.security.is_safe_path(action['path']):
|
|
441
|
+
self.file_manager.create_file(action['path'], action['content'])
|
|
442
|
+
self.stats["files_created"].append(action['path'])
|
|
367
443
|
|
|
368
444
|
elif action['type'] == 'modify':
|
|
369
|
-
self.
|
|
370
|
-
|
|
445
|
+
if self.security.is_safe_path(action['path']):
|
|
446
|
+
self.file_manager.modify_file(action['path'], action['content'])
|
|
447
|
+
self.stats["files_modified"].append(action['path'])
|
|
371
448
|
|
|
372
449
|
elif action['type'] == 'delete':
|
|
373
|
-
self.
|
|
374
|
-
|
|
450
|
+
if self.security.is_safe_path(action['path']):
|
|
451
|
+
self.file_manager.delete_file(action['path'])
|
|
452
|
+
self.stats["files_deleted"].append(action['path'])
|
|
375
453
|
|
|
376
454
|
elif action['type'] == 'read':
|
|
377
|
-
self.
|
|
455
|
+
if self.security.is_safe_path(action['path']):
|
|
456
|
+
self.file_manager.read_file(action['path'])
|
|
457
|
+
self.stats["files_read"].append(action['path'])
|
|
378
458
|
|
|
379
459
|
elif action['type'] == 'list':
|
|
380
|
-
self.
|
|
381
|
-
|
|
382
|
-
elif action['type'] == 'mkdir':
|
|
383
|
-
self._make_directory(action['path'])
|
|
460
|
+
self.file_manager.list_directory(action['path'])
|
|
384
461
|
|
|
385
462
|
elif action['type'] == 'exec':
|
|
386
|
-
self.
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
elif action['type'] == 'copy':
|
|
390
|
-
self._copy_file(action['path'])
|
|
391
|
-
|
|
392
|
-
elif action['type'] == 'move':
|
|
393
|
-
self._move_file(action['path'])
|
|
463
|
+
if self.security.is_safe_command(action['command']):
|
|
464
|
+
self.file_manager.execute_command(action['command'])
|
|
465
|
+
self.stats["commands_executed"].append(action['command'])
|
|
394
466
|
|
|
395
467
|
except Exception as e:
|
|
396
|
-
print_error(f"
|
|
397
|
-
|
|
398
|
-
def _delete_file(self, path):
|
|
399
|
-
"""Удаляет файл"""
|
|
400
|
-
full_path = self.project_path / path
|
|
401
|
-
if not self.security.is_safe_path(str(path)):
|
|
402
|
-
print_error(f"Unsafe: {path}")
|
|
403
|
-
return
|
|
404
|
-
|
|
405
|
-
if full_path.exists():
|
|
406
|
-
# Бэкап перед удалением
|
|
407
|
-
backup = full_path.with_suffix(full_path.suffix + '.bak')
|
|
408
|
-
shutil.copy2(full_path, backup)
|
|
409
|
-
full_path.unlink()
|
|
410
|
-
print_success(f"Deleted: {path} (backup: {backup.name})")
|
|
411
|
-
else:
|
|
412
|
-
print_warning(f"Not found: {path}")
|
|
413
|
-
|
|
414
|
-
def _read_file(self, path):
|
|
415
|
-
"""Читает и показывает файл"""
|
|
416
|
-
full_path = self.project_path / path
|
|
417
|
-
if full_path.exists():
|
|
418
|
-
content = full_path.read_text(encoding='utf-8', errors='ignore')
|
|
419
|
-
print_header(f"\n=== {path} ===")
|
|
420
|
-
print(content)
|
|
421
|
-
print_header("=" * (len(path) + 8))
|
|
422
|
-
else:
|
|
423
|
-
print_warning(f"Not found: {path}")
|
|
424
|
-
|
|
425
|
-
def _list_directory(self, path):
|
|
426
|
-
"""Показывает содержимое директории"""
|
|
427
|
-
full_path = self.project_path / path
|
|
428
|
-
if full_path.exists() and full_path.is_dir():
|
|
429
|
-
items = sorted(full_path.iterdir())
|
|
430
|
-
print_header(f"\n=== {path}/ ({len(items)} items) ===")
|
|
431
|
-
for item in items:
|
|
432
|
-
if item.is_dir():
|
|
433
|
-
print(f" [DIR] {item.name}/")
|
|
434
|
-
else:
|
|
435
|
-
size = item.stat().st_size
|
|
436
|
-
print(f" [FILE] {item.name} ({self._format_size(size)})")
|
|
437
|
-
else:
|
|
438
|
-
print_warning(f"Not found: {path}")
|
|
439
|
-
|
|
440
|
-
def _make_directory(self, path):
|
|
441
|
-
"""Создаёт директорию"""
|
|
442
|
-
full_path = self.project_path / path
|
|
443
|
-
full_path.mkdir(parents=True, exist_ok=True)
|
|
444
|
-
print_success(f"Created dir: {path}")
|
|
445
|
-
|
|
446
|
-
def _copy_file(self, source):
|
|
447
|
-
"""Копирует файл (ожидает, что следом будет CREATE)"""
|
|
448
|
-
print_info(f"Copy source noted: {source}")
|
|
449
|
-
|
|
450
|
-
def _move_file(self, source):
|
|
451
|
-
"""Перемещает файл (ожидает, что следом будет CREATE)"""
|
|
452
|
-
full_path = self.project_path / source
|
|
453
|
-
if full_path.exists():
|
|
454
|
-
print_info(f"Move source noted: {source}")
|
|
455
|
-
else:
|
|
456
|
-
print_warning(f"Source not found: {source}")
|
|
468
|
+
print_error(f"Action failed: {e}")
|
|
457
469
|
|
|
458
470
|
def _handle_command(self, user_input):
|
|
459
|
-
|
|
471
|
+
parts = user_input.split()
|
|
472
|
+
cmd = parts[0].lower()
|
|
473
|
+
|
|
460
474
|
if cmd in ['/help', '/h']:
|
|
461
475
|
print("""
|
|
462
476
|
COMMANDS:
|
|
463
|
-
/help
|
|
464
|
-
/scan
|
|
465
|
-
/models
|
|
466
|
-
/model
|
|
467
|
-
/clear
|
|
468
|
-
/save
|
|
469
|
-
/stats
|
|
470
|
-
/exit
|
|
477
|
+
/help Show help
|
|
478
|
+
/scan Scan project
|
|
479
|
+
/models List models
|
|
480
|
+
/model NAME Switch model
|
|
481
|
+
/clear Clear history
|
|
482
|
+
/save Save session
|
|
483
|
+
/stats Statistics
|
|
484
|
+
/exit Exit
|
|
471
485
|
""")
|
|
472
486
|
elif cmd in ['/scan', '/s']:
|
|
473
487
|
print(self.file_manager.scan_project())
|
|
474
488
|
elif cmd in ['/models', '/m']:
|
|
475
489
|
for name in MODELS:
|
|
476
490
|
print(f" {name}")
|
|
491
|
+
elif cmd in ['/model'] and len(parts) > 1:
|
|
492
|
+
model_name = parts[1]
|
|
493
|
+
if model_name in MODELS:
|
|
494
|
+
self.current_model = MODELS[model_name]
|
|
495
|
+
self.model_settings = get_model_settings(self.current_model)
|
|
496
|
+
print_success(f"Switched to: {model_name}")
|
|
477
497
|
elif cmd in ['/clear', '/c']:
|
|
478
498
|
self.conversation_history = [self.conversation_history[0]]
|
|
479
499
|
print_success("Cleared")
|
|
480
|
-
elif cmd in ['/save']:
|
|
481
|
-
self.file_manager.save_session(self.conversation_history, self.current_model, self.stats)
|
|
482
500
|
elif cmd in ['/stats']:
|
|
483
|
-
duration = datetime.now() - self.stats["start_time"]
|
|
484
501
|
print(f"Messages: {self.stats['messages_sent']}")
|
|
485
|
-
print(f"Created: {len(self.stats['files_created'])}
|
|
486
|
-
print(f"Modified: {len(self.stats['files_modified'])}
|
|
487
|
-
print(f"Deleted: {len(self.stats['files_deleted'])}
|
|
502
|
+
print(f"Created: {len(self.stats['files_created'])}")
|
|
503
|
+
print(f"Modified: {len(self.stats['files_modified'])}")
|
|
504
|
+
print(f"Deleted: {len(self.stats['files_deleted'])}")
|
|
488
505
|
print(f"Commands: {len(self.stats['commands_executed'])}")
|
|
489
|
-
print(f"Duration: {duration}")
|
|
490
506
|
elif cmd in ['/exit', '/q']:
|
|
491
|
-
print_success("Goodbye!")
|
|
492
507
|
os._exit(0)
|
|
493
|
-
elif cmd in ['/model'] and len(user_input.split()) > 1:
|
|
494
|
-
model_name = user_input.split()[1]
|
|
495
|
-
if model_name in MODELS:
|
|
496
|
-
self.current_model = MODELS[model_name]
|
|
497
|
-
self.model_settings = get_model_settings(self.current_model)
|
|
498
|
-
print_success(f"Switched to: {model_name}")
|
|
499
|
-
else:
|
|
500
|
-
print_error(f"Unknown model: {model_name}")
|
|
501
508
|
return ""
|
|
502
509
|
|
|
503
510
|
def execute_command(self, command):
|
|
@@ -508,17 +515,7 @@ COMMANDS:
|
|
|
508
515
|
print()
|
|
509
516
|
print_info(f"Project: {self.project_path}")
|
|
510
517
|
print_info(f"Model: {self.current_model}")
|
|
511
|
-
|
|
512
|
-
try:
|
|
513
|
-
files = list(self.project_path.rglob("*"))
|
|
514
|
-
files = [f for f in files if f.is_file()
|
|
515
|
-
and not any(ex in f.parts for ex in self.file_manager.excluded_dirs)]
|
|
516
|
-
visible = [f for f in files if not f.name.startswith('.')]
|
|
517
|
-
if visible:
|
|
518
|
-
print_info(f"\nFound {len(visible)} files (AI sees all)")
|
|
519
|
-
except:
|
|
520
|
-
pass
|
|
521
|
-
|
|
518
|
+
print_info("Files: auto | Commands: confirmation required")
|
|
522
519
|
print_info("/help for commands\n")
|
|
523
520
|
|
|
524
521
|
while True:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|