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