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.
Files changed (24) hide show
  1. {handycode-2.1.6 → handycode-2.2.0}/PKG-INFO +1 -1
  2. {handycode-2.1.6 → handycode-2.2.0}/handycode/__init__.py +1 -1
  3. {handycode-2.1.6 → handycode-2.2.0}/handycode/assistant.py +99 -206
  4. {handycode-2.1.6 → handycode-2.2.0}/handycode/logo.py +20 -39
  5. handycode-2.2.0/handycode/utils.py +140 -0
  6. {handycode-2.1.6 → handycode-2.2.0}/handycode.egg-info/PKG-INFO +1 -1
  7. {handycode-2.1.6 → handycode-2.2.0}/setup.py +1 -1
  8. handycode-2.1.6/handycode/utils.py +0 -140
  9. {handycode-2.1.6 → handycode-2.2.0}/LICENSE +0 -0
  10. {handycode-2.1.6 → handycode-2.2.0}/README.md +0 -0
  11. {handycode-2.1.6 → handycode-2.2.0}/handycode/__main__.py +0 -0
  12. {handycode-2.1.6 → handycode-2.2.0}/handycode/cli.py +0 -0
  13. {handycode-2.1.6 → handycode-2.2.0}/handycode/config.py +0 -0
  14. {handycode-2.1.6 → handycode-2.2.0}/handycode/file_manager.py +0 -0
  15. {handycode-2.1.6 → handycode-2.2.0}/handycode/main.py +0 -0
  16. {handycode-2.1.6 → handycode-2.2.0}/handycode/models.py +0 -0
  17. {handycode-2.1.6 → handycode-2.2.0}/handycode/project_templates.py +0 -0
  18. {handycode-2.1.6 → handycode-2.2.0}/handycode/security.py +0 -0
  19. {handycode-2.1.6 → handycode-2.2.0}/handycode.egg-info/SOURCES.txt +0 -0
  20. {handycode-2.1.6 → handycode-2.2.0}/handycode.egg-info/dependency_links.txt +0 -0
  21. {handycode-2.1.6 → handycode-2.2.0}/handycode.egg-info/entry_points.txt +0 -0
  22. {handycode-2.1.6 → handycode-2.2.0}/handycode.egg-info/requires.txt +0 -0
  23. {handycode-2.1.6 → handycode-2.2.0}/handycode.egg-info/top_level.txt +0 -0
  24. {handycode-2.1.6 → 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.6
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.6"
6
+ __version__ = "2.2.0"
7
7
  __author__ = "AURA Tec."
8
8
  __license__ = "MIT"
9
9
 
@@ -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, print_command, print_status
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
- "files_created": [],
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
- pass
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
- 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."""
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 ⚠ Press Ctrl+C again to exit")
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
- lines = content.count('\n') + 1
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
- lines = content.count('\n') + 1
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.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
- )
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.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
- )
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
- with urllib.request.urlopen(req, context=ctx, timeout=120) as response:
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
- print_divider("─", 60, Colors.BRIGHT_BLACK)
348
- print(colorize(" HandyCode", Colors.BRIGHT_CYAN + Colors.BOLD))
349
- print_divider("", 60, Colors.BRIGHT_BLACK)
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
- 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
-
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
- 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
-
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"Running: {cmd}")
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
- 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()
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
- 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())
313
+ print_section("📁 Проект", self.file_manager.scan_project().split('\n'))
406
314
  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}")
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
- model_name = parts[1]
415
- if model_name in MODELS:
416
- self.current_model = MODELS[model_name]
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"Switched to {model_name}")
325
+ print_success(f"Модель изменена на {name}")
419
326
  elif cmd in ['/clear', '/c']:
420
327
  self.conversation_history = [self.conversation_history[0]]
421
- print_success("Chat history cleared")
328
+ print_success("История очищена")
422
329
  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))
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("Goodbye!")
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, 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)
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(" ❯ ", Colors.BRIGHT_CYAN + Colors.BOLD)).strip()
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}{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,51 +27,35 @@ 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
- ║ ║
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} - {C.GREEN}AI Ассистент{C.RESET} | {C.BRIGHT_BLACK}Prod. by AURA Tec.{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
- logo = f"""
76
- {C.CYAN}{C.BOLD}╔═════════════════════════════════════════════════════════════════════════════════════════════════╗
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}{C.BOLD}УСТАНОВКА HANDYCODE{C.CYAN} ║
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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: handycode
3
- Version: 2.1.6
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.6",
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,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