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.
- {handycode-2.3.0 → handycode-2.3.1}/PKG-INFO +1 -1
- {handycode-2.3.0 → handycode-2.3.1}/handycode/__init__.py +1 -1
- {handycode-2.3.0 → handycode-2.3.1}/handycode/assistant.py +52 -13
- handycode-2.3.1/handycode/config.py +163 -0
- {handycode-2.3.0 → handycode-2.3.1}/handycode.egg-info/PKG-INFO +1 -1
- {handycode-2.3.0 → handycode-2.3.1}/setup.py +1 -1
- handycode-2.3.0/handycode/config.py +0 -78
- {handycode-2.3.0 → handycode-2.3.1}/LICENSE +0 -0
- {handycode-2.3.0 → handycode-2.3.1}/README.md +0 -0
- {handycode-2.3.0 → handycode-2.3.1}/handycode/__main__.py +0 -0
- {handycode-2.3.0 → handycode-2.3.1}/handycode/cli.py +0 -0
- {handycode-2.3.0 → handycode-2.3.1}/handycode/file_manager.py +0 -0
- {handycode-2.3.0 → handycode-2.3.1}/handycode/logo.py +0 -0
- {handycode-2.3.0 → handycode-2.3.1}/handycode/main.py +0 -0
- {handycode-2.3.0 → handycode-2.3.1}/handycode/models.py +0 -0
- {handycode-2.3.0 → handycode-2.3.1}/handycode/project_templates.py +0 -0
- {handycode-2.3.0 → handycode-2.3.1}/handycode/security.py +0 -0
- {handycode-2.3.0 → handycode-2.3.1}/handycode/utils.py +0 -0
- {handycode-2.3.0 → handycode-2.3.1}/handycode.egg-info/SOURCES.txt +0 -0
- {handycode-2.3.0 → handycode-2.3.1}/handycode.egg-info/dependency_links.txt +0 -0
- {handycode-2.3.0 → handycode-2.3.1}/handycode.egg-info/entry_points.txt +0 -0
- {handycode-2.3.0 → handycode-2.3.1}/handycode.egg-info/requires.txt +0 -0
- {handycode-2.3.0 → handycode-2.3.1}/handycode.egg-info/top_level.txt +0 -0
- {handycode-2.3.0 → handycode-2.3.1}/setup.cfg +0 -0
|
@@ -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
|
-
|
|
132
|
-
|
|
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
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
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
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
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,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
|
|
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
|