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