handycode 2.1.2__tar.gz → 2.1.5__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.2 → handycode-2.1.5}/PKG-INFO +1 -1
  2. {handycode-2.1.2 → handycode-2.1.5}/handycode/__init__.py +1 -1
  3. {handycode-2.1.2 → handycode-2.1.5}/handycode/assistant.py +140 -201
  4. {handycode-2.1.2 → handycode-2.1.5}/handycode/logo.py +2 -0
  5. {handycode-2.1.2 → handycode-2.1.5}/handycode/models.py +20 -10
  6. {handycode-2.1.2 → handycode-2.1.5}/handycode.egg-info/PKG-INFO +1 -1
  7. {handycode-2.1.2 → handycode-2.1.5}/setup.py +1 -1
  8. {handycode-2.1.2 → handycode-2.1.5}/LICENSE +0 -0
  9. {handycode-2.1.2 → handycode-2.1.5}/README.md +0 -0
  10. {handycode-2.1.2 → handycode-2.1.5}/handycode/__main__.py +0 -0
  11. {handycode-2.1.2 → handycode-2.1.5}/handycode/cli.py +0 -0
  12. {handycode-2.1.2 → handycode-2.1.5}/handycode/config.py +0 -0
  13. {handycode-2.1.2 → handycode-2.1.5}/handycode/file_manager.py +0 -0
  14. {handycode-2.1.2 → handycode-2.1.5}/handycode/main.py +0 -0
  15. {handycode-2.1.2 → handycode-2.1.5}/handycode/project_templates.py +0 -0
  16. {handycode-2.1.2 → handycode-2.1.5}/handycode/security.py +0 -0
  17. {handycode-2.1.2 → handycode-2.1.5}/handycode/utils.py +0 -0
  18. {handycode-2.1.2 → handycode-2.1.5}/handycode.egg-info/SOURCES.txt +0 -0
  19. {handycode-2.1.2 → handycode-2.1.5}/handycode.egg-info/dependency_links.txt +0 -0
  20. {handycode-2.1.2 → handycode-2.1.5}/handycode.egg-info/entry_points.txt +0 -0
  21. {handycode-2.1.2 → handycode-2.1.5}/handycode.egg-info/requires.txt +0 -0
  22. {handycode-2.1.2 → handycode-2.1.5}/handycode.egg-info/top_level.txt +0 -0
  23. {handycode-2.1.2 → handycode-2.1.5}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: handycode
3
- Version: 2.1.2
3
+ Version: 2.1.5
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.2"
6
+ __version__ = "2.1.5"
7
7
  __author__ = "AURA Tec."
8
8
  __license__ = "MIT"
9
9
 
@@ -70,6 +70,10 @@ class HandyCode:
70
70
  "start_time": datetime.now()
71
71
  }
72
72
 
73
+ # Буфер для потокового создания файлов
74
+ self.stream_buffer = ""
75
+ self.current_file_action = None
76
+
73
77
  self._setup_readline()
74
78
  signal.signal(signal.SIGINT, self._signal_handler)
75
79
  self._interrupt_count = 0
@@ -131,64 +135,33 @@ class HandyCode:
131
135
  return f"{size:.1f}TB"
132
136
 
133
137
  def _get_system_prompt(self):
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
138
+ return """You are HandyCode - an AI assistant for coding. You can create, modify, delete files and run commands.
140
139
 
141
- def main():
142
- print("Hello World")
140
+ CRITICAL: You MUST complete ALL actions in ONE response. If user asks to create AND run a project, do BOTH in the same response.
143
141
 
144
- if __name__ == "__main__":
145
- main()
142
+ FILE FORMAT - use EXACTLY:
143
+ [[CREATE:filename]]
144
+ code here
146
145
  [[END]]
147
146
 
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
147
+ [[MODIFY:filename]]
148
+ new code here
151
149
  [[END]]
152
150
 
153
- To delete a file:
154
- [[DELETE:path/to/file.py]]
155
-
156
- To read a file:
157
- [[READ:path/to/file.py]]
151
+ [[DELETE:filename]]
152
+ [[READ:filename]]
153
+ [[LIST:directory/]]
154
+ [[EXEC:command]]
158
155
 
159
- To list directory:
160
- [[LIST:path/]]
156
+ RULES:
157
+ 1. CREATE and EXEC in SAME response - create files AND run them together
158
+ 2. Always use [[END]] to close files
159
+ 3. NEVER put comments inside [[CREATE]]...[[END]] blocks
160
+ 4. Put explanations BEFORE [[CREATE]] blocks, not inside
161
+ 5. When user asks to create and run - do both immediately
162
+ 6. Files create automatically, commands need confirmation
161
163
 
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."""
164
+ Speak Russian. Write code in English."""
192
165
 
193
166
  def _setup_readline(self):
194
167
  if not HAS_READLINE:
@@ -212,13 +185,71 @@ Respond in Russian. Write code in English."""
212
185
  def reset_interrupt(self):
213
186
  self._interrupt_count = 0
214
187
 
188
+ def _process_stream_chunk(self, chunk):
189
+ """Обрабатывает кусок потокового ответа, создавая файлы на лету"""
190
+ self.stream_buffer += chunk
191
+
192
+ # Ищем [[CREATE:...]]...[[END]]
193
+ while True:
194
+ # Ищем начало CREATE
195
+ create_match = re.search(r'\[\[CREATE:(.+?)\]\](.*?)\[\[END\]\]', self.stream_buffer, re.DOTALL)
196
+ if create_match:
197
+ path = create_match.group(1).strip()
198
+ content = create_match.group(2).strip()
199
+ content = re.sub(r'^```[\w]*\n', '', content)
200
+ content = re.sub(r'\n```$', '', content)
201
+
202
+ if content and self.security.is_safe_path(path):
203
+ self.file_manager.create_file(path, content)
204
+ self.stats["files_created"].append(path)
205
+ lines = content.count('\n') + 1
206
+ print(f"\n ✅ Created: {path} ({lines} lines)")
207
+
208
+ # Удаляем обработанный блок из буфера
209
+ self.stream_buffer = self.stream_buffer[create_match.end():]
210
+ else:
211
+ break
212
+
213
+ # Ищем [[MODIFY:...]]...[[END]]
214
+ while True:
215
+ modify_match = re.search(r'\[\[MODIFY:(.+?)\]\](.*?)\[\[END\]\]', self.stream_buffer, re.DOTALL)
216
+ if modify_match:
217
+ path = modify_match.group(1).strip()
218
+ content = modify_match.group(2).strip()
219
+ content = re.sub(r'^```[\w]*\n', '', content)
220
+ content = re.sub(r'\n```$', '', content)
221
+
222
+ if content and self.security.is_safe_path(path):
223
+ self.file_manager.modify_file(path, content)
224
+ self.stats["files_modified"].append(path)
225
+ lines = content.count('\n') + 1
226
+ print(f"\n ✏️ Modified: {path} ({lines} lines)")
227
+
228
+ self.stream_buffer = self.stream_buffer[modify_match.end():]
229
+ else:
230
+ break
231
+
232
+ # Ищем [[EXEC:...]]
233
+ while True:
234
+ exec_match = re.search(r'\[\[EXEC:(.+?)\]\]', self.stream_buffer)
235
+ if exec_match:
236
+ command = exec_match.group(1).strip()
237
+ self.pending_commands.append(command)
238
+ self.stream_buffer = self.stream_buffer[exec_match.end():]
239
+ else:
240
+ break
241
+
215
242
  def _make_request_streaming(self, data):
243
+ """Потоковый запрос с обработкой файлов в реальном времени"""
244
+ self.stream_buffer = ""
245
+ self.pending_commands = []
246
+
216
247
  if HAS_REQUESTS:
217
- return self._make_request_streaming_requests(data)
248
+ return self._stream_requests(data)
218
249
  else:
219
- return self._make_request_urllib(data)
250
+ return self._stream_urllib(data)
220
251
 
221
- def _make_request_streaming_requests(self, data):
252
+ def _stream_requests(self, data):
222
253
  try:
223
254
  response = requests.post(
224
255
  self.api_url,
@@ -233,6 +264,8 @@ Respond in Russian. Write code in English."""
233
264
  response.raise_for_status()
234
265
 
235
266
  full_response = ""
267
+ in_code_block = False
268
+
236
269
  for line in response.iter_lines():
237
270
  if line:
238
271
  line = line.decode('utf-8')
@@ -246,18 +279,34 @@ Respond in Russian. Write code in English."""
246
279
  delta = chunk['choices'][0].get('delta', {})
247
280
  content = delta.get('content', '')
248
281
  if content:
249
- # Показываем только если это не внутри кодового блока
250
- print(content, end="", flush=True)
251
282
  full_response += content
283
+
284
+ # Определяем, нужно ли показывать
285
+ if '[[CREATE:' in full_response[-100:] or '[[MODIFY:' in full_response[-100:]:
286
+ in_code_block = True
287
+
288
+ if '[[END]]' in full_response[-20:]:
289
+ in_code_block = False
290
+
291
+ # Показываем только текст вне кодовых блоков
292
+ if not in_code_block:
293
+ # Не показываем маркеры
294
+ display = content.replace('[[CREATE:', '').replace('[[MODIFY:', '').replace('[[END]]', '').replace('[[EXEC:', '')
295
+ if display.strip():
296
+ print(display, end="", flush=True)
297
+
298
+ # Обрабатываем буфер для создания файлов
299
+ self._process_stream_chunk(content)
252
300
  except:
253
301
  continue
302
+
254
303
  print()
255
304
  return full_response
256
305
  except Exception as e:
257
306
  print_error(f"API Error: {e}")
258
307
  return ""
259
308
 
260
- def _make_request_urllib(self, data):
309
+ def _stream_urllib(self, data):
261
310
  try:
262
311
  json_data = json.dumps({**data, "stream": True}).encode('utf-8')
263
312
  req = urllib.request.Request(
@@ -272,6 +321,8 @@ Respond in Russian. Write code in English."""
272
321
  ctx = ssl.create_default_context()
273
322
 
274
323
  full_response = ""
324
+ in_code_block = False
325
+
275
326
  with urllib.request.urlopen(req, context=ctx, timeout=120) as response:
276
327
  for line in response:
277
328
  line = line.decode('utf-8').strip()
@@ -285,8 +336,20 @@ Respond in Russian. Write code in English."""
285
336
  delta = chunk['choices'][0].get('delta', {})
286
337
  content = delta.get('content', '')
287
338
  if content:
288
- print(content, end="", flush=True)
289
339
  full_response += content
340
+
341
+ if '[[CREATE:' in full_response[-100:] or '[[MODIFY:' in full_response[-100:]:
342
+ in_code_block = True
343
+
344
+ if '[[END]]' in full_response[-20:]:
345
+ in_code_block = False
346
+
347
+ if not in_code_block:
348
+ display = content.replace('[[CREATE:', '').replace('[[MODIFY:', '').replace('[[END]]', '').replace('[[EXEC:', '')
349
+ if display.strip():
350
+ print(display, end="", flush=True)
351
+
352
+ self._process_stream_chunk(content)
290
353
  except:
291
354
  continue
292
355
  print()
@@ -318,155 +381,31 @@ Respond in Russian. Write code in English."""
318
381
  if response:
319
382
  self.conversation_history.append({"role": "assistant", "content": response})
320
383
 
321
- actions = self._parse_actions(response)
322
- if actions:
323
- self._execute_actions(actions)
384
+ # Выполняем оставшиеся команды
385
+ if self.pending_commands:
386
+ print_header("\nCOMMANDS")
387
+ for i, cmd in enumerate(self.pending_commands, 1):
388
+ print(f" {i}. {cmd}")
389
+
390
+ if self.auto_approve:
391
+ choice = 'A'
392
+ else:
393
+ print("\n[A] Execute all [S] Skip [C] Cancel")
394
+ choice = input("> ").strip().upper()
395
+
396
+ if choice == 'A':
397
+ for cmd in self.pending_commands:
398
+ if self.security.is_safe_command(cmd):
399
+ self.file_manager.execute_command(cmd)
400
+ self.stats["commands_executed"].append(cmd)
401
+ elif choice == 'S':
402
+ print_warning("Skipped")
324
403
 
325
404
  self.stats["messages_sent"] += 1
326
405
  return response
327
406
  except Exception as e:
328
407
  return print_error(f"Error: {e}")
329
408
 
330
- def _parse_actions(self, response):
331
- actions = []
332
-
333
- # CREATE с [[END]]
334
- create_pattern = r'\[\[CREATE:(.+?)\]\](.*?)\[\[END\]\]'
335
- for match in re.finditer(create_pattern, response, re.DOTALL):
336
- path = match.group(1).strip()
337
- content = match.group(2).strip()
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):
365
- path = match.group(1).strip()
366
- content = match.group(2).strip()
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})
371
-
372
- # DELETE
373
- for match in re.finditer(r'\[\[DELETE:(.+?)\]\]', response):
374
- actions.append({'type': 'delete', 'path': match.group(1).strip()})
375
-
376
- # READ
377
- for match in re.finditer(r'\[\[READ:(.+?)\]\]', response):
378
- actions.append({'type': 'read', 'path': match.group(1).strip()})
379
-
380
- # LIST
381
- for match in re.finditer(r'\[\[LIST:(.+?)\]\]', response):
382
- actions.append({'type': 'list', 'path': match.group(1).strip()})
383
-
384
- # EXEC
385
- for match in re.finditer(r'\[\[EXEC:(.+?)\]\]', response):
386
- actions.append({'type': 'exec', 'command': match.group(1).strip()})
387
-
388
- return actions
389
-
390
- def _execute_actions(self, actions):
391
- if not actions:
392
- return
393
-
394
- file_actions = [a for a in actions if a['type'] in ['create', 'modify', 'delete', 'read', 'list']]
395
- exec_actions = [a for a in actions if a['type'] == 'exec']
396
-
397
- # Файловые операции - автоматически, показываем только информацию
398
- if file_actions:
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
-
414
- for action in file_actions:
415
- self._execute_action(action)
416
-
417
- # Команды - требуют подтверждения
418
- if exec_actions:
419
- print_header("\nCOMMANDS (confirmation required)")
420
- for i, action in enumerate(exec_actions, 1):
421
- print(f" {i}. {action['command']}")
422
-
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()
428
-
429
- if choice == 'A':
430
- for action in exec_actions:
431
- self._execute_action(action)
432
- elif choice == 'S':
433
- print_warning("Skipped")
434
- elif choice == 'C':
435
- print_warning("Cancelled")
436
-
437
- def _execute_action(self, action):
438
- try:
439
- if action['type'] == 'create':
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'])
443
-
444
- elif action['type'] == 'modify':
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'])
448
-
449
- elif action['type'] == 'delete':
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'])
453
-
454
- elif action['type'] == 'read':
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'])
458
-
459
- elif action['type'] == 'list':
460
- self.file_manager.list_directory(action['path'])
461
-
462
- elif action['type'] == 'exec':
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'])
466
-
467
- except Exception as e:
468
- print_error(f"Action failed: {e}")
469
-
470
409
  def _handle_command(self, user_input):
471
410
  parts = user_input.split()
472
411
  cmd = parts[0].lower()
@@ -515,7 +454,7 @@ COMMANDS:
515
454
  print()
516
455
  print_info(f"Project: {self.project_path}")
517
456
  print_info(f"Model: {self.current_model}")
518
- print_info("Files: auto | Commands: confirmation required")
457
+ print_info("Files: auto-create in real-time | Commands: confirmation required")
519
458
  print_info("/help for commands\n")
520
459
 
521
460
  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.2
3
+ Version: 2.1.5
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.2",
5
+ version="2.1.5",
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