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.
Files changed (25) hide show
  1. {handycode-2.1.3 → handycode-2.1.6}/PKG-INFO +1 -1
  2. {handycode-2.1.3 → handycode-2.1.6}/handycode/__init__.py +1 -1
  3. handycode-2.1.6/handycode/assistant.py +469 -0
  4. {handycode-2.1.3 → handycode-2.1.6}/handycode/logo.py +17 -18
  5. handycode-2.1.6/handycode/utils.py +140 -0
  6. {handycode-2.1.3 → handycode-2.1.6}/handycode.egg-info/PKG-INFO +1 -1
  7. {handycode-2.1.3 → handycode-2.1.6}/setup.py +1 -1
  8. handycode-2.1.3/handycode/assistant.py +0 -530
  9. handycode-2.1.3/handycode/utils.py +0 -92
  10. {handycode-2.1.3 → handycode-2.1.6}/LICENSE +0 -0
  11. {handycode-2.1.3 → handycode-2.1.6}/README.md +0 -0
  12. {handycode-2.1.3 → handycode-2.1.6}/handycode/__main__.py +0 -0
  13. {handycode-2.1.3 → handycode-2.1.6}/handycode/cli.py +0 -0
  14. {handycode-2.1.3 → handycode-2.1.6}/handycode/config.py +0 -0
  15. {handycode-2.1.3 → handycode-2.1.6}/handycode/file_manager.py +0 -0
  16. {handycode-2.1.3 → handycode-2.1.6}/handycode/main.py +0 -0
  17. {handycode-2.1.3 → handycode-2.1.6}/handycode/models.py +0 -0
  18. {handycode-2.1.3 → handycode-2.1.6}/handycode/project_templates.py +0 -0
  19. {handycode-2.1.3 → handycode-2.1.6}/handycode/security.py +0 -0
  20. {handycode-2.1.3 → handycode-2.1.6}/handycode.egg-info/SOURCES.txt +0 -0
  21. {handycode-2.1.3 → handycode-2.1.6}/handycode.egg-info/dependency_links.txt +0 -0
  22. {handycode-2.1.3 → handycode-2.1.6}/handycode.egg-info/entry_points.txt +0 -0
  23. {handycode-2.1.3 → handycode-2.1.6}/handycode.egg-info/requires.txt +0 -0
  24. {handycode-2.1.3 → handycode-2.1.6}/handycode.egg-info/top_level.txt +0 -0
  25. {handycode-2.1.3 → handycode-2.1.6}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: handycode
3
- Version: 2.1.3
3
+ Version: 2.1.6
4
4
  Summary: AI Code Assistant for DeepSeek
5
5
  Home-page: https://github.com/AuraTechno/HandyCode
6
6
  Author: AuraTechno
@@ -3,7 +3,7 @@ HandyCode - AI Ассистент для разработки
3
3
  Аналог Claude Code для командной строки
4
4
  """
5
5
 
6
- __version__ = "2.1.3"
6
+ __version__ = "2.1.6"
7
7
  __author__ = "AURA Tec."
8
8
  __license__ = "MIT"
9
9
 
@@ -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.0.0"
63
+ return "HandyCode v2.1.3"
65
64
 
66
65
  C = Colors
67
- return f"{C.CYAN}HandyCode{C.RESET} {C.WHITE}v2.0.0{C.RESET} - {C.GREEN}AI Ассистент{C.RESET}"
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
- {C.CYAN}{C.BOLD}╔═════════════════════════════════════════════════════════════════════════════════════════════════╗
78
- ║ ║
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
- ║ {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} ║
85
- ║ ║
86
- {C.WHITE}AI Ассистент для разработки{C.CYAN}
87
- {C.WHITE}Prod. by AURA Tec.{C.CYAN}
88
- {C.WHITE}2.1.3{C.CYAN}
89
- ║ ║
90
- ╚═════════════════════════════════════════════════════════════════════════════════════════════════╝{C.RESET}
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