handycode 2.1.0__py3-none-any.whl
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/__init__.py +13 -0
- handycode/__main__.py +5 -0
- handycode/assistant.py +533 -0
- handycode/cli.py +130 -0
- handycode/config.py +78 -0
- handycode/file_manager.py +238 -0
- handycode/logo.py +90 -0
- handycode/main.py +26 -0
- handycode/models.py +77 -0
- handycode/project_templates.py +55 -0
- handycode/security.py +103 -0
- handycode/utils.py +92 -0
- handycode-2.1.0.dist-info/METADATA +20 -0
- handycode-2.1.0.dist-info/RECORD +18 -0
- handycode-2.1.0.dist-info/WHEEL +5 -0
- handycode-2.1.0.dist-info/entry_points.txt +3 -0
- handycode-2.1.0.dist-info/licenses/LICENSE +21 -0
- handycode-2.1.0.dist-info/top_level.txt +1 -0
handycode/config.py
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
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)
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Управление файлами для HandyCode
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import shutil
|
|
7
|
+
import subprocess
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import List, Dict, Optional
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
|
|
12
|
+
from handycode.utils import print_success, print_error, print_warning, print_info
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class FileManager:
|
|
16
|
+
"""Управляет файловыми операциями HandyCode"""
|
|
17
|
+
|
|
18
|
+
def __init__(self, project_root: Path):
|
|
19
|
+
"""Инициализация файлового менеджера"""
|
|
20
|
+
self.project_root = Path(project_root).resolve()
|
|
21
|
+
self.allowed_extensions = {
|
|
22
|
+
'.html', '.css', '.scss', '.sass', '.less',
|
|
23
|
+
'.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs',
|
|
24
|
+
'.vue', '.svelte', '.astro',
|
|
25
|
+
'.py', '.pyi', '.pyx', '.pxd',
|
|
26
|
+
'.json', '.yaml', '.yml', '.toml', '.ini', '.cfg',
|
|
27
|
+
'.xml', '.env', '.gitignore', '.dockerignore',
|
|
28
|
+
'.md', '.mdx', '.rst', '.txt', '.log',
|
|
29
|
+
'.sql', '.sh', '.bash', '.zsh', '.bat', '.ps1',
|
|
30
|
+
'.java', '.kt', '.scala',
|
|
31
|
+
'.cpp', '.c', '.h', '.hpp', '.cs',
|
|
32
|
+
'.rs', '.go', '.rb', '.php', '.swift',
|
|
33
|
+
'.dart', '.r', '.jl', '.lua',
|
|
34
|
+
'.dockerfile', '.makefile', '.cmake',
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
self.excluded_dirs = {
|
|
38
|
+
'node_modules', '__pycache__', '.git', '.svn',
|
|
39
|
+
'venv', '.venv', 'env', '.env',
|
|
40
|
+
'dist', 'build', '.next', '.nuxt',
|
|
41
|
+
'target', 'out', '.idea', '.vscode',
|
|
42
|
+
'.DS_Store', 'Thumbs.db',
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
def scan_project(self) -> str:
|
|
46
|
+
"""Сканирует проект и возвращает структуру"""
|
|
47
|
+
if not self.project_root.exists():
|
|
48
|
+
return ""
|
|
49
|
+
|
|
50
|
+
try:
|
|
51
|
+
lines = [f"📁 Проект: {self.project_root.name}"]
|
|
52
|
+
|
|
53
|
+
# Собираем все файлы
|
|
54
|
+
all_files = []
|
|
55
|
+
for ext in self.allowed_extensions:
|
|
56
|
+
all_files.extend(self.project_root.rglob(f"*{ext}"))
|
|
57
|
+
|
|
58
|
+
# Фильтруем исключённые
|
|
59
|
+
files = [
|
|
60
|
+
f for f in all_files
|
|
61
|
+
if f.is_file() and not any(ex in f.parts for ex in self.excluded_dirs)
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
lines.append(f"📄 Файлов: {len(files)}")
|
|
65
|
+
|
|
66
|
+
# Строим дерево
|
|
67
|
+
if files:
|
|
68
|
+
lines.append("\n📂 Структура:")
|
|
69
|
+
tree = self._build_tree(files)
|
|
70
|
+
lines.extend(self._print_tree(tree))
|
|
71
|
+
|
|
72
|
+
# Ключевые файлы
|
|
73
|
+
key_files = [
|
|
74
|
+
'package.json', 'requirements.txt', 'Dockerfile',
|
|
75
|
+
'README.md', 'setup.py', 'pyproject.toml',
|
|
76
|
+
'tsconfig.json', '.gitignore', 'docker-compose.yml',
|
|
77
|
+
'main.py', 'app.py', 'index.js', 'server.js'
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
for file in files:
|
|
81
|
+
if file.name in key_files:
|
|
82
|
+
try:
|
|
83
|
+
content = file.read_text(encoding='utf-8')
|
|
84
|
+
if len(content) < 5000:
|
|
85
|
+
lines.append(f"\n{'=' * 40}")
|
|
86
|
+
lines.append(f"📄 {file.relative_to(self.project_root)}")
|
|
87
|
+
lines.append('=' * 40)
|
|
88
|
+
lines.append(content)
|
|
89
|
+
except:
|
|
90
|
+
pass
|
|
91
|
+
|
|
92
|
+
return '\n'.join(lines)
|
|
93
|
+
except Exception as e:
|
|
94
|
+
return f"Ошибка сканирования: {e}"
|
|
95
|
+
|
|
96
|
+
def _build_tree(self, files: List[Path]) -> dict:
|
|
97
|
+
"""Строит дерево директорий"""
|
|
98
|
+
tree = {}
|
|
99
|
+
for file in files:
|
|
100
|
+
try:
|
|
101
|
+
relative = file.relative_to(self.project_root)
|
|
102
|
+
parts = relative.parts
|
|
103
|
+
current = tree
|
|
104
|
+
for part in parts[:-1]:
|
|
105
|
+
if part not in current:
|
|
106
|
+
current[part] = {}
|
|
107
|
+
current = current[part]
|
|
108
|
+
|
|
109
|
+
if '__files__' not in current:
|
|
110
|
+
current['__files__'] = []
|
|
111
|
+
current['__files__'].append(parts[-1])
|
|
112
|
+
except:
|
|
113
|
+
pass
|
|
114
|
+
return tree
|
|
115
|
+
|
|
116
|
+
def _print_tree(self, node: dict, indent: str = "") -> List[str]:
|
|
117
|
+
"""Выводит дерево директорий"""
|
|
118
|
+
result = []
|
|
119
|
+
# Сначала директории
|
|
120
|
+
dirs = sorted([k for k in node if k != '__files__'])
|
|
121
|
+
for d in dirs:
|
|
122
|
+
result.append(f"{indent}📁 {d}/")
|
|
123
|
+
result.extend(self._print_tree(node[d], indent + " "))
|
|
124
|
+
# Потом файлы
|
|
125
|
+
if '__files__' in node:
|
|
126
|
+
for f in sorted(node['__files__']):
|
|
127
|
+
result.append(f"{indent}📄 {f}")
|
|
128
|
+
return result
|
|
129
|
+
|
|
130
|
+
def create_file(self, path: str, content: str) -> bool:
|
|
131
|
+
"""Создаёт файл"""
|
|
132
|
+
try:
|
|
133
|
+
full_path = self.project_root / path
|
|
134
|
+
|
|
135
|
+
# Создаём директории
|
|
136
|
+
full_path.parent.mkdir(parents=True, exist_ok=True)
|
|
137
|
+
|
|
138
|
+
# Резервная копия если файл существует
|
|
139
|
+
if full_path.exists():
|
|
140
|
+
backup = full_path.with_suffix(full_path.suffix + '.bak')
|
|
141
|
+
shutil.copy2(full_path, backup)
|
|
142
|
+
print_warning(f"📦 Резервная копия: {backup.name}")
|
|
143
|
+
|
|
144
|
+
# Записываем файл
|
|
145
|
+
full_path.write_text(content, encoding='utf-8')
|
|
146
|
+
|
|
147
|
+
lines = content.count('\n') + 1
|
|
148
|
+
size = len(content)
|
|
149
|
+
print_success(f"✅ Создан: {path} ({lines} строк, {size} байт)")
|
|
150
|
+
|
|
151
|
+
return True
|
|
152
|
+
except Exception as e:
|
|
153
|
+
print_error(f"Ошибка создания {path}: {e}")
|
|
154
|
+
return False
|
|
155
|
+
|
|
156
|
+
def modify_file(self, path: str, content: str) -> bool:
|
|
157
|
+
"""Изменяет файл"""
|
|
158
|
+
try:
|
|
159
|
+
full_path = self.project_root / path
|
|
160
|
+
|
|
161
|
+
if not full_path.exists():
|
|
162
|
+
print_warning(f"Файл не существует, создаём: {path}")
|
|
163
|
+
return self.create_file(path, content)
|
|
164
|
+
|
|
165
|
+
# Резервная копия
|
|
166
|
+
backup = full_path.with_suffix(full_path.suffix + '.bak')
|
|
167
|
+
shutil.copy2(full_path, backup)
|
|
168
|
+
print_warning(f"📦 Резервная копия: {backup.name}")
|
|
169
|
+
|
|
170
|
+
# Записываем новое содержимое
|
|
171
|
+
full_path.write_text(content, encoding='utf-8')
|
|
172
|
+
|
|
173
|
+
lines = content.count('\n') + 1
|
|
174
|
+
print_success(f"✅ Изменён: {path} ({lines} строк)")
|
|
175
|
+
|
|
176
|
+
return True
|
|
177
|
+
except Exception as e:
|
|
178
|
+
print_error(f"Ошибка изменения {path}: {e}")
|
|
179
|
+
return False
|
|
180
|
+
|
|
181
|
+
def execute_command(self, command: str, timeout: int = 300) -> bool:
|
|
182
|
+
"""Выполняет команду"""
|
|
183
|
+
try:
|
|
184
|
+
print_warning(f"\n⚡ Выполнение: {command}")
|
|
185
|
+
|
|
186
|
+
result = subprocess.run(
|
|
187
|
+
command,
|
|
188
|
+
shell=True,
|
|
189
|
+
cwd=self.project_root,
|
|
190
|
+
capture_output=True,
|
|
191
|
+
text=True,
|
|
192
|
+
timeout=timeout
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
if result.stdout:
|
|
196
|
+
print(result.stdout)
|
|
197
|
+
|
|
198
|
+
if result.returncode != 0:
|
|
199
|
+
if result.stderr:
|
|
200
|
+
print_error(result.stderr)
|
|
201
|
+
print_error(f"Команда завершилась с ошибкой (код {result.returncode})")
|
|
202
|
+
return False
|
|
203
|
+
|
|
204
|
+
print_success("✅ Команда выполнена успешно")
|
|
205
|
+
return True
|
|
206
|
+
|
|
207
|
+
except subprocess.TimeoutExpired:
|
|
208
|
+
print_error(f"Таймаут выполнения ({timeout}с)")
|
|
209
|
+
return False
|
|
210
|
+
except Exception as e:
|
|
211
|
+
print_error(f"Ошибка выполнения: {e}")
|
|
212
|
+
return False
|
|
213
|
+
|
|
214
|
+
def save_session(self, history: List[Dict], model: str, stats: Dict) -> str:
|
|
215
|
+
"""Сохраняет сессию в файл"""
|
|
216
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
217
|
+
filename = f"handycode_сессия_{timestamp}.md"
|
|
218
|
+
|
|
219
|
+
try:
|
|
220
|
+
with open(filename, 'w', encoding='utf-8') as f:
|
|
221
|
+
f.write(f"# Сессия HandyCode\n\n")
|
|
222
|
+
f.write(f"**Дата:** {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
|
|
223
|
+
f.write(f"**Модель:** {model}\n")
|
|
224
|
+
f.write(f"**Сообщений:** {stats.get('messages_sent', 0)}\n\n")
|
|
225
|
+
f.write("---\n\n")
|
|
226
|
+
|
|
227
|
+
for msg in history[1:]: # Пропускаем системный промпт
|
|
228
|
+
if msg['role'] == 'user':
|
|
229
|
+
f.write(f"## 👤 Пользователь\n\n{msg['content']}\n\n")
|
|
230
|
+
else:
|
|
231
|
+
clean = msg['content']
|
|
232
|
+
# Убираем маркеры действий для читаемости
|
|
233
|
+
clean = clean.replace('[[CREATE:', '').replace('[[MODIFY:', '').replace('[[EXEC:', '')
|
|
234
|
+
f.write(f"## 🤖 Ассистент\n\n{clean}\n\n---\n\n")
|
|
235
|
+
|
|
236
|
+
return print_success(f"✅ Сессия сохранена: {filename}")
|
|
237
|
+
except Exception as e:
|
|
238
|
+
return print_error(f"Ошибка сохранения: {e}")
|
handycode/logo.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Логотип HandyCode для командной строки
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from .utils import Colors, supports_color
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_logo() -> str:
|
|
10
|
+
"""Возвращает ASCII логотип HandyCode"""
|
|
11
|
+
if not supports_color():
|
|
12
|
+
return get_logo_plain()
|
|
13
|
+
|
|
14
|
+
C = Colors
|
|
15
|
+
logo = f"""
|
|
16
|
+
{C.CYAN}{C.BOLD}╔═════════════════════════════════════════════════════════════════════════════════════════════════╗
|
|
17
|
+
║ ║
|
|
18
|
+
║ {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
|
+
║ {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} ║
|
|
20
|
+
║ {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} ║
|
|
21
|
+
║ {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} ║
|
|
22
|
+
║ {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} ║
|
|
23
|
+
║ {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} ║
|
|
24
|
+
║ ║
|
|
25
|
+
║ {C.WHITE}AI Ассистент для разработки{C.CYAN} ║
|
|
26
|
+
║ {C.WHITE}Prod. by AURA Tec.{C.CYAN} ║
|
|
27
|
+
║ ║
|
|
28
|
+
╚═════════════════════════════════════════════════════════════════════════════════════════════════╝{C.RESET}
|
|
29
|
+
"""
|
|
30
|
+
return logo
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
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
|
+
╚══════════════════════════════════════════════════════════════╝
|
|
56
|
+
"""
|
|
57
|
+
return logo
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def get_small_logo() -> str:
|
|
61
|
+
"""Возвращает маленький логотип"""
|
|
62
|
+
if not supports_color():
|
|
63
|
+
return "HandyCode v2.0.0"
|
|
64
|
+
|
|
65
|
+
C = Colors
|
|
66
|
+
return f"{C.CYAN}HandyCode{C.RESET} {C.WHITE}v2.0.0{C.RESET} - {C.GREEN}AI Ассистент{C.RESET}"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def get_install_logo() -> str:
|
|
70
|
+
"""Возвращает логотип для установки"""
|
|
71
|
+
if not supports_color():
|
|
72
|
+
return get_logo_plain()
|
|
73
|
+
|
|
74
|
+
C = Colors
|
|
75
|
+
logo = f"""
|
|
76
|
+
{C.CYAN}{C.BOLD}╔═════════════════════════════════════════════════════════════════════════════════════════════════╗
|
|
77
|
+
║ ║
|
|
78
|
+
║ {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
|
+
║ {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
|
+
║ ║
|
|
85
|
+
║ {C.WHITE}AI Ассистент для разработки{C.CYAN} ║
|
|
86
|
+
║ {C.WHITE}Prod. by AURA Tec.{C.CYAN} ║
|
|
87
|
+
║ ║
|
|
88
|
+
╚═════════════════════════════════════════════════════════════════════════════════════════════════╝{C.RESET}
|
|
89
|
+
"""
|
|
90
|
+
return logo
|
handycode/main.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Главная точка входа HandyCode"""
|
|
3
|
+
|
|
4
|
+
import sys
|
|
5
|
+
import os
|
|
6
|
+
|
|
7
|
+
if __package__ is None:
|
|
8
|
+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
9
|
+
|
|
10
|
+
from handycode.cli import main as cli_main
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def main():
|
|
14
|
+
"""Главная точка входа"""
|
|
15
|
+
try:
|
|
16
|
+
cli_main()
|
|
17
|
+
except KeyboardInterrupt:
|
|
18
|
+
print("\n\n👋 До свидания!")
|
|
19
|
+
sys.exit(0)
|
|
20
|
+
except Exception as e:
|
|
21
|
+
print(f"\n❌ Критическая ошибка: {e}")
|
|
22
|
+
sys.exit(1)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
if __name__ == "__main__":
|
|
26
|
+
main()
|
handycode/models.py
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Конфигурация AI моделей
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
MODELS = {
|
|
6
|
+
# DeepSeek модели
|
|
7
|
+
"deepseek": "deepseek/deepseek-chat",
|
|
8
|
+
"deepseek-coder": "deepseek/deepseek-coder",
|
|
9
|
+
"deepseek-r1": "deepseek/deepseek-r1",
|
|
10
|
+
"deepseek-r1-distill": "deepseek/deepseek-r1-distill-qwen-32b",
|
|
11
|
+
|
|
12
|
+
# OpenAI модели
|
|
13
|
+
"gpt4": "openai/gpt-4-turbo-preview",
|
|
14
|
+
"gpt4o": "openai/gpt-4o",
|
|
15
|
+
"gpt3": "openai/gpt-3.5-turbo",
|
|
16
|
+
|
|
17
|
+
# Anthropic модели
|
|
18
|
+
"claude": "anthropic/claude-3-opus",
|
|
19
|
+
"claude-sonnet": "anthropic/claude-3-sonnet",
|
|
20
|
+
"claude-haiku": "anthropic/claude-3-haiku",
|
|
21
|
+
|
|
22
|
+
# Google модели
|
|
23
|
+
"gemini": "google/gemini-pro",
|
|
24
|
+
"gemini-flash": "google/gemini-flash-1.5",
|
|
25
|
+
|
|
26
|
+
# Meta модели
|
|
27
|
+
"llama": "meta-llama/llama-3-70b-instruct",
|
|
28
|
+
"llama-8b": "meta-llama/llama-3-8b-instruct",
|
|
29
|
+
|
|
30
|
+
# Другие модели
|
|
31
|
+
"mixtral": "mistralai/mixtral-8x7b-instruct",
|
|
32
|
+
"codellama": "codellama/codellama-70b-instruct",
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
MODEL_SETTINGS = {
|
|
36
|
+
"deepseek/deepseek-chat": {
|
|
37
|
+
"temperature": 1,
|
|
38
|
+
"max_tokens": 8000,
|
|
39
|
+
"description": "DeepSeek V3 - Лучший баланс скорости и качества"
|
|
40
|
+
},
|
|
41
|
+
"deepseek/deepseek-coder": {
|
|
42
|
+
"temperature": 1,
|
|
43
|
+
"max_tokens": 8000,
|
|
44
|
+
"description": "DeepSeek Coder - Специализирована для написания кода"
|
|
45
|
+
},
|
|
46
|
+
"deepseek/deepseek-r1": {
|
|
47
|
+
"temperature": 1,
|
|
48
|
+
"max_tokens": 4000,
|
|
49
|
+
"description": "DeepSeek R1 - Глубокое мышление и анализ"
|
|
50
|
+
},
|
|
51
|
+
"openai/gpt-4-turbo-preview": {
|
|
52
|
+
"temperature": 0.3,
|
|
53
|
+
"max_tokens": 4000,
|
|
54
|
+
"description": "GPT-4 Turbo - Мощная универсальная модель"
|
|
55
|
+
},
|
|
56
|
+
"anthropic/claude-3-opus": {
|
|
57
|
+
"temperature": 0.3,
|
|
58
|
+
"max_tokens": 4000,
|
|
59
|
+
"description": "Claude 3 Opus - Продвинутый анализ"
|
|
60
|
+
},
|
|
61
|
+
"anthropic/claude-3-sonnet": {
|
|
62
|
+
"temperature": 0.3,
|
|
63
|
+
"max_tokens": 4000,
|
|
64
|
+
"description": "Claude 3 Sonnet - Быстрая и способная"
|
|
65
|
+
},
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
DEFAULT_SETTINGS = {
|
|
69
|
+
"temperature": 0.3,
|
|
70
|
+
"max_tokens": 4000,
|
|
71
|
+
"description": "Универсальная модель"
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def get_model_settings(model_id: str) -> dict:
|
|
76
|
+
"""Получает настройки для конкретной модели"""
|
|
77
|
+
return MODEL_SETTINGS.get(model_id, DEFAULT_SETTINGS)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Шаблоны проектов для быстрой настройки
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Dict
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
TEMPLATES = {
|
|
9
|
+
'python': {
|
|
10
|
+
'description': 'Шаблон Python проекта',
|
|
11
|
+
'files': {
|
|
12
|
+
'src/__init__.py': '',
|
|
13
|
+
'src/main.py': '"""Главный модуль"""\n\n\ndef main():\n print("Привет, Мир!")\n\n\nif __name__ == "__main__":\n main()\n',
|
|
14
|
+
'tests/__init__.py': '',
|
|
15
|
+
'tests/test_main.py': '"""Тесты"""\n\nfrom src.main import main\n\n\ndef test_main():\n assert main() is None\n',
|
|
16
|
+
'requirements.txt': '# Зависимости\n',
|
|
17
|
+
'README.md': '# Проект\n\n## Установка\n\n```bash\npip install -r requirements.txt\n```\n\n## Запуск\n\n```bash\npython src/main.py\n```\n',
|
|
18
|
+
'.gitignore': '__pycache__/\n*.pyc\n.env\nvenv/\ndist/\n',
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
'react': {
|
|
22
|
+
'description': 'React с Vite',
|
|
23
|
+
'files': {
|
|
24
|
+
'src/App.jsx': 'import React from "react";\n\nexport default function App() {\n return <h1>Привет, Мир!</h1>;\n}\n',
|
|
25
|
+
'src/main.jsx': 'import React from "react";\nimport ReactDOM from "react-dom/client";\nimport App from "./App";\n\nReactDOM.createRoot(document.getElementById("root")).render(\n <React.StrictMode>\n <App />\n </React.StrictMode>\n);\n',
|
|
26
|
+
'index.html': '<!DOCTYPE html>\n<html>\n<head>\n <meta charset="UTF-8" />\n <title>React App</title>\n</head>\n<body>\n <div id="root"></div>\n <script type="module" src="/src/main.jsx"></script>\n</body>\n</html>\n',
|
|
27
|
+
'package.json': '{\n "name": "react-app",\n "version": "1.0.0",\n "scripts": {\n "dev": "vite",\n "build": "vite build"\n },\n "dependencies": {\n "react": "^18.2.0",\n "react-dom": "^18.2.0"\n },\n "devDependencies": {\n "@vitejs/plugin-react": "^4.0.0",\n "vite": "^5.0.0"\n }\n}\n',
|
|
28
|
+
'vite.config.js': 'import { defineConfig } from "vite";\nimport react from "@vitejs/plugin-react";\n\nexport default defineConfig({\n plugins: [react()]\n});\n',
|
|
29
|
+
'.gitignore': 'node_modules/\ndist/\n',
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
'fastapi': {
|
|
33
|
+
'description': 'FastAPI проект',
|
|
34
|
+
'files': {
|
|
35
|
+
'app/__init__.py': '',
|
|
36
|
+
'app/main.py': 'from fastapi import FastAPI\n\napp = FastAPI()\n\n\n@app.get("/")\nasync def root():\n return {"message": "Привет, Мир!"}\n',
|
|
37
|
+
'requirements.txt': 'fastapi\nuvicorn\n',
|
|
38
|
+
'README.md': '# FastAPI Проект\n\n## Запуск\n\n```bash\nuvicorn app.main:app --reload\n```\n',
|
|
39
|
+
'.gitignore': '__pycache__/\n*.pyc\n.env\nvenv/\n',
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
'node': {
|
|
43
|
+
'description': 'Node.js с Express',
|
|
44
|
+
'files': {
|
|
45
|
+
'src/index.js': 'const express = require("express");\n\nconst app = express();\nconst PORT = process.env.PORT || 3000;\n\napp.get("/", (req, res) => {\n res.json({ message: "Привет, Мир!" });\n});\n\napp.listen(PORT, () => {\n console.log(`Сервер: http://localhost:${PORT}`);\n});\n',
|
|
46
|
+
'package.json': '{\n "name": "node-app",\n "version": "1.0.0",\n "main": "src/index.js",\n "scripts": {\n "start": "node src/index.js"\n },\n "dependencies": {\n "express": "^4.18.0"\n }\n}\n',
|
|
47
|
+
'.gitignore': 'node_modules/\n.env\n',
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def get_template(template_name: str) -> Dict:
|
|
54
|
+
"""Получает шаблон проекта"""
|
|
55
|
+
return TEMPLATES.get(template_name, {})
|