handycode 2.2.0__tar.gz → 2.2.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.2.0 → handycode-2.2.1}/PKG-INFO +1 -1
- {handycode-2.2.0 → handycode-2.2.1}/handycode/__init__.py +1 -1
- {handycode-2.2.0 → handycode-2.2.1}/handycode/assistant.py +140 -16
- {handycode-2.2.0 → handycode-2.2.1}/handycode.egg-info/PKG-INFO +1 -1
- {handycode-2.2.0 → handycode-2.2.1}/setup.py +1 -1
- {handycode-2.2.0 → handycode-2.2.1}/LICENSE +0 -0
- {handycode-2.2.0 → handycode-2.2.1}/README.md +0 -0
- {handycode-2.2.0 → handycode-2.2.1}/handycode/__main__.py +0 -0
- {handycode-2.2.0 → handycode-2.2.1}/handycode/cli.py +0 -0
- {handycode-2.2.0 → handycode-2.2.1}/handycode/config.py +0 -0
- {handycode-2.2.0 → handycode-2.2.1}/handycode/file_manager.py +0 -0
- {handycode-2.2.0 → handycode-2.2.1}/handycode/logo.py +0 -0
- {handycode-2.2.0 → handycode-2.2.1}/handycode/main.py +0 -0
- {handycode-2.2.0 → handycode-2.2.1}/handycode/models.py +0 -0
- {handycode-2.2.0 → handycode-2.2.1}/handycode/project_templates.py +0 -0
- {handycode-2.2.0 → handycode-2.2.1}/handycode/security.py +0 -0
- {handycode-2.2.0 → handycode-2.2.1}/handycode/utils.py +0 -0
- {handycode-2.2.0 → handycode-2.2.1}/handycode.egg-info/SOURCES.txt +0 -0
- {handycode-2.2.0 → handycode-2.2.1}/handycode.egg-info/dependency_links.txt +0 -0
- {handycode-2.2.0 → handycode-2.2.1}/handycode.egg-info/entry_points.txt +0 -0
- {handycode-2.2.0 → handycode-2.2.1}/handycode.egg-info/requires.txt +0 -0
- {handycode-2.2.0 → handycode-2.2.1}/handycode.egg-info/top_level.txt +0 -0
- {handycode-2.2.0 → handycode-2.2.1}/setup.cfg +0 -0
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Основной класс ассистента HandyCode с
|
|
2
|
+
Основной класс ассистента HandyCode с интерактивным меню
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
import os
|
|
5
|
+
import os
|
|
6
|
+
import re
|
|
7
|
+
import json
|
|
8
|
+
import sys
|
|
9
|
+
import atexit
|
|
10
|
+
import signal
|
|
6
11
|
from pathlib import Path
|
|
7
12
|
from typing import List, Dict, Optional
|
|
8
13
|
from datetime import datetime
|
|
@@ -18,7 +23,9 @@ try:
|
|
|
18
23
|
HAS_REQUESTS = True
|
|
19
24
|
except ImportError:
|
|
20
25
|
HAS_REQUESTS = False
|
|
21
|
-
import urllib.request
|
|
26
|
+
import urllib.request
|
|
27
|
+
import urllib.error
|
|
28
|
+
import ssl
|
|
22
29
|
|
|
23
30
|
from handycode.config import Config
|
|
24
31
|
from handycode.models import MODELS, get_model_settings
|
|
@@ -26,10 +33,128 @@ from handycode.file_manager import FileManager
|
|
|
26
33
|
from handycode.security import SecurityChecker
|
|
27
34
|
from handycode.utils import (
|
|
28
35
|
Colors, Theme, colorize, print_colored, print_header, print_success,
|
|
29
|
-
print_error, print_warning, print_info, print_logo,
|
|
36
|
+
print_error, print_warning, print_info, print_logo,
|
|
30
37
|
print_divider, print_file_action, print_status, print_section, print_box
|
|
31
38
|
)
|
|
32
39
|
|
|
40
|
+
|
|
41
|
+
def interactive_confirm(commands):
|
|
42
|
+
"""
|
|
43
|
+
Интерактивное меню выбора команд.
|
|
44
|
+
Управление: ↑/↓ для навигации, ПРОБЕЛ для выбора, ENTER для подтверждения.
|
|
45
|
+
Возвращает список выбранных команд.
|
|
46
|
+
"""
|
|
47
|
+
if not commands:
|
|
48
|
+
return []
|
|
49
|
+
|
|
50
|
+
# Настройка для Windows
|
|
51
|
+
if os.name == 'nt':
|
|
52
|
+
import msvcrt
|
|
53
|
+
|
|
54
|
+
def get_key():
|
|
55
|
+
key = msvcrt.getch()
|
|
56
|
+
if key == b'\xe0': # стрелки
|
|
57
|
+
key = msvcrt.getch()
|
|
58
|
+
if key == b'H': return 'up'
|
|
59
|
+
if key == b'P': return 'down'
|
|
60
|
+
if key == b'\r': return 'enter'
|
|
61
|
+
if key == b' ': return 'space'
|
|
62
|
+
if key == b'a': return 'a'
|
|
63
|
+
if key == b'A': return 'A'
|
|
64
|
+
if key == b's': return 's'
|
|
65
|
+
if key == b'S': return 'S'
|
|
66
|
+
if key == b'c': return 'c'
|
|
67
|
+
if key == b'C': return 'C'
|
|
68
|
+
if key == b'\x1b': return 'escape'
|
|
69
|
+
return key.decode('utf-8', errors='ignore')
|
|
70
|
+
else:
|
|
71
|
+
import tty
|
|
72
|
+
import termios
|
|
73
|
+
|
|
74
|
+
def get_key():
|
|
75
|
+
fd = sys.stdin.fileno()
|
|
76
|
+
old = termios.tcgetattr(fd)
|
|
77
|
+
try:
|
|
78
|
+
tty.setraw(fd)
|
|
79
|
+
key = sys.stdin.read(1)
|
|
80
|
+
if key == '\x1b':
|
|
81
|
+
key += sys.stdin.read(2)
|
|
82
|
+
if key == '\x1b[A': return 'up'
|
|
83
|
+
if key == '\x1b[B': return 'down'
|
|
84
|
+
return 'escape'
|
|
85
|
+
if key == '\r': return 'enter'
|
|
86
|
+
if key == ' ': return 'space'
|
|
87
|
+
return key
|
|
88
|
+
finally:
|
|
89
|
+
termios.tcsetattr(fd, termios.TCSADRAIN, old)
|
|
90
|
+
|
|
91
|
+
selected = [True] * len(commands) # все выбраны по умолчанию
|
|
92
|
+
current = 0
|
|
93
|
+
|
|
94
|
+
def render():
|
|
95
|
+
# Очищаем предыдущий вывод
|
|
96
|
+
print(f"\033[{len(commands) + 4}A\033[J", end="")
|
|
97
|
+
|
|
98
|
+
print()
|
|
99
|
+
print(colorize(" ⚡ Команды для выполнения:", Theme.HIGHLIGHT + Colors.BOLD))
|
|
100
|
+
print(colorize(" ─────────────────────────────────────────────────", Theme.MUTED))
|
|
101
|
+
|
|
102
|
+
for i, cmd in enumerate(commands):
|
|
103
|
+
if i == current:
|
|
104
|
+
prefix = colorize(" ›", Theme.PRIMARY + Colors.BOLD)
|
|
105
|
+
else:
|
|
106
|
+
prefix = " "
|
|
107
|
+
|
|
108
|
+
if selected[i]:
|
|
109
|
+
checkbox = colorize("◉", Theme.SUCCESS)
|
|
110
|
+
cmd_color = Theme.SUCCESS
|
|
111
|
+
else:
|
|
112
|
+
checkbox = colorize("○", Theme.MUTED)
|
|
113
|
+
cmd_color = Theme.MUTED
|
|
114
|
+
|
|
115
|
+
print(f"{prefix} {checkbox} {colorize(cmd, cmd_color)}")
|
|
116
|
+
|
|
117
|
+
print()
|
|
118
|
+
print(colorize(" Управление:", Theme.MUTED))
|
|
119
|
+
print(colorize(" ↑↓ Навигация ПРОБЕЛ Выбрать A Все S Пропустить ENTER Подтвердить", Theme.MUTED))
|
|
120
|
+
|
|
121
|
+
# Рендерим первый раз
|
|
122
|
+
print()
|
|
123
|
+
print()
|
|
124
|
+
print()
|
|
125
|
+
print()
|
|
126
|
+
print()
|
|
127
|
+
print()
|
|
128
|
+
for _ in range(len(commands) + 4):
|
|
129
|
+
print()
|
|
130
|
+
|
|
131
|
+
render()
|
|
132
|
+
|
|
133
|
+
while True:
|
|
134
|
+
key = get_key()
|
|
135
|
+
|
|
136
|
+
if key == 'up':
|
|
137
|
+
current = (current - 1) % len(commands)
|
|
138
|
+
render()
|
|
139
|
+
elif key == 'down':
|
|
140
|
+
current = (current + 1) % len(commands)
|
|
141
|
+
render()
|
|
142
|
+
elif key == 'space':
|
|
143
|
+
selected[current] = not selected[current]
|
|
144
|
+
render()
|
|
145
|
+
elif key in ['a', 'A']:
|
|
146
|
+
selected = [True] * len(commands)
|
|
147
|
+
render()
|
|
148
|
+
elif key in ['s', 'S']:
|
|
149
|
+
selected = [False] * len(commands)
|
|
150
|
+
render()
|
|
151
|
+
elif key in ['c', 'C', 'escape']:
|
|
152
|
+
return []
|
|
153
|
+
elif key == 'enter':
|
|
154
|
+
print()
|
|
155
|
+
return [cmd for cmd, sel in zip(commands, selected) if sel]
|
|
156
|
+
|
|
157
|
+
|
|
33
158
|
class HandyCode:
|
|
34
159
|
def __init__(self, project_path, model="deepseek", auto_approve=False, config=None):
|
|
35
160
|
self.project_path = project_path
|
|
@@ -127,7 +252,6 @@ Speak Russian. Write code in English."""
|
|
|
127
252
|
|
|
128
253
|
def reset_interrupt(self): self._interrupt_count = 0
|
|
129
254
|
|
|
130
|
-
# Потоковая обработка
|
|
131
255
|
def _process_stream_chunk(self, chunk):
|
|
132
256
|
self.stream_buffer += chunk
|
|
133
257
|
while True:
|
|
@@ -263,7 +387,6 @@ Speak Russian. Write code in English."""
|
|
|
263
387
|
}
|
|
264
388
|
|
|
265
389
|
try:
|
|
266
|
-
# Заголовок ответа
|
|
267
390
|
print_divider("─", 60, Theme.MUTED)
|
|
268
391
|
print(colorize(" HandyCode", Theme.PRIMARY + Colors.BOLD), end="")
|
|
269
392
|
print(colorize(" ● ответ", Theme.MUTED))
|
|
@@ -274,22 +397,23 @@ Speak Russian. Write code in English."""
|
|
|
274
397
|
if response:
|
|
275
398
|
self.conversation_history.append({"role": "assistant", "content": response})
|
|
276
399
|
|
|
277
|
-
# Команды
|
|
278
400
|
if self.pending_commands:
|
|
279
|
-
print()
|
|
280
|
-
print_section("⚡ Команды (требуют подтверждения)",
|
|
281
|
-
[colorize(cmd, Theme.WARNING) for cmd in self.pending_commands])
|
|
282
401
|
if self.auto_approve:
|
|
283
|
-
|
|
402
|
+
selected_commands = self.pending_commands
|
|
284
403
|
else:
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
if
|
|
288
|
-
|
|
404
|
+
selected_commands = interactive_confirm(self.pending_commands)
|
|
405
|
+
|
|
406
|
+
if selected_commands:
|
|
407
|
+
print()
|
|
408
|
+
print_section("⚡ Выполнение команд", [])
|
|
409
|
+
for cmd in selected_commands:
|
|
289
410
|
if self.security.is_safe_command(cmd):
|
|
290
411
|
print_status(f"Выполняется: {cmd}")
|
|
291
412
|
self.file_manager.execute_command(cmd)
|
|
292
413
|
self.stats["commands_executed"].append(cmd)
|
|
414
|
+
else:
|
|
415
|
+
print_warning("Команды пропущены")
|
|
416
|
+
|
|
293
417
|
self.stats["messages_sent"] += 1
|
|
294
418
|
return response
|
|
295
419
|
except Exception as e:
|
|
@@ -310,7 +434,7 @@ Speak Russian. Write code in English."""
|
|
|
310
434
|
"/exit Выход"
|
|
311
435
|
], Theme.PRIMARY)
|
|
312
436
|
elif cmd in ['/scan', '/s']:
|
|
313
|
-
|
|
437
|
+
print(self.file_manager.scan_project())
|
|
314
438
|
elif cmd in ['/models', '/m']:
|
|
315
439
|
lines = []
|
|
316
440
|
for name, mid in MODELS.items():
|
|
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
|
|
File without changes
|