handycode 2.1.3__tar.gz → 2.1.6__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.3 → handycode-2.1.6}/PKG-INFO +1 -1
- {handycode-2.1.3 → handycode-2.1.6}/handycode/__init__.py +1 -1
- handycode-2.1.6/handycode/assistant.py +469 -0
- {handycode-2.1.3 → handycode-2.1.6}/handycode/logo.py +17 -18
- handycode-2.1.6/handycode/utils.py +140 -0
- {handycode-2.1.3 → handycode-2.1.6}/handycode.egg-info/PKG-INFO +1 -1
- {handycode-2.1.3 → handycode-2.1.6}/setup.py +1 -1
- handycode-2.1.3/handycode/assistant.py +0 -530
- handycode-2.1.3/handycode/utils.py +0 -92
- {handycode-2.1.3 → handycode-2.1.6}/LICENSE +0 -0
- {handycode-2.1.3 → handycode-2.1.6}/README.md +0 -0
- {handycode-2.1.3 → handycode-2.1.6}/handycode/__main__.py +0 -0
- {handycode-2.1.3 → handycode-2.1.6}/handycode/cli.py +0 -0
- {handycode-2.1.3 → handycode-2.1.6}/handycode/config.py +0 -0
- {handycode-2.1.3 → handycode-2.1.6}/handycode/file_manager.py +0 -0
- {handycode-2.1.3 → handycode-2.1.6}/handycode/main.py +0 -0
- {handycode-2.1.3 → handycode-2.1.6}/handycode/models.py +0 -0
- {handycode-2.1.3 → handycode-2.1.6}/handycode/project_templates.py +0 -0
- {handycode-2.1.3 → handycode-2.1.6}/handycode/security.py +0 -0
- {handycode-2.1.3 → handycode-2.1.6}/handycode.egg-info/SOURCES.txt +0 -0
- {handycode-2.1.3 → handycode-2.1.6}/handycode.egg-info/dependency_links.txt +0 -0
- {handycode-2.1.3 → handycode-2.1.6}/handycode.egg-info/entry_points.txt +0 -0
- {handycode-2.1.3 → handycode-2.1.6}/handycode.egg-info/requires.txt +0 -0
- {handycode-2.1.3 → handycode-2.1.6}/handycode.egg-info/top_level.txt +0 -0
- {handycode-2.1.3 → handycode-2.1.6}/setup.cfg +0 -0
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Основной класс ассистента HandyCode
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import re
|
|
7
|
+
import json
|
|
8
|
+
import sys
|
|
9
|
+
import atexit
|
|
10
|
+
import signal
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import List, Dict, Optional
|
|
13
|
+
from datetime import datetime
|
|
14
|
+
|
|
15
|
+
try:
|
|
16
|
+
import readline
|
|
17
|
+
|
|
18
|
+
HAS_READLINE = True
|
|
19
|
+
except ImportError:
|
|
20
|
+
HAS_READLINE = False
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
import requests
|
|
24
|
+
|
|
25
|
+
HAS_REQUESTS = True
|
|
26
|
+
except ImportError:
|
|
27
|
+
HAS_REQUESTS = False
|
|
28
|
+
import urllib.request
|
|
29
|
+
import urllib.error
|
|
30
|
+
import ssl
|
|
31
|
+
|
|
32
|
+
from handycode.config import Config
|
|
33
|
+
from handycode.models import MODELS, get_model_settings
|
|
34
|
+
from handycode.file_manager import FileManager
|
|
35
|
+
from handycode.security import SecurityChecker
|
|
36
|
+
from handycode.utils import (
|
|
37
|
+
Colors, print_colored, print_header, print_success,
|
|
38
|
+
print_error, print_warning, print_info, print_logo,
|
|
39
|
+
print_divider, print_file_action, print_command, print_status
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class HandyCode:
|
|
44
|
+
def __init__(self, project_path, model="deepseek", auto_approve=False, config=None):
|
|
45
|
+
self.project_path = project_path
|
|
46
|
+
self.auto_approve = auto_approve
|
|
47
|
+
self.config = config or Config()
|
|
48
|
+
|
|
49
|
+
self.api_key = self.config.get_api_key()
|
|
50
|
+
if not self.api_key:
|
|
51
|
+
raise ValueError("API key not found")
|
|
52
|
+
|
|
53
|
+
self.api_url = "https://openrouter.ai/api/v1/chat/completions"
|
|
54
|
+
self.current_model = MODELS.get(model, MODELS["deepseek"])
|
|
55
|
+
self.model_settings = get_model_settings(self.current_model)
|
|
56
|
+
|
|
57
|
+
self.file_manager = FileManager(self.project_path)
|
|
58
|
+
self.security = SecurityChecker(self.project_path)
|
|
59
|
+
|
|
60
|
+
project_context = self._build_project_context()
|
|
61
|
+
|
|
62
|
+
self.conversation_history = [
|
|
63
|
+
{"role": "system", "content": self._get_system_prompt() + project_context}
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
self.stats = {
|
|
67
|
+
"messages_sent": 0,
|
|
68
|
+
"files_created": [],
|
|
69
|
+
"files_modified": [],
|
|
70
|
+
"files_deleted": [],
|
|
71
|
+
"files_read": [],
|
|
72
|
+
"commands_executed": [],
|
|
73
|
+
"start_time": datetime.now()
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
self.stream_buffer = ""
|
|
77
|
+
self.pending_commands = []
|
|
78
|
+
|
|
79
|
+
self._setup_readline()
|
|
80
|
+
signal.signal(signal.SIGINT, self._signal_handler)
|
|
81
|
+
self._interrupt_count = 0
|
|
82
|
+
|
|
83
|
+
def _build_project_context(self):
|
|
84
|
+
context = f"\n\n=== CURRENT PROJECT ===\n"
|
|
85
|
+
context += f"Directory: {self.project_path}\n"
|
|
86
|
+
try:
|
|
87
|
+
all_files = []
|
|
88
|
+
for ext in self.file_manager.allowed_extensions:
|
|
89
|
+
all_files.extend(self.project_path.rglob(f"*{ext}"))
|
|
90
|
+
all_files.extend(self.project_path.rglob("*"))
|
|
91
|
+
|
|
92
|
+
seen = set()
|
|
93
|
+
files = []
|
|
94
|
+
for f in sorted(all_files):
|
|
95
|
+
if f.is_file() and f not in seen:
|
|
96
|
+
rel = str(f.relative_to(self.project_path))
|
|
97
|
+
if not any(ex in f.parts for ex in self.file_manager.excluded_dirs):
|
|
98
|
+
if not any(rel.startswith(ex) for ex in self.file_manager.excluded_dirs):
|
|
99
|
+
files.append(f)
|
|
100
|
+
seen.add(f)
|
|
101
|
+
|
|
102
|
+
context += f"\nFiles ({len(files)}):\n"
|
|
103
|
+
for file in files:
|
|
104
|
+
try:
|
|
105
|
+
rel_path = file.relative_to(self.project_path)
|
|
106
|
+
size = file.stat().st_size
|
|
107
|
+
context += f" {rel_path} ({self._format_size(size)})\n"
|
|
108
|
+
except:
|
|
109
|
+
pass
|
|
110
|
+
except:
|
|
111
|
+
pass
|
|
112
|
+
return context
|
|
113
|
+
|
|
114
|
+
def _format_size(self, size):
|
|
115
|
+
for unit in ['B', 'KB', 'MB', 'GB']:
|
|
116
|
+
if size < 1024:
|
|
117
|
+
return f"{size:.1f}{unit}"
|
|
118
|
+
size /= 1024
|
|
119
|
+
return f"{size:.1f}TB"
|
|
120
|
+
|
|
121
|
+
def _get_system_prompt(self):
|
|
122
|
+
return """You are HandyCode - AI coding assistant. Create/modify/delete files and run commands.
|
|
123
|
+
|
|
124
|
+
FORMAT:
|
|
125
|
+
[[CREATE:path/file]]
|
|
126
|
+
code here
|
|
127
|
+
[[END]]
|
|
128
|
+
|
|
129
|
+
[[MODIFY:path/file]]
|
|
130
|
+
new code here
|
|
131
|
+
[[END]]
|
|
132
|
+
|
|
133
|
+
[[EXEC:command]]
|
|
134
|
+
|
|
135
|
+
RULES:
|
|
136
|
+
1. CREATE + EXEC in ONE response
|
|
137
|
+
2. Use [[END]] after file content
|
|
138
|
+
3. NO comments inside [[CREATE]]...[[END]]
|
|
139
|
+
4. Explanations BEFORE [[CREATE]] blocks
|
|
140
|
+
5. Files create automatically, commands need confirmation
|
|
141
|
+
|
|
142
|
+
Speak Russian. Write code in English."""
|
|
143
|
+
|
|
144
|
+
def _setup_readline(self):
|
|
145
|
+
if not HAS_READLINE:
|
|
146
|
+
return
|
|
147
|
+
try:
|
|
148
|
+
histfile = os.path.join(os.path.expanduser("~"), ".handycode", "history")
|
|
149
|
+
os.makedirs(os.path.dirname(histfile), exist_ok=True)
|
|
150
|
+
readline.read_history_file(histfile)
|
|
151
|
+
readline.set_history_length(1000)
|
|
152
|
+
atexit.register(readline.write_history_file, histfile)
|
|
153
|
+
except:
|
|
154
|
+
pass
|
|
155
|
+
|
|
156
|
+
def _signal_handler(self, sig, frame):
|
|
157
|
+
self._interrupt_count += 1
|
|
158
|
+
if self._interrupt_count == 1:
|
|
159
|
+
print("\n\n ⚠ Press Ctrl+C again to exit")
|
|
160
|
+
else:
|
|
161
|
+
os._exit(0)
|
|
162
|
+
|
|
163
|
+
def reset_interrupt(self):
|
|
164
|
+
self._interrupt_count = 0
|
|
165
|
+
|
|
166
|
+
def _process_stream_chunk(self, chunk):
|
|
167
|
+
self.stream_buffer += chunk
|
|
168
|
+
|
|
169
|
+
# CREATE файлы в реальном времени
|
|
170
|
+
while True:
|
|
171
|
+
match = re.search(r'\[\[CREATE:(.+?)\]\](.*?)\[\[END\]\]', self.stream_buffer, re.DOTALL)
|
|
172
|
+
if match:
|
|
173
|
+
path = match.group(1).strip()
|
|
174
|
+
content = match.group(2).strip()
|
|
175
|
+
content = re.sub(r'^```[\w]*\n', '', content)
|
|
176
|
+
content = re.sub(r'\n```$', '', content)
|
|
177
|
+
|
|
178
|
+
if content and self.security.is_safe_path(path):
|
|
179
|
+
self.file_manager.create_file(path, content)
|
|
180
|
+
self.stats["files_created"].append(path)
|
|
181
|
+
lines = content.count('\n') + 1
|
|
182
|
+
print_file_action('create', path, f"({lines} lines)")
|
|
183
|
+
|
|
184
|
+
self.stream_buffer = self.stream_buffer[match.end():]
|
|
185
|
+
else:
|
|
186
|
+
break
|
|
187
|
+
|
|
188
|
+
# MODIFY файлы
|
|
189
|
+
while True:
|
|
190
|
+
match = re.search(r'\[\[MODIFY:(.+?)\]\](.*?)\[\[END\]\]', self.stream_buffer, re.DOTALL)
|
|
191
|
+
if match:
|
|
192
|
+
path = match.group(1).strip()
|
|
193
|
+
content = match.group(2).strip()
|
|
194
|
+
content = re.sub(r'^```[\w]*\n', '', content)
|
|
195
|
+
content = re.sub(r'\n```$', '', content)
|
|
196
|
+
|
|
197
|
+
if content and self.security.is_safe_path(path):
|
|
198
|
+
self.file_manager.modify_file(path, content)
|
|
199
|
+
self.stats["files_modified"].append(path)
|
|
200
|
+
lines = content.count('\n') + 1
|
|
201
|
+
print_file_action('modify', path, f"({lines} lines)")
|
|
202
|
+
|
|
203
|
+
self.stream_buffer = self.stream_buffer[match.end():]
|
|
204
|
+
else:
|
|
205
|
+
break
|
|
206
|
+
|
|
207
|
+
# EXEC команды
|
|
208
|
+
while True:
|
|
209
|
+
match = re.search(r'\[\[EXEC:(.+?)\]\]', self.stream_buffer)
|
|
210
|
+
if match:
|
|
211
|
+
self.pending_commands.append(match.group(1).strip())
|
|
212
|
+
self.stream_buffer = self.stream_buffer[match.end():]
|
|
213
|
+
else:
|
|
214
|
+
break
|
|
215
|
+
|
|
216
|
+
def _make_request_streaming(self, data):
|
|
217
|
+
self.stream_buffer = ""
|
|
218
|
+
self.pending_commands = []
|
|
219
|
+
|
|
220
|
+
if HAS_REQUESTS:
|
|
221
|
+
return self._stream_requests(data)
|
|
222
|
+
else:
|
|
223
|
+
return self._stream_urllib(data)
|
|
224
|
+
|
|
225
|
+
def _stream_requests(self, data):
|
|
226
|
+
try:
|
|
227
|
+
response = requests.post(
|
|
228
|
+
self.api_url,
|
|
229
|
+
headers={
|
|
230
|
+
"Authorization": f"Bearer {self.api_key}",
|
|
231
|
+
"Content-Type": "application/json"
|
|
232
|
+
},
|
|
233
|
+
json={**data, "stream": True},
|
|
234
|
+
timeout=120,
|
|
235
|
+
stream=True
|
|
236
|
+
)
|
|
237
|
+
response.raise_for_status()
|
|
238
|
+
|
|
239
|
+
full_response = ""
|
|
240
|
+
in_code = False
|
|
241
|
+
|
|
242
|
+
for line in response.iter_lines():
|
|
243
|
+
if line:
|
|
244
|
+
line = line.decode('utf-8')
|
|
245
|
+
if line.startswith('data: '):
|
|
246
|
+
data_str = line[6:]
|
|
247
|
+
if data_str.strip() == '[DONE]':
|
|
248
|
+
break
|
|
249
|
+
try:
|
|
250
|
+
chunk = json.loads(data_str)
|
|
251
|
+
if 'choices' in chunk and chunk['choices']:
|
|
252
|
+
delta = chunk['choices'][0].get('delta', {})
|
|
253
|
+
content = delta.get('content', '')
|
|
254
|
+
if content:
|
|
255
|
+
full_response += content
|
|
256
|
+
|
|
257
|
+
if '[[CREATE:' in content or '[[MODIFY:' in content:
|
|
258
|
+
in_code = True
|
|
259
|
+
if '[[END]]' in content:
|
|
260
|
+
in_code = False
|
|
261
|
+
|
|
262
|
+
if not in_code:
|
|
263
|
+
clean = content.replace('[[CREATE:', '').replace('[[MODIFY:', '').replace(
|
|
264
|
+
'[[END]]', '').replace('[[EXEC:', '').replace(']]', '')
|
|
265
|
+
if clean.strip():
|
|
266
|
+
print(clean, end="", flush=True)
|
|
267
|
+
|
|
268
|
+
self._process_stream_chunk(content)
|
|
269
|
+
except:
|
|
270
|
+
continue
|
|
271
|
+
|
|
272
|
+
print()
|
|
273
|
+
return full_response
|
|
274
|
+
except Exception as e:
|
|
275
|
+
print_error(f"API Error: {e}")
|
|
276
|
+
return ""
|
|
277
|
+
|
|
278
|
+
def _stream_urllib(self, data):
|
|
279
|
+
try:
|
|
280
|
+
json_data = json.dumps({**data, "stream": True}).encode('utf-8')
|
|
281
|
+
req = urllib.request.Request(
|
|
282
|
+
self.api_url,
|
|
283
|
+
data=json_data,
|
|
284
|
+
headers={
|
|
285
|
+
"Authorization": f"Bearer {self.api_key}",
|
|
286
|
+
"Content-Type": "application/json"
|
|
287
|
+
},
|
|
288
|
+
method='POST'
|
|
289
|
+
)
|
|
290
|
+
ctx = ssl.create_default_context()
|
|
291
|
+
|
|
292
|
+
full_response = ""
|
|
293
|
+
in_code = False
|
|
294
|
+
|
|
295
|
+
with urllib.request.urlopen(req, context=ctx, timeout=120) as response:
|
|
296
|
+
for line in response:
|
|
297
|
+
line = line.decode('utf-8').strip()
|
|
298
|
+
if line.startswith('data: '):
|
|
299
|
+
data_str = line[6:]
|
|
300
|
+
if data_str == '[DONE]':
|
|
301
|
+
break
|
|
302
|
+
try:
|
|
303
|
+
chunk = json.loads(data_str)
|
|
304
|
+
if 'choices' in chunk and chunk['choices']:
|
|
305
|
+
delta = chunk['choices'][0].get('delta', {})
|
|
306
|
+
content = delta.get('content', '')
|
|
307
|
+
if content:
|
|
308
|
+
full_response += content
|
|
309
|
+
|
|
310
|
+
if '[[CREATE:' in content or '[[MODIFY:' in content:
|
|
311
|
+
in_code = True
|
|
312
|
+
if '[[END]]' in content:
|
|
313
|
+
in_code = False
|
|
314
|
+
|
|
315
|
+
if not in_code:
|
|
316
|
+
clean = content.replace('[[CREATE:', '').replace('[[MODIFY:', '').replace(
|
|
317
|
+
'[[END]]', '').replace('[[EXEC:', '').replace(']]', '')
|
|
318
|
+
if clean.strip():
|
|
319
|
+
print(clean, end="", flush=True)
|
|
320
|
+
|
|
321
|
+
self._process_stream_chunk(content)
|
|
322
|
+
except:
|
|
323
|
+
continue
|
|
324
|
+
print()
|
|
325
|
+
return full_response
|
|
326
|
+
except Exception as e:
|
|
327
|
+
print_error(f"Request error: {e}")
|
|
328
|
+
return ""
|
|
329
|
+
|
|
330
|
+
def send_message(self, user_input):
|
|
331
|
+
if user_input.startswith('/'):
|
|
332
|
+
return self._handle_command(user_input)
|
|
333
|
+
|
|
334
|
+
self.conversation_history.append({"role": "user", "content": user_input})
|
|
335
|
+
|
|
336
|
+
if len(self.conversation_history) > 20:
|
|
337
|
+
self.conversation_history = [self.conversation_history[0]] + self.conversation_history[-19:]
|
|
338
|
+
|
|
339
|
+
payload = {
|
|
340
|
+
"model": self.current_model,
|
|
341
|
+
"messages": self.conversation_history,
|
|
342
|
+
"temperature": self.model_settings.get("temperature", 0.3),
|
|
343
|
+
"max_tokens": self.model_settings.get("max_tokens", 8000),
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
try:
|
|
347
|
+
print_divider("─", 60, Colors.BRIGHT_BLACK)
|
|
348
|
+
print(colorize(" HandyCode", Colors.BRIGHT_CYAN + Colors.BOLD))
|
|
349
|
+
print_divider("─", 60, Colors.BRIGHT_BLACK)
|
|
350
|
+
|
|
351
|
+
response = self._make_request_streaming(payload)
|
|
352
|
+
|
|
353
|
+
if response:
|
|
354
|
+
self.conversation_history.append({"role": "assistant", "content": response})
|
|
355
|
+
|
|
356
|
+
if self.pending_commands:
|
|
357
|
+
print()
|
|
358
|
+
print_divider("─", 60, Colors.BRIGHT_BLACK)
|
|
359
|
+
print(colorize(" ⚡ Commands (confirmation required):", Colors.YELLOW))
|
|
360
|
+
for i, cmd in enumerate(self.pending_commands, 1):
|
|
361
|
+
print_command(cmd, i)
|
|
362
|
+
|
|
363
|
+
if self.auto_approve:
|
|
364
|
+
choice = 'A'
|
|
365
|
+
else:
|
|
366
|
+
print()
|
|
367
|
+
print(colorize(f" {Colors.BRIGHT_BLACK}[A] Execute all [S] Skip [C] Cancel{Colors.RESET}",
|
|
368
|
+
Colors.BRIGHT_BLACK))
|
|
369
|
+
choice = input(colorize(" > ", Colors.WHITE)).strip().upper()
|
|
370
|
+
|
|
371
|
+
if choice == 'A':
|
|
372
|
+
print()
|
|
373
|
+
for cmd in self.pending_commands:
|
|
374
|
+
if self.security.is_safe_command(cmd):
|
|
375
|
+
print_status(f"Running: {cmd}")
|
|
376
|
+
self.file_manager.execute_command(cmd)
|
|
377
|
+
self.stats["commands_executed"].append(cmd)
|
|
378
|
+
|
|
379
|
+
self.stats["messages_sent"] += 1
|
|
380
|
+
return response
|
|
381
|
+
except Exception as e:
|
|
382
|
+
return print_error(f"Error: {e}")
|
|
383
|
+
|
|
384
|
+
def _handle_command(self, user_input):
|
|
385
|
+
parts = user_input.split()
|
|
386
|
+
cmd = parts[0].lower()
|
|
387
|
+
|
|
388
|
+
if cmd in ['/help', '/h']:
|
|
389
|
+
print()
|
|
390
|
+
print(colorize(" Commands:", Colors.BRIGHT_CYAN + Colors.BOLD))
|
|
391
|
+
print_divider("─", 40, Colors.BRIGHT_BLACK)
|
|
392
|
+
print(colorize(" /help Show this help", Colors.WHITE))
|
|
393
|
+
print(colorize(" /scan Scan project files", Colors.WHITE))
|
|
394
|
+
print(colorize(" /models List AI models", Colors.WHITE))
|
|
395
|
+
print(colorize(" /model NAME Switch model", Colors.WHITE))
|
|
396
|
+
print(colorize(" /clear Clear chat history", Colors.WHITE))
|
|
397
|
+
print(colorize(" /save Save session to file", Colors.WHITE))
|
|
398
|
+
print(colorize(" /stats Show statistics", Colors.WHITE))
|
|
399
|
+
print(colorize(" /exit Exit program", Colors.WHITE))
|
|
400
|
+
print()
|
|
401
|
+
elif cmd in ['/scan', '/s']:
|
|
402
|
+
print()
|
|
403
|
+
print(colorize(" Project Files:", Colors.BRIGHT_CYAN + Colors.BOLD))
|
|
404
|
+
print_divider("─", 40, Colors.BRIGHT_BLACK)
|
|
405
|
+
print(self.file_manager.scan_project())
|
|
406
|
+
elif cmd in ['/models', '/m']:
|
|
407
|
+
print()
|
|
408
|
+
print(colorize(" Available Models:", Colors.BRIGHT_CYAN + Colors.BOLD))
|
|
409
|
+
print_divider("─", 40, Colors.BRIGHT_BLACK)
|
|
410
|
+
for name in MODELS:
|
|
411
|
+
marker = colorize(" (current)", Colors.GREEN) if MODELS[name] == self.current_model else ""
|
|
412
|
+
print(f" • {name}{marker}")
|
|
413
|
+
elif cmd in ['/model'] and len(parts) > 1:
|
|
414
|
+
model_name = parts[1]
|
|
415
|
+
if model_name in MODELS:
|
|
416
|
+
self.current_model = MODELS[model_name]
|
|
417
|
+
self.model_settings = get_model_settings(self.current_model)
|
|
418
|
+
print_success(f"Switched to {model_name}")
|
|
419
|
+
elif cmd in ['/clear', '/c']:
|
|
420
|
+
self.conversation_history = [self.conversation_history[0]]
|
|
421
|
+
print_success("Chat history cleared")
|
|
422
|
+
elif cmd in ['/stats']:
|
|
423
|
+
print()
|
|
424
|
+
print(colorize(" Session Stats:", Colors.BRIGHT_CYAN + Colors.BOLD))
|
|
425
|
+
print_divider("─", 40, Colors.BRIGHT_BLACK)
|
|
426
|
+
print(colorize(f" Messages: {self.stats['messages_sent']}", Colors.WHITE))
|
|
427
|
+
print(colorize(f" Created: {len(self.stats['files_created'])} files", Colors.GREEN))
|
|
428
|
+
print(colorize(f" Modified: {len(self.stats['files_modified'])} files", Colors.YELLOW))
|
|
429
|
+
print(colorize(f" Deleted: {len(self.stats['files_deleted'])} files", Colors.RED))
|
|
430
|
+
print(colorize(f" Commands: {len(self.stats['commands_executed'])}", Colors.CYAN))
|
|
431
|
+
elif cmd in ['/exit', '/q']:
|
|
432
|
+
print_success("Goodbye!")
|
|
433
|
+
os._exit(0)
|
|
434
|
+
return ""
|
|
435
|
+
|
|
436
|
+
def execute_command(self, command):
|
|
437
|
+
self.send_message(command)
|
|
438
|
+
|
|
439
|
+
def run(self):
|
|
440
|
+
print_logo()
|
|
441
|
+
print_divider("─", 60, Colors.BRIGHT_BLACK)
|
|
442
|
+
print(colorize(f" 📁 Project: {self.project_path}", Colors.WHITE))
|
|
443
|
+
print(colorize(f" 🤖 Model: {self.current_model}", Colors.WHITE))
|
|
444
|
+
print(colorize(f" {Colors.BRIGHT_BLACK}/help for commands{Colors.RESET}", Colors.BRIGHT_BLACK))
|
|
445
|
+
print_divider("─", 60, Colors.BRIGHT_BLACK)
|
|
446
|
+
print()
|
|
447
|
+
|
|
448
|
+
while True:
|
|
449
|
+
try:
|
|
450
|
+
self.reset_interrupt()
|
|
451
|
+
user_input = input(colorize(" ❯ ", Colors.BRIGHT_CYAN + Colors.BOLD)).strip()
|
|
452
|
+
if user_input:
|
|
453
|
+
self.send_message(user_input)
|
|
454
|
+
except KeyboardInterrupt:
|
|
455
|
+
continue
|
|
456
|
+
except EOFError:
|
|
457
|
+
break
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
# Добавляем colorize как глобальную функцию для удобства
|
|
461
|
+
def colorize(text, color):
|
|
462
|
+
if supports_color():
|
|
463
|
+
return f"{color}{text}{Colors.RESET}"
|
|
464
|
+
return text
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
def supports_color():
|
|
468
|
+
from handycode.utils import supports_color as sc
|
|
469
|
+
return sc()
|
|
@@ -51,7 +51,6 @@ def get_logo_plain() -> str:
|
|
|
51
51
|
║ ║
|
|
52
52
|
║ AI Ассистент для разработки ║
|
|
53
53
|
║ Prod. by AURA Tec. ║
|
|
54
|
-
║ 2.1.2 ║
|
|
55
54
|
║ ║
|
|
56
55
|
╚══════════════════════════════════════════════════════════════╝
|
|
57
56
|
"""
|
|
@@ -61,10 +60,10 @@ def get_logo_plain() -> str:
|
|
|
61
60
|
def get_small_logo() -> str:
|
|
62
61
|
"""Возвращает маленький логотип"""
|
|
63
62
|
if not supports_color():
|
|
64
|
-
return "HandyCode v2.
|
|
63
|
+
return "HandyCode v2.1.3"
|
|
65
64
|
|
|
66
65
|
C = Colors
|
|
67
|
-
return f"{C.CYAN}HandyCode{C.RESET} {C.WHITE}v2.
|
|
66
|
+
return f"{C.CYAN}HandyCode{C.RESET} {C.WHITE}v2.1.3{C.RESET} - {C.GREEN}AI Ассистент{C.RESET} | {C.BRIGHT_BLACK}Prod. by AURA Tec.{C.RESET}"
|
|
68
67
|
|
|
69
68
|
|
|
70
69
|
def get_install_logo() -> str:
|
|
@@ -74,19 +73,19 @@ def get_install_logo() -> str:
|
|
|
74
73
|
|
|
75
74
|
C = Colors
|
|
76
75
|
logo = f"""
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
76
|
+
{C.CYAN}{C.BOLD}╔═════════════════════════════════════════════════════════════════════════════════════════════════╗
|
|
77
|
+
║ ║
|
|
78
|
+
║ {C.YELLOW}██╗ ██╗{C.CYAN} {C.GREEN}█████╗{C.CYAN} {C.BLUE}███╗ ██╗{C.CYAN} {C.MAGENTA}██████╗{C.CYAN} {C.RED}██╗ ██╗{C.CYAN} {C.WHITE}██████╗{C.CYAN} {C.GREEN}███████╗{C.CYAN} {C.BLUE}██████╗{C.CYAN} {C.MAGENTA}███████╗{C.CYAN} ║
|
|
79
|
+
║ {C.YELLOW}██║ ██║{C.CYAN} {C.GREEN}██╔══██╗{C.CYAN} {C.BLUE}████╗ ██║{C.CYAN} {C.MAGENTA}██╔══██╗{C.CYAN} {C.RED}╚██╗ ██╔╝{C.CYAN} {C.WHITE}██╔════╝{C.CYAN} {C.GREEN}██╔════██╗{C.CYAN} {C.BLUE}██╔══██╗{C.CYAN} {C.MAGENTA}██╔════╝{C.CYAN} ║
|
|
80
|
+
║ {C.YELLOW}███████║{C.CYAN} {C.GREEN}███████║{C.CYAN} {C.BLUE}██╔██╗ ██║{C.CYAN} {C.MAGENTA}██║ ██║{C.CYAN} {C.RED}╚████╔╝{C.CYAN} {C.WHITE}██║{C.CYAN} {C.GREEN}██║ ██║{C.CYAN} {C.BLUE}██║ ██║{C.CYAN} {C.MAGENTA}█████╗{C.CYAN} ║
|
|
81
|
+
║ {C.YELLOW}██╔══██║{C.CYAN} {C.GREEN}██╔══██║{C.CYAN} {C.BLUE}██║╚██╗██║{C.CYAN} {C.MAGENTA}██║ ██║{C.CYAN} {C.RED}╚██╔╝{C.CYAN} {C.WHITE}██║{C.CYAN} {C.GREEN}██║ ██║{C.CYAN} {C.BLUE}██║ ██║{C.CYAN} {C.MAGENTA}██╔══╝{C.CYAN} ║
|
|
82
|
+
║ {C.YELLOW}██║ ██║{C.CYAN} {C.GREEN}██║ ██║{C.CYAN} {C.BLUE}██║ ╚████║{C.CYAN} {C.MAGENTA}██████╔╝{C.CYAN} {C.RED}██║{C.CYAN} {C.WHITE}╚██████╗{C.CYAN} {C.GREEN}███████╔╝{C.CYAN} {C.BLUE}██████╔╝{C.CYAN} {C.MAGENTA}███████╗{C.CYAN} ║
|
|
83
|
+
║ {C.YELLOW}╚═╝ ╚═╝{C.CYAN} {C.GREEN}╚═╝ ╚═╝{C.CYAN} {C.BLUE}╚═╝ ╚═══╝{C.CYAN} {C.MAGENTA}╚═════╝{C.CYAN} {C.RED}╚═╝{C.CYAN} {C.WHITE} ╚═════╝{C.CYAN} {C.GREEN}╚═════╝{C.CYAN} {C.BLUE}╚═════╝{C.CYAN} {C.MAGENTA}╚══════╝{C.CYAN} ║
|
|
84
|
+
║ ║
|
|
85
|
+
║ {C.WHITE}{C.BOLD}УСТАНОВКА HANDYCODE{C.CYAN} ║
|
|
86
|
+
║ {C.WHITE}AI Ассистент для разработки{C.CYAN} ║
|
|
87
|
+
║ {C.WHITE}Prod. by AURA Tec.{C.CYAN} ║
|
|
88
|
+
║ ║
|
|
89
|
+
╚═════════════════════════════════════════════════════════════════════════════════════════════════╝{C.RESET}
|
|
90
|
+
"""
|
|
92
91
|
return logo
|