handycode 2.1.1__tar.gz → 2.1.3__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 (23) hide show
  1. {handycode-2.1.1 → handycode-2.1.3}/PKG-INFO +1 -1
  2. {handycode-2.1.1 → handycode-2.1.3}/handycode/__init__.py +1 -1
  3. {handycode-2.1.1 → handycode-2.1.3}/handycode/assistant.py +228 -231
  4. {handycode-2.1.1 → handycode-2.1.3}/handycode/logo.py +2 -0
  5. {handycode-2.1.1 → handycode-2.1.3}/handycode/models.py +20 -10
  6. {handycode-2.1.1 → handycode-2.1.3}/handycode.egg-info/PKG-INFO +1 -1
  7. {handycode-2.1.1 → handycode-2.1.3}/setup.py +1 -1
  8. {handycode-2.1.1 → handycode-2.1.3}/LICENSE +0 -0
  9. {handycode-2.1.1 → handycode-2.1.3}/README.md +0 -0
  10. {handycode-2.1.1 → handycode-2.1.3}/handycode/__main__.py +0 -0
  11. {handycode-2.1.1 → handycode-2.1.3}/handycode/cli.py +0 -0
  12. {handycode-2.1.1 → handycode-2.1.3}/handycode/config.py +0 -0
  13. {handycode-2.1.1 → handycode-2.1.3}/handycode/file_manager.py +0 -0
  14. {handycode-2.1.1 → handycode-2.1.3}/handycode/main.py +0 -0
  15. {handycode-2.1.1 → handycode-2.1.3}/handycode/project_templates.py +0 -0
  16. {handycode-2.1.1 → handycode-2.1.3}/handycode/security.py +0 -0
  17. {handycode-2.1.1 → handycode-2.1.3}/handycode/utils.py +0 -0
  18. {handycode-2.1.1 → handycode-2.1.3}/handycode.egg-info/SOURCES.txt +0 -0
  19. {handycode-2.1.1 → handycode-2.1.3}/handycode.egg-info/dependency_links.txt +0 -0
  20. {handycode-2.1.1 → handycode-2.1.3}/handycode.egg-info/entry_points.txt +0 -0
  21. {handycode-2.1.1 → handycode-2.1.3}/handycode.egg-info/requires.txt +0 -0
  22. {handycode-2.1.1 → handycode-2.1.3}/handycode.egg-info/top_level.txt +0 -0
  23. {handycode-2.1.1 → handycode-2.1.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: handycode
3
- Version: 2.1.1
3
+ Version: 2.1.3
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.1"
6
+ __version__ = "2.1.3"
7
7
  __author__ = "AURA Tec."
8
8
  __license__ = "MIT"
9
9
 
@@ -8,7 +8,6 @@ import json
8
8
  import sys
9
9
  import atexit
10
10
  import signal
11
- import shutil
12
11
  from pathlib import Path
13
12
  from typing import List, Dict, Optional
14
13
  from datetime import datetime
@@ -66,6 +65,7 @@ class HandyCode:
66
65
  "files_created": [],
67
66
  "files_modified": [],
68
67
  "files_deleted": [],
68
+ "files_read": [],
69
69
  "commands_executed": [],
70
70
  "start_time": datetime.now()
71
71
  }
@@ -75,48 +75,46 @@ class HandyCode:
75
75
  self._interrupt_count = 0
76
76
 
77
77
  def _build_project_context(self):
78
- """Собирает контекст проекта"""
79
- context = f"\n\n=== PROJECT CONTEXT ===\n"
80
- context += f"Working directory: {self.project_path}\n"
81
- context += f"OS: {sys.platform}\n"
78
+ context = f"\n\n=== CURRENT PROJECT ===\n"
79
+ context += f"Directory: {self.project_path}\n"
82
80
 
83
81
  try:
84
82
  all_files = []
85
83
  for ext in self.file_manager.allowed_extensions:
86
84
  all_files.extend(self.project_path.rglob(f"*{ext}"))
87
-
88
85
  all_files.extend(self.project_path.rglob("*"))
89
- all_files = list(set(all_files))
90
-
91
- files = [f for f in all_files if f.is_file()
92
- and not any(ex in f.parts for ex in self.file_manager.excluded_dirs)]
93
-
94
- context += f"\nFiles in project ({len(files)} total):\n"
95
86
 
96
- for file in sorted(files):
87
+ seen = set()
88
+ files = []
89
+ for f in sorted(all_files):
90
+ if f.is_file() and f not in seen:
91
+ rel = str(f.relative_to(self.project_path))
92
+ if not any(ex in f.parts for ex in self.file_manager.excluded_dirs):
93
+ if not any(rel.startswith(ex) for ex in self.file_manager.excluded_dirs):
94
+ files.append(f)
95
+ seen.add(f)
96
+
97
+ context += f"\nFiles ({len(files)}):\n"
98
+ for file in files:
97
99
  try:
98
100
  rel_path = file.relative_to(self.project_path)
99
101
  size = file.stat().st_size
100
- context += f" - {rel_path} ({self._format_size(size)})\n"
102
+ context += f" {rel_path} ({self._format_size(size)})\n"
101
103
  except:
102
104
  pass
103
105
 
104
- context += f"\nFile contents (for context):\n"
105
- total_size = 0
106
- max_total = 30000
107
-
108
- for file in sorted(files):
109
- if total_size >= max_total:
106
+ context += f"\nFile contents:\n"
107
+ total = 0
108
+ for file in files:
109
+ if total > 50000:
110
110
  break
111
-
112
111
  try:
113
112
  content = file.read_text(encoding='utf-8', errors='ignore')
114
113
  if len(content) > 3000:
115
114
  content = content[:3000] + "\n... (truncated)"
116
-
117
115
  rel_path = file.relative_to(self.project_path)
118
116
  context += f"\n=== {rel_path} ===\n{content}\n"
119
- total_size += len(content)
117
+ total += len(content)
120
118
  except:
121
119
  pass
122
120
 
@@ -128,35 +126,69 @@ class HandyCode:
128
126
  def _format_size(self, size):
129
127
  for unit in ['B', 'KB', 'MB', 'GB']:
130
128
  if size < 1024:
131
- return f"{size:.0f}{unit}"
129
+ return f"{size:.1f}{unit}"
132
130
  size /= 1024
133
- return f"{size:.0f}GB"
131
+ return f"{size:.1f}TB"
134
132
 
135
133
  def _get_system_prompt(self):
136
- return """You are HandyCode, a powerful AI code assistant with FULL file system access.
137
-
138
- YOU CAN:
139
- - CREATE files: [[CREATE:path/to/file.ext]] content
140
- - MODIFY files: [[MODIFY:path/to/file.ext]] new content
141
- - DELETE files: [[DELETE:path/to/file.ext]]
142
- - READ files: [[READ:path/to/file.ext]]
143
- - LIST directory: [[LIST:path/to/dir]]
144
- - EXECUTE commands: [[EXEC:command]]
145
- - CREATE folders: [[MKDIR:path/to/dir]]
146
- - COPY files: [[COPY:source]] -> [[CREATE:destination]] (use CREATE with content)
147
- - MOVE files: [[MOVE:source]] [[CREATE:destination]]
148
-
149
- FILES ARE AUTO-CREATED without asking. Only COMMANDS need confirmation.
150
- You see ALL project files and their contents.
151
-
152
- RULES:
153
- 1. CREATE/MODIFY/DELETE/MKDIR happen automatically
154
- 2. EXEC commands need user confirmation
155
- 3. Show COMPLETE file contents
156
- 4. Create ALL needed files
157
- 5. Use the project context
158
-
159
- Speak Russian. Write code in English."""
134
+ return """You are HandyCode - a powerful AI assistant for file operations and coding.
135
+
136
+ FILE OPERATIONS - USE EXACT FORMAT:
137
+ To create a file, use [[CREATE:path/to/file]] followed by the complete file content on the next lines, then [[END]] to mark end of file:
138
+ [[CREATE:path/to/file.py]]
139
+ import os
140
+
141
+ def main():
142
+ print("Hello World")
143
+
144
+ if __name__ == "__main__":
145
+ main()
146
+ [[END]]
147
+
148
+ To modify a file, use [[MODIFY:path/to/file]] followed by the complete new content, then [[END]]:
149
+ [[MODIFY:path/to/file.py]]
150
+ new complete content here
151
+ [[END]]
152
+
153
+ To delete a file:
154
+ [[DELETE:path/to/file.py]]
155
+
156
+ To read a file:
157
+ [[READ:path/to/file.py]]
158
+
159
+ To list directory:
160
+ [[LIST:path/]]
161
+
162
+ To run a command (requires confirmation):
163
+ [[EXEC:python script.py]]
164
+
165
+ CRITICAL RULES:
166
+ 1. ALWAYS put [[END]] after file content for CREATE and MODIFY
167
+ 2. Show COMPLETE file content between [[CREATE/MODIFY:...]] and [[END]]
168
+ 3. NEVER include comments or explanations INSIDE the file content
169
+ 4. Only the actual code goes between [[CREATE:...]] and [[END]]
170
+ 5. Explain what you're doing BEFORE the [[CREATE:...]] block
171
+ 6. Do NOT put your explanations inside [[CREATE:...]] [[END]] blocks
172
+ 7. Files are created/modified automatically without asking
173
+ 8. Commands (EXEC) require user confirmation
174
+
175
+ Example of CORRECT format:
176
+ I'll create a Python script for you.
177
+
178
+ [[CREATE:hello.py]]
179
+ print("Hello World")
180
+ [[END]]
181
+
182
+ Now you can run it with: python hello.py
183
+
184
+ Example of WRONG format (DON'T DO THIS):
185
+ [[CREATE:hello.py]]
186
+ Here's your file:
187
+ print("Hello World")
188
+ This file prints hello
189
+ [[END]]
190
+
191
+ Respond in Russian. Write code in English."""
160
192
 
161
193
  def _setup_readline(self):
162
194
  if not HAS_READLINE:
@@ -180,11 +212,13 @@ Speak Russian. Write code in English."""
180
212
  def reset_interrupt(self):
181
213
  self._interrupt_count = 0
182
214
 
183
- def _make_request_stream(self, data):
184
- """Отправляет запрос и получает ответ в реальном времени"""
185
- if not HAS_REQUESTS:
186
- return self._make_request(data)
215
+ def _make_request_streaming(self, data):
216
+ if HAS_REQUESTS:
217
+ return self._make_request_streaming_requests(data)
218
+ else:
219
+ return self._make_request_urllib(data)
187
220
 
221
+ def _make_request_streaming_requests(self, data):
188
222
  try:
189
223
  response = requests.post(
190
224
  self.api_url,
@@ -196,9 +230,9 @@ Speak Russian. Write code in English."""
196
230
  timeout=120,
197
231
  stream=True
198
232
  )
233
+ response.raise_for_status()
199
234
 
200
235
  full_response = ""
201
-
202
236
  for line in response.iter_lines():
203
237
  if line:
204
238
  line = line.decode('utf-8')
@@ -212,37 +246,54 @@ Speak Russian. Write code in English."""
212
246
  delta = chunk['choices'][0].get('delta', {})
213
247
  content = delta.get('content', '')
214
248
  if content:
249
+ # Показываем только если это не внутри кодового блока
215
250
  print(content, end="", flush=True)
216
251
  full_response += content
217
252
  except:
218
253
  continue
219
-
220
254
  print()
221
255
  return full_response
222
-
223
256
  except Exception as e:
224
257
  print_error(f"API Error: {e}")
225
258
  return ""
226
259
 
227
- def _make_request(self, data):
228
- """Обычный запрос без стриминга"""
229
- if HAS_REQUESTS:
230
- try:
231
- response = requests.post(
232
- self.api_url,
233
- headers={
234
- "Authorization": f"Bearer {self.api_key}",
235
- "Content-Type": "application/json"
236
- },
237
- json=data,
238
- timeout=120
239
- )
240
- result = response.json()
241
- if 'choices' in result and result['choices']:
242
- return result['choices'][0]['message']['content']
243
- except Exception as e:
244
- print_error(f"API Error: {e}")
245
- return ""
260
+ def _make_request_urllib(self, data):
261
+ try:
262
+ json_data = json.dumps({**data, "stream": True}).encode('utf-8')
263
+ req = urllib.request.Request(
264
+ self.api_url,
265
+ data=json_data,
266
+ headers={
267
+ "Authorization": f"Bearer {self.api_key}",
268
+ "Content-Type": "application/json"
269
+ },
270
+ method='POST'
271
+ )
272
+ ctx = ssl.create_default_context()
273
+
274
+ full_response = ""
275
+ with urllib.request.urlopen(req, context=ctx, timeout=120) as response:
276
+ for line in response:
277
+ line = line.decode('utf-8').strip()
278
+ if line.startswith('data: '):
279
+ data_str = line[6:]
280
+ if data_str == '[DONE]':
281
+ break
282
+ try:
283
+ chunk = json.loads(data_str)
284
+ if 'choices' in chunk and chunk['choices']:
285
+ delta = chunk['choices'][0].get('delta', {})
286
+ content = delta.get('content', '')
287
+ if content:
288
+ print(content, end="", flush=True)
289
+ full_response += content
290
+ except:
291
+ continue
292
+ print()
293
+ return full_response
294
+ except Exception as e:
295
+ print_error(f"Request error: {e}")
296
+ return ""
246
297
 
247
298
  def send_message(self, user_input):
248
299
  if user_input.startswith('/'):
@@ -250,6 +301,9 @@ Speak Russian. Write code in English."""
250
301
 
251
302
  self.conversation_history.append({"role": "user", "content": user_input})
252
303
 
304
+ if len(self.conversation_history) > 20:
305
+ self.conversation_history = [self.conversation_history[0]] + self.conversation_history[-19:]
306
+
253
307
  payload = {
254
308
  "model": self.current_model,
255
309
  "messages": self.conversation_history,
@@ -259,14 +313,14 @@ Speak Russian. Write code in English."""
259
313
 
260
314
  try:
261
315
  print_info(f"\nDEEPSEEK:")
262
- response = self._make_request_stream(payload)
316
+ response = self._make_request_streaming(payload)
263
317
 
264
318
  if response:
265
319
  self.conversation_history.append({"role": "assistant", "content": response})
266
320
 
267
321
  actions = self._parse_actions(response)
268
322
  if actions:
269
- self._execute_actions_smart(actions)
323
+ self._execute_actions(actions)
270
324
 
271
325
  self.stats["messages_sent"] += 1
272
326
  return response
@@ -274,24 +328,46 @@ Speak Russian. Write code in English."""
274
328
  return print_error(f"Error: {e}")
275
329
 
276
330
  def _parse_actions(self, response):
277
- """Парсит все типы действий"""
278
331
  actions = []
279
332
 
280
- # CREATE
281
- for match in re.finditer(r'\[\[CREATE:(.+?)\]\](.*?)(?=\[\[|$)', response, re.DOTALL):
333
+ # CREATE с [[END]]
334
+ create_pattern = r'\[\[CREATE:(.+?)\]\](.*?)\[\[END\]\]'
335
+ for match in re.finditer(create_pattern, response, re.DOTALL):
282
336
  path = match.group(1).strip()
283
337
  content = match.group(2).strip()
284
- content = re.sub(r'^```[\w]*\n?', '', content)
285
- content = re.sub(r'\n?```$', '', content)
286
- actions.append({'type': 'create', 'path': path, 'content': content})
287
-
288
- # MODIFY
289
- for match in re.finditer(r'\[\[MODIFY:(.+?)\]\](.*?)(?=\[\[|$)', response, re.DOTALL):
338
+ # Убираем маркеры кода если есть
339
+ content = re.sub(r'^```[\w]*\n', '', content)
340
+ content = re.sub(r'\n```$', '', content)
341
+ if content:
342
+ actions.append({'type': 'create', 'path': path, 'content': content})
343
+
344
+ # CREATE без [[END]] (старый формат, берём до следующего [[ или конца)
345
+ if not any(a['type'] == 'create' for a in actions):
346
+ old_create = r'\[\[CREATE:(.+?)\]\](.*?)(?=\[\[|$)'
347
+ for match in re.finditer(old_create, response, re.DOTALL):
348
+ path = match.group(1).strip()
349
+ content = match.group(2).strip()
350
+ content = re.sub(r'^```[\w]*\n', '', content)
351
+ content = re.sub(r'\n```$', '', content)
352
+ # Убираем явно не-кодовые строки
353
+ lines = content.split('\n')
354
+ clean_lines = []
355
+ for line in lines:
356
+ if not line.startswith('Here') and not line.startswith('This file') and not line.startswith('Now you'):
357
+ clean_lines.append(line)
358
+ content = '\n'.join(clean_lines).strip()
359
+ if content:
360
+ actions.append({'type': 'create', 'path': path, 'content': content})
361
+
362
+ # MODIFY с [[END]]
363
+ modify_pattern = r'\[\[MODIFY:(.+?)\]\](.*?)\[\[END\]\]'
364
+ for match in re.finditer(modify_pattern, response, re.DOTALL):
290
365
  path = match.group(1).strip()
291
366
  content = match.group(2).strip()
292
- content = re.sub(r'^```[\w]*\n?', '', content)
293
- content = re.sub(r'\n?```$', '', content)
294
- actions.append({'type': 'modify', 'path': path, 'content': content})
367
+ content = re.sub(r'^```[\w]*\n', '', content)
368
+ content = re.sub(r'\n```$', '', content)
369
+ if content:
370
+ actions.append({'type': 'modify', 'path': path, 'content': content})
295
371
 
296
372
  # DELETE
297
373
  for match in re.finditer(r'\[\[DELETE:(.+?)\]\]', response):
@@ -305,46 +381,50 @@ Speak Russian. Write code in English."""
305
381
  for match in re.finditer(r'\[\[LIST:(.+?)\]\]', response):
306
382
  actions.append({'type': 'list', 'path': match.group(1).strip()})
307
383
 
308
- # MKDIR
309
- for match in re.finditer(r'\[\[MKDIR:(.+?)\]\]', response):
310
- actions.append({'type': 'mkdir', 'path': match.group(1).strip()})
311
-
312
- # COPY
313
- for match in re.finditer(r'\[\[COPY:(.+?)\]\]', response):
314
- actions.append({'type': 'copy', 'path': match.group(1).strip()})
315
-
316
- # MOVE
317
- for match in re.finditer(r'\[\[MOVE:(.+?)\]\]', response):
318
- actions.append({'type': 'move', 'path': match.group(1).strip()})
319
-
320
384
  # EXEC
321
385
  for match in re.finditer(r'\[\[EXEC:(.+?)\]\]', response):
322
386
  actions.append({'type': 'exec', 'command': match.group(1).strip()})
323
387
 
324
388
  return actions
325
389
 
326
- def _execute_actions_smart(self, actions):
327
- """Умное выполнение: файлы авто, команды с подтверждением"""
390
+ def _execute_actions(self, actions):
328
391
  if not actions:
329
392
  return
330
393
 
331
- file_actions = [a for a in actions if a['type'] in ['create', 'modify', 'delete', 'mkdir', 'read', 'list', 'copy', 'move']]
394
+ file_actions = [a for a in actions if a['type'] in ['create', 'modify', 'delete', 'read', 'list']]
332
395
  exec_actions = [a for a in actions if a['type'] == 'exec']
333
396
 
334
- # Файловые операции выполняем автоматически
397
+ # Файловые операции - автоматически, показываем только информацию
335
398
  if file_actions:
336
- print_header("\nAUTO FILE OPERATIONS:")
399
+ print_header("\nFILE OPERATIONS")
400
+ for i, action in enumerate(file_actions, 1):
401
+ if action['type'] == 'create':
402
+ lines = action['content'].count('\n') + 1
403
+ print(f" {i}. Created: {action['path']} ({lines} lines)")
404
+ elif action['type'] == 'modify':
405
+ lines = action['content'].count('\n') + 1
406
+ print(f" {i}. Modified: {action['path']} ({lines} lines)")
407
+ elif action['type'] == 'delete':
408
+ print(f" {i}. Deleted: {action['path']}")
409
+ elif action['type'] == 'read':
410
+ print(f" {i}. Read: {action['path']}")
411
+ elif action['type'] == 'list':
412
+ print(f" {i}. Listed: {action['path']}")
413
+
337
414
  for action in file_actions:
338
415
  self._execute_action(action)
339
416
 
340
- # Команды требуют подтверждения
417
+ # Команды - требуют подтверждения
341
418
  if exec_actions:
342
- print_header("\nCOMMANDS TO EXECUTE:")
419
+ print_header("\nCOMMANDS (confirmation required)")
343
420
  for i, action in enumerate(exec_actions, 1):
344
421
  print(f" {i}. {action['command']}")
345
422
 
346
- print("\n[A] Execute all [S] Skip all [1-N] Select [C] Cancel")
347
- choice = input("> ").strip().upper()
423
+ if self.auto_approve:
424
+ choice = 'A'
425
+ else:
426
+ print("\n[A] Execute all [S] Skip [C] Cancel")
427
+ choice = input("> ").strip().upper()
348
428
 
349
429
  if choice == 'A':
350
430
  for action in exec_actions:
@@ -353,151 +433,78 @@ Speak Russian. Write code in English."""
353
433
  print_warning("Skipped")
354
434
  elif choice == 'C':
355
435
  print_warning("Cancelled")
356
- elif choice.isdigit():
357
- idx = int(choice) - 1
358
- if 0 <= idx < len(exec_actions):
359
- self._execute_action(exec_actions[idx])
360
436
 
361
437
  def _execute_action(self, action):
362
- """Выполняет одно действие"""
363
438
  try:
364
439
  if action['type'] == 'create':
365
- self.file_manager.create_file(action['path'], action['content'])
366
- self.stats["files_created"].append(action['path'])
440
+ if self.security.is_safe_path(action['path']):
441
+ self.file_manager.create_file(action['path'], action['content'])
442
+ self.stats["files_created"].append(action['path'])
367
443
 
368
444
  elif action['type'] == 'modify':
369
- self.file_manager.modify_file(action['path'], action['content'])
370
- self.stats["files_modified"].append(action['path'])
445
+ if self.security.is_safe_path(action['path']):
446
+ self.file_manager.modify_file(action['path'], action['content'])
447
+ self.stats["files_modified"].append(action['path'])
371
448
 
372
449
  elif action['type'] == 'delete':
373
- self._delete_file(action['path'])
374
- self.stats["files_deleted"].append(action['path'])
450
+ if self.security.is_safe_path(action['path']):
451
+ self.file_manager.delete_file(action['path'])
452
+ self.stats["files_deleted"].append(action['path'])
375
453
 
376
454
  elif action['type'] == 'read':
377
- self._read_file(action['path'])
455
+ if self.security.is_safe_path(action['path']):
456
+ self.file_manager.read_file(action['path'])
457
+ self.stats["files_read"].append(action['path'])
378
458
 
379
459
  elif action['type'] == 'list':
380
- self._list_directory(action['path'])
381
-
382
- elif action['type'] == 'mkdir':
383
- self._make_directory(action['path'])
460
+ self.file_manager.list_directory(action['path'])
384
461
 
385
462
  elif action['type'] == 'exec':
386
- self.file_manager.execute_command(action['command'])
387
- self.stats["commands_executed"].append(action['command'])
388
-
389
- elif action['type'] == 'copy':
390
- self._copy_file(action['path'])
391
-
392
- elif action['type'] == 'move':
393
- self._move_file(action['path'])
463
+ if self.security.is_safe_command(action['command']):
464
+ self.file_manager.execute_command(action['command'])
465
+ self.stats["commands_executed"].append(action['command'])
394
466
 
395
467
  except Exception as e:
396
- print_error(f"Failed: {e}")
397
-
398
- def _delete_file(self, path):
399
- """Удаляет файл"""
400
- full_path = self.project_path / path
401
- if not self.security.is_safe_path(str(path)):
402
- print_error(f"Unsafe: {path}")
403
- return
404
-
405
- if full_path.exists():
406
- # Бэкап перед удалением
407
- backup = full_path.with_suffix(full_path.suffix + '.bak')
408
- shutil.copy2(full_path, backup)
409
- full_path.unlink()
410
- print_success(f"Deleted: {path} (backup: {backup.name})")
411
- else:
412
- print_warning(f"Not found: {path}")
413
-
414
- def _read_file(self, path):
415
- """Читает и показывает файл"""
416
- full_path = self.project_path / path
417
- if full_path.exists():
418
- content = full_path.read_text(encoding='utf-8', errors='ignore')
419
- print_header(f"\n=== {path} ===")
420
- print(content)
421
- print_header("=" * (len(path) + 8))
422
- else:
423
- print_warning(f"Not found: {path}")
424
-
425
- def _list_directory(self, path):
426
- """Показывает содержимое директории"""
427
- full_path = self.project_path / path
428
- if full_path.exists() and full_path.is_dir():
429
- items = sorted(full_path.iterdir())
430
- print_header(f"\n=== {path}/ ({len(items)} items) ===")
431
- for item in items:
432
- if item.is_dir():
433
- print(f" [DIR] {item.name}/")
434
- else:
435
- size = item.stat().st_size
436
- print(f" [FILE] {item.name} ({self._format_size(size)})")
437
- else:
438
- print_warning(f"Not found: {path}")
439
-
440
- def _make_directory(self, path):
441
- """Создаёт директорию"""
442
- full_path = self.project_path / path
443
- full_path.mkdir(parents=True, exist_ok=True)
444
- print_success(f"Created dir: {path}")
445
-
446
- def _copy_file(self, source):
447
- """Копирует файл (ожидает, что следом будет CREATE)"""
448
- print_info(f"Copy source noted: {source}")
449
-
450
- def _move_file(self, source):
451
- """Перемещает файл (ожидает, что следом будет CREATE)"""
452
- full_path = self.project_path / source
453
- if full_path.exists():
454
- print_info(f"Move source noted: {source}")
455
- else:
456
- print_warning(f"Source not found: {source}")
468
+ print_error(f"Action failed: {e}")
457
469
 
458
470
  def _handle_command(self, user_input):
459
- cmd = user_input.lower().split()[0]
471
+ parts = user_input.split()
472
+ cmd = parts[0].lower()
473
+
460
474
  if cmd in ['/help', '/h']:
461
475
  print("""
462
476
  COMMANDS:
463
- /help Show help
464
- /scan Scan project
465
- /models List models
466
- /model N Switch model
467
- /clear Clear history
468
- /save Save session
469
- /stats Statistics
470
- /exit Exit
477
+ /help Show help
478
+ /scan Scan project
479
+ /models List models
480
+ /model NAME Switch model
481
+ /clear Clear history
482
+ /save Save session
483
+ /stats Statistics
484
+ /exit Exit
471
485
  """)
472
486
  elif cmd in ['/scan', '/s']:
473
487
  print(self.file_manager.scan_project())
474
488
  elif cmd in ['/models', '/m']:
475
489
  for name in MODELS:
476
490
  print(f" {name}")
491
+ elif cmd in ['/model'] and len(parts) > 1:
492
+ model_name = parts[1]
493
+ if model_name in MODELS:
494
+ self.current_model = MODELS[model_name]
495
+ self.model_settings = get_model_settings(self.current_model)
496
+ print_success(f"Switched to: {model_name}")
477
497
  elif cmd in ['/clear', '/c']:
478
498
  self.conversation_history = [self.conversation_history[0]]
479
499
  print_success("Cleared")
480
- elif cmd in ['/save']:
481
- self.file_manager.save_session(self.conversation_history, self.current_model, self.stats)
482
500
  elif cmd in ['/stats']:
483
- duration = datetime.now() - self.stats["start_time"]
484
501
  print(f"Messages: {self.stats['messages_sent']}")
485
- print(f"Created: {len(self.stats['files_created'])} files")
486
- print(f"Modified: {len(self.stats['files_modified'])} files")
487
- print(f"Deleted: {len(self.stats['files_deleted'])} files")
502
+ print(f"Created: {len(self.stats['files_created'])}")
503
+ print(f"Modified: {len(self.stats['files_modified'])}")
504
+ print(f"Deleted: {len(self.stats['files_deleted'])}")
488
505
  print(f"Commands: {len(self.stats['commands_executed'])}")
489
- print(f"Duration: {duration}")
490
506
  elif cmd in ['/exit', '/q']:
491
- print_success("Goodbye!")
492
507
  os._exit(0)
493
- elif cmd in ['/model'] and len(user_input.split()) > 1:
494
- model_name = user_input.split()[1]
495
- if model_name in MODELS:
496
- self.current_model = MODELS[model_name]
497
- self.model_settings = get_model_settings(self.current_model)
498
- print_success(f"Switched to: {model_name}")
499
- else:
500
- print_error(f"Unknown model: {model_name}")
501
508
  return ""
502
509
 
503
510
  def execute_command(self, command):
@@ -508,17 +515,7 @@ COMMANDS:
508
515
  print()
509
516
  print_info(f"Project: {self.project_path}")
510
517
  print_info(f"Model: {self.current_model}")
511
-
512
- try:
513
- files = list(self.project_path.rglob("*"))
514
- files = [f for f in files if f.is_file()
515
- and not any(ex in f.parts for ex in self.file_manager.excluded_dirs)]
516
- visible = [f for f in files if not f.name.startswith('.')]
517
- if visible:
518
- print_info(f"\nFound {len(visible)} files (AI sees all)")
519
- except:
520
- pass
521
-
518
+ print_info("Files: auto | Commands: confirmation required")
522
519
  print_info("/help for commands\n")
523
520
 
524
521
  while True:
@@ -51,6 +51,7 @@ def get_logo_plain() -> str:
51
51
  ║ ║
52
52
  ║ AI Ассистент для разработки ║
53
53
  ║ Prod. by AURA Tec. ║
54
+ ║ 2.1.2 ║
54
55
  ║ ║
55
56
  ╚══════════════════════════════════════════════════════════════╝
56
57
  """
@@ -84,6 +85,7 @@ def get_install_logo() -> str:
84
85
  ║ ║
85
86
  ║ {C.WHITE}AI Ассистент для разработки{C.CYAN} ║
86
87
  ║ {C.WHITE}Prod. by AURA Tec.{C.CYAN} ║
88
+ ║ {C.WHITE}2.1.3{C.CYAN} ║
87
89
  ║ ║
88
90
  ╚═════════════════════════════════════════════════════════════════════════════════════════════════╝{C.RESET}
89
91
  """
@@ -34,40 +34,50 @@ MODELS = {
34
34
 
35
35
  MODEL_SETTINGS = {
36
36
  "deepseek/deepseek-chat": {
37
- "temperature": 1,
37
+ "temperature": 0.3,
38
38
  "max_tokens": 8000,
39
- "description": "DeepSeek V3 - Лучший баланс скорости и качества"
39
+ "description": "DeepSeek V3 - Универсальный (точный код)"
40
40
  },
41
41
  "deepseek/deepseek-coder": {
42
- "temperature": 1,
42
+ "temperature": 0.1,
43
43
  "max_tokens": 8000,
44
- "description": "DeepSeek Coder - Специализирована для написания кода"
44
+ "description": "DeepSeek Coder - Код (макс. точность)"
45
45
  },
46
46
  "deepseek/deepseek-r1": {
47
- "temperature": 1,
47
+ "temperature": 0.5,
48
48
  "max_tokens": 4000,
49
- "description": "DeepSeek R1 - Глубокое мышление и анализ"
49
+ "description": "DeepSeek R1 - Анализ и архитектура"
50
50
  },
51
51
  "openai/gpt-4-turbo-preview": {
52
52
  "temperature": 0.3,
53
53
  "max_tokens": 4000,
54
- "description": "GPT-4 Turbo - Мощная универсальная модель"
54
+ "description": "GPT-4 Turbo"
55
55
  },
56
56
  "anthropic/claude-3-opus": {
57
57
  "temperature": 0.3,
58
58
  "max_tokens": 4000,
59
- "description": "Claude 3 Opus - Продвинутый анализ"
59
+ "description": "Claude 3 Opus"
60
60
  },
61
61
  "anthropic/claude-3-sonnet": {
62
+ "temperature": 0.2,
63
+ "max_tokens": 4000,
64
+ "description": "Claude 3 Sonnet"
65
+ },
66
+ "google/gemini-pro": {
67
+ "temperature": 0.4,
68
+ "max_tokens": 4000,
69
+ "description": "Gemini Pro"
70
+ },
71
+ "meta-llama/llama-3-70b-instruct": {
62
72
  "temperature": 0.3,
63
73
  "max_tokens": 4000,
64
- "description": "Claude 3 Sonnet - Быстрая и способная"
74
+ "description": "Llama 3 70B"
65
75
  },
66
76
  }
67
77
 
68
78
  DEFAULT_SETTINGS = {
69
79
  "temperature": 0.3,
70
- "max_tokens": 4000,
80
+ "max_tokens": 8000,
71
81
  "description": "Универсальная модель"
72
82
  }
73
83
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: handycode
3
- Version: 2.1.1
3
+ Version: 2.1.3
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.1",
5
+ version="2.1.3",
6
6
  author="AuraTechno",
7
7
  description="AI Code Assistant for DeepSeek",
8
8
  long_description="HandyCode - AI Code Assistant",
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes