handycode 2.3.0__tar.gz → 2.3.1__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.3.0 → handycode-2.3.1}/PKG-INFO +1 -1
  2. {handycode-2.3.0 → handycode-2.3.1}/handycode/__init__.py +1 -1
  3. {handycode-2.3.0 → handycode-2.3.1}/handycode/assistant.py +52 -13
  4. handycode-2.3.1/handycode/config.py +163 -0
  5. {handycode-2.3.0 → handycode-2.3.1}/handycode.egg-info/PKG-INFO +1 -1
  6. {handycode-2.3.0 → handycode-2.3.1}/setup.py +1 -1
  7. handycode-2.3.0/handycode/config.py +0 -78
  8. {handycode-2.3.0 → handycode-2.3.1}/LICENSE +0 -0
  9. {handycode-2.3.0 → handycode-2.3.1}/README.md +0 -0
  10. {handycode-2.3.0 → handycode-2.3.1}/handycode/__main__.py +0 -0
  11. {handycode-2.3.0 → handycode-2.3.1}/handycode/cli.py +0 -0
  12. {handycode-2.3.0 → handycode-2.3.1}/handycode/file_manager.py +0 -0
  13. {handycode-2.3.0 → handycode-2.3.1}/handycode/logo.py +0 -0
  14. {handycode-2.3.0 → handycode-2.3.1}/handycode/main.py +0 -0
  15. {handycode-2.3.0 → handycode-2.3.1}/handycode/models.py +0 -0
  16. {handycode-2.3.0 → handycode-2.3.1}/handycode/project_templates.py +0 -0
  17. {handycode-2.3.0 → handycode-2.3.1}/handycode/security.py +0 -0
  18. {handycode-2.3.0 → handycode-2.3.1}/handycode/utils.py +0 -0
  19. {handycode-2.3.0 → handycode-2.3.1}/handycode.egg-info/SOURCES.txt +0 -0
  20. {handycode-2.3.0 → handycode-2.3.1}/handycode.egg-info/dependency_links.txt +0 -0
  21. {handycode-2.3.0 → handycode-2.3.1}/handycode.egg-info/entry_points.txt +0 -0
  22. {handycode-2.3.0 → handycode-2.3.1}/handycode.egg-info/requires.txt +0 -0
  23. {handycode-2.3.0 → handycode-2.3.1}/handycode.egg-info/top_level.txt +0 -0
  24. {handycode-2.3.0 → handycode-2.3.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: handycode
3
- Version: 2.3.0
3
+ Version: 2.3.1
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.3.0"
6
+ __version__ = "2.3.1"
7
7
  __author__ = "AURA Tec."
8
8
  __license__ = "MIT"
9
9
 
@@ -127,16 +127,26 @@ class HandyCode:
127
127
  self.project_path = project_path
128
128
  self.auto_approve = auto_approve
129
129
  self.config = config or Config()
130
+
131
+ # Этот вызов запросит ключ если его нет
130
132
  self.api_key = self.config.get_api_key()
131
- if not self.api_key:
132
- raise ValueError("API key not found")
133
+
134
+ # Проверяем что ключ не пустой
135
+ if not self.api_key or len(self.api_key) < 10:
136
+ print()
137
+ print(" ❌ API ключ не настроен. HandyCode не может работать без ключа.")
138
+ print(" Получите ключ на https://openrouter.ai/keys")
139
+ print(" и добавьте в ~/.handycode/.env:")
140
+ print(" OPENROUTER_API_KEY=ваш_ключ")
141
+ print()
142
+ sys.exit(1)
143
+
133
144
  self.api_url = "https://openrouter.ai/api/v1/chat/completions"
134
145
  self.current_model = MODELS.get(model, MODELS["deepseek"])
135
146
  self.model_settings = get_model_settings(self.current_model)
136
147
  self.file_manager = FileManager(self.project_path)
137
148
  self.security = SecurityChecker(self.project_path)
138
149
 
139
- # Получаем список установленных пакетов
140
150
  self.installed_packages = self.file_manager.get_installed_packages()
141
151
 
142
152
  project_context = self._build_project_context()
@@ -151,7 +161,7 @@ class HandyCode:
151
161
  }
152
162
  self.stream_buffer = ""
153
163
  self.pending_commands = []
154
- self.command_results = [] # Результаты выполнения команд
164
+ self.command_results = []
155
165
  self._setup_readline()
156
166
  signal.signal(signal.SIGINT, self._signal_handler)
157
167
  self._interrupt_count = 0
@@ -359,14 +369,29 @@ Speak Russian. Write code in English. Code ONLY inside [[CREATE]]...[[END]]."""
359
369
  content = delta.get('content', '')
360
370
  if content:
361
371
  full_response += content
372
+
373
+ # Вход в кодовый блок - скрываем всё
362
374
  if '[[CREATE:' in content or '[[MODIFY:' in content:
363
375
  in_code = True
376
+ continue
377
+
378
+ # Выход из кодового блока
364
379
  if '[[END]]' in content:
365
380
  in_code = False
366
- if not in_code:
367
- clean = content.replace('[[CREATE:', '').replace('[[MODIFY:', '').replace('[[END]]', '').replace('[[EXEC:', '').replace('[[INSTALL:', '').replace(']]', '')
368
- if clean.strip():
369
- print(clean, end="", flush=True)
381
+ continue
382
+
383
+ # Если внутри кода - НИЧЕГО не выводим
384
+ if in_code:
385
+ continue
386
+
387
+ # Очищаем маркеры
388
+ clean = content
389
+ for marker in ['[[CREATE:', '[[MODIFY:', '[[END]]', '[[EXEC:', '[[INSTALL:', '[[DELETE:', '[[READ:', '[[LIST:', ']]']:
390
+ clean = clean.replace(marker, '')
391
+
392
+ if clean.strip():
393
+ print(clean, end="", flush=True)
394
+
370
395
  self._process_stream_chunk(content)
371
396
  except: continue
372
397
  print()
@@ -397,14 +422,29 @@ Speak Russian. Write code in English. Code ONLY inside [[CREATE]]...[[END]]."""
397
422
  content = delta.get('content', '')
398
423
  if content:
399
424
  full_response += content
425
+
426
+ # Вход в кодовый блок - скрываем всё
400
427
  if '[[CREATE:' in content or '[[MODIFY:' in content:
401
428
  in_code = True
429
+ continue
430
+
431
+ # Выход из кодового блока
402
432
  if '[[END]]' in content:
403
433
  in_code = False
404
- if not in_code:
405
- clean = content.replace('[[CREATE:', '').replace('[[MODIFY:', '').replace('[[END]]', '').replace('[[EXEC:', '').replace('[[INSTALL:', '').replace(']]', '')
406
- if clean.strip():
407
- print(clean, end="", flush=True)
434
+ continue
435
+
436
+ # Если внутри кода - НИЧЕГО не выводим
437
+ if in_code:
438
+ continue
439
+
440
+ # Очищаем маркеры
441
+ clean = content
442
+ for marker in ['[[CREATE:', '[[MODIFY:', '[[END]]', '[[EXEC:', '[[INSTALL:', '[[DELETE:', '[[READ:', '[[LIST:', ']]']:
443
+ clean = clean.replace(marker, '')
444
+
445
+ if clean.strip():
446
+ print(clean, end="", flush=True)
447
+
408
448
  self._process_stream_chunk(content)
409
449
  except: continue
410
450
  print()
@@ -459,7 +499,6 @@ Speak Russian. Write code in English. Code ONLY inside [[CREATE]]...[[END]]."""
459
499
  "output": output
460
500
  })
461
501
 
462
- # Показываем ошибки
463
502
  errors = [r for r in self.command_results if not r['success']]
464
503
  if errors:
465
504
  print()
@@ -0,0 +1,163 @@
1
+ """
2
+ Управление конфигурацией HandyCode
3
+ """
4
+
5
+ import os
6
+ import json
7
+ from pathlib import Path
8
+
9
+
10
+ class Config:
11
+ """Управляет конфигурацией HandyCode"""
12
+
13
+ def __init__(self):
14
+ self.config_dir = Path.home() / '.handycode'
15
+ self.config_dir.mkdir(exist_ok=True)
16
+
17
+ self.env_file = self.config_dir / '.env'
18
+ self.config_file = self.config_dir / 'config.json'
19
+
20
+ self.config = self._load_config()
21
+
22
+ def _load_config(self) -> dict:
23
+ default_config = {
24
+ "default_model": "deepseek",
25
+ "auto_approve": False,
26
+ "language": "ru",
27
+ "installed_version": "2.3.0",
28
+ }
29
+
30
+ if self.config_file.exists():
31
+ try:
32
+ with open(self.config_file, encoding='utf-8') as f:
33
+ loaded = json.load(f)
34
+ default_config.update(loaded)
35
+ except:
36
+ pass
37
+
38
+ return default_config
39
+
40
+ def save_config(self):
41
+ with open(self.config_file, 'w', encoding='utf-8') as f:
42
+ json.dump(self.config, f, indent=2, ensure_ascii=False)
43
+
44
+ def get_api_key(self) -> str:
45
+ """Получает API ключ из разных источников, запрашивает если нет"""
46
+ api_key = None
47
+
48
+ # 1. Переменная окружения
49
+ api_key = os.getenv('OPENROUTER_API_KEY')
50
+ if api_key:
51
+ return api_key
52
+
53
+ # 2. .env файл
54
+ if self.env_file.exists():
55
+ try:
56
+ with open(self.env_file, encoding='utf-8') as f:
57
+ for line in f:
58
+ line = line.strip()
59
+ if line.startswith('OPENROUTER_API_KEY='):
60
+ key = line.split('=', 1)[1].strip().strip('"').strip("'")
61
+ if key and not key.startswith('#'):
62
+ api_key = key
63
+ break
64
+ except:
65
+ pass
66
+
67
+ if api_key:
68
+ return api_key
69
+
70
+ # 3. Файл конфигурации
71
+ if 'api_key' in self.config and self.config['api_key']:
72
+ return self.config['api_key']
73
+
74
+ # 4. Запрашиваем у пользователя
75
+ api_key = self._request_api_key()
76
+
77
+ return api_key
78
+
79
+ def _request_api_key(self) -> str:
80
+ """Запрашивает API ключ у пользователя с красивым оформлением"""
81
+ print()
82
+ print("╔══════════════════════════════════════════════════════════════╗")
83
+ print("║ ║")
84
+ print("║ 🔑 API КЛЮЧ НЕ НАЙДЕН ║")
85
+ print("║ ║")
86
+ print("╚══════════════════════════════════════════════════════════════╝")
87
+ print()
88
+ print(" Для работы HandyCode требуется API ключ OpenRouter.")
89
+ print(" Получите его бесплатно на сайте:")
90
+ print()
91
+ print(" https://openrouter.ai/keys")
92
+ print()
93
+ print(" Инструкция:")
94
+ print(" 1. Зарегистрируйтесь на openrouter.ai")
95
+ print(" 2. Перейдите в раздел Keys")
96
+ print(" 3. Создайте новый ключ")
97
+ print(" 4. Скопируйте ключ и вставьте его ниже")
98
+ print()
99
+
100
+ while True:
101
+ api_key = input(" API ключ: ").strip()
102
+
103
+ if not api_key:
104
+ print()
105
+ print(" ⚠ Ключ не может быть пустым. Попробуйте снова.")
106
+ print()
107
+ continue
108
+
109
+ if len(api_key) < 20:
110
+ print()
111
+ print(" ⚠ Ключ слишком короткий. Проверьте ключ.")
112
+ print()
113
+ continue
114
+
115
+ break
116
+
117
+ # Сохраняем ключ
118
+ try:
119
+ # В .env файл
120
+ env_content = ""
121
+ if self.env_file.exists():
122
+ with open(self.env_file, encoding='utf-8') as f:
123
+ env_content = f.read()
124
+
125
+ if 'OPENROUTER_API_KEY=' in env_content:
126
+ lines = env_content.split('\n')
127
+ new_lines = []
128
+ for line in lines:
129
+ if line.startswith('OPENROUTER_API_KEY='):
130
+ new_lines.append(f'OPENROUTER_API_KEY={api_key}')
131
+ else:
132
+ new_lines.append(line)
133
+ env_content = '\n'.join(new_lines)
134
+ else:
135
+ if env_content and not env_content.endswith('\n'):
136
+ env_content += '\n'
137
+ env_content += f'OPENROUTER_API_KEY={api_key}\n'
138
+
139
+ with open(self.env_file, 'w', encoding='utf-8') as f:
140
+ f.write(env_content)
141
+
142
+ try:
143
+ os.chmod(self.env_file, 0o600)
144
+ except:
145
+ pass
146
+
147
+ print()
148
+ print(" ✅ Ключ сохранён в ~/.handycode/.env")
149
+ print()
150
+
151
+ except Exception as e:
152
+ print(f" ⚠ Не удалось сохранить ключ: {e}")
153
+ print(f" Добавьте вручную в {self.env_file}:")
154
+ print(f" OPENROUTER_API_KEY={api_key}")
155
+
156
+ return api_key
157
+
158
+ def get(self, key: str, default=None):
159
+ return self.config.get(key, default)
160
+
161
+ def set(self, key: str, value):
162
+ self.config[key] = value
163
+ self.save_config()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: handycode
3
- Version: 2.3.0
3
+ Version: 2.3.1
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.3.0",
5
+ version="2.3.1",
6
6
  author="AuraTechno",
7
7
  description="AI Code Assistant for DeepSeek",
8
8
  long_description="HandyCode - AI Code Assistant",
@@ -1,78 +0,0 @@
1
- """
2
- Управление конфигурацией HandyCode (без внешних зависимостей)
3
- """
4
-
5
- import os
6
- import json
7
- from pathlib import Path
8
-
9
-
10
- class Config:
11
- """Управляет конфигурацией HandyCode"""
12
-
13
- def __init__(self):
14
- self.config_dir = Path.home() / '.handycode'
15
- self.config_dir.mkdir(exist_ok=True)
16
-
17
- self.env_file = self.config_dir / '.env'
18
- self.config_file = self.config_dir / 'config.json'
19
-
20
- self.config = self._load_config()
21
-
22
- def _load_config(self) -> dict:
23
- default_config = {
24
- "default_model": "deepseek",
25
- "auto_approve": False,
26
- "language": "ru",
27
- "installed_version": "2.0.0",
28
- }
29
-
30
- if self.config_file.exists():
31
- try:
32
- with open(self.config_file, encoding='utf-8') as f:
33
- loaded = json.load(f)
34
- default_config.update(loaded)
35
- except:
36
- pass
37
-
38
- return default_config
39
-
40
- def save_config(self):
41
- with open(self.config_file, 'w', encoding='utf-8') as f:
42
- json.dump(self.config, f, indent=2, ensure_ascii=False)
43
-
44
- def get_api_key(self) -> str:
45
- api_key = os.getenv('OPENROUTER_API_KEY')
46
- if api_key:
47
- return api_key
48
-
49
- if self.env_file.exists():
50
- try:
51
- with open(self.env_file, encoding='utf-8') as f:
52
- for line in f:
53
- if line.startswith('OPENROUTER_API_KEY='):
54
- key = line.split('=', 1)[1].strip()
55
- if key:
56
- return key
57
- except:
58
- pass
59
-
60
- if 'api_key' in self.config and self.config['api_key']:
61
- return self.config['api_key']
62
-
63
- print("\nAPI ключ не найден!")
64
- print("Получите ключ на: https://openrouter.ai/keys")
65
- api_key = input("Введите API ключ: ").strip()
66
-
67
- if api_key:
68
- try:
69
- with open(self.env_file, 'w', encoding='utf-8') as f:
70
- f.write(f'OPENROUTER_API_KEY={api_key}\n')
71
- os.chmod(self.env_file, 0o600)
72
- except:
73
- pass
74
-
75
- return api_key
76
-
77
- def get(self, key: str, default=None):
78
- return self.config.get(key, default)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes