hanuscode 1.0.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.
Files changed (93) hide show
  1. hanus/__init__.py +5 -0
  2. hanus/__main__.py +10 -0
  3. hanus/action_handlers.py +76 -0
  4. hanus/action_parser.py +82 -0
  5. hanus/agent_runner.py +1445 -0
  6. hanus/analysis/__init__.py +5 -0
  7. hanus/analysis/debt.py +702 -0
  8. hanus/analysis/dependencies.py +475 -0
  9. hanus/cache/__init__.py +5 -0
  10. hanus/cache/response_cache.py +560 -0
  11. hanus/config.py +401 -0
  12. hanus/connectors/__init__.py +19 -0
  13. hanus/connectors/base.py +114 -0
  14. hanus/connectors/claude_connector.py +146 -0
  15. hanus/connectors/gemini_connector.py +141 -0
  16. hanus/connectors/glm_connector.py +160 -0
  17. hanus/connectors/ollama_connector.py +174 -0
  18. hanus/connectors/openai_connector.py +122 -0
  19. hanus/connectors/registry.py +26 -0
  20. hanus/context/__init__.py +7 -0
  21. hanus/context/manager.py +837 -0
  22. hanus/context/selective.py +626 -0
  23. hanus/error_recovery/__init__.py +5 -0
  24. hanus/error_recovery/auto_fix.py +605 -0
  25. hanus/hooks/__init__.py +5 -0
  26. hanus/hooks/manager.py +247 -0
  27. hanus/instincts/__init__.py +44 -0
  28. hanus/instincts/cli.py +372 -0
  29. hanus/instincts/detector.py +281 -0
  30. hanus/instincts/evolver.py +361 -0
  31. hanus/instincts/manager.py +343 -0
  32. hanus/instincts/types.py +253 -0
  33. hanus/logger.py +81 -0
  34. hanus/memory/__init__.py +8 -0
  35. hanus/memory/manager.py +265 -0
  36. hanus/memory/types.py +119 -0
  37. hanus/monitor.py +341 -0
  38. hanus/parallel/__init__.py +5 -0
  39. hanus/parallel/executor.py +300 -0
  40. hanus/permissions.py +182 -0
  41. hanus/plan/__init__.py +8 -0
  42. hanus/plan/mode.py +267 -0
  43. hanus/plan/models.py +152 -0
  44. hanus/plugin_manager.py +754 -0
  45. hanus/plugin_registry.py +391 -0
  46. hanus/plugins/__init__.py +1 -0
  47. hanus/plugins/arena.py +630 -0
  48. hanus/plugins/code_review.py +123 -0
  49. hanus/plugins/cortex.py +1750 -0
  50. hanus/plugins/deps_check.py +27 -0
  51. hanus/plugins/git_ops.py +33 -0
  52. hanus/plugins/metasploit.py +530 -0
  53. hanus/plugins/notes.py +583 -0
  54. hanus/plugins/search_code.py +59 -0
  55. hanus/plugins/searchsploit.py +495 -0
  56. hanus/plugins/strategist.py +175 -0
  57. hanus/plugins/webui.py +5200 -0
  58. hanus/profiles.py +479 -0
  59. hanus/profiles_builtin/__init__.py +0 -0
  60. hanus/profiles_builtin/architect/profile.yaml +12 -0
  61. hanus/profiles_builtin/architect/system_prompt.txt +71 -0
  62. hanus/profiles_builtin/deep/profile.yaml +12 -0
  63. hanus/profiles_builtin/deep/system_prompt.txt +66 -0
  64. hanus/profiles_builtin/developer/__init__.py +0 -0
  65. hanus/profiles_builtin/developer/profile.yaml +9 -0
  66. hanus/profiles_builtin/developer/system_prompt.txt +176 -0
  67. hanus/profiles_builtin/speed/profile.yaml +12 -0
  68. hanus/profiles_builtin/speed/system_prompt.txt +51 -0
  69. hanus/project_tools.py +177 -0
  70. hanus/query_engine.py +1594 -0
  71. hanus/rules/__init__.py +237 -0
  72. hanus/search/__init__.py +5 -0
  73. hanus/search/semantic.py +596 -0
  74. hanus/session_manager.py +547 -0
  75. hanus/skill_manager.py +702 -0
  76. hanus/skills/__init__.py +4 -0
  77. hanus/subagent/__init__.py +8 -0
  78. hanus/subagent/agents/__init__.py +253 -0
  79. hanus/subagent/manager.py +309 -0
  80. hanus/subagent/types.py +266 -0
  81. hanus/suggestions/__init__.py +5 -0
  82. hanus/suggestions/proactive.py +451 -0
  83. hanus/tasks/__init__.py +8 -0
  84. hanus/tasks/manager.py +330 -0
  85. hanus/tasks/models.py +106 -0
  86. hanus/terminal_prompt.py +166 -0
  87. hanus/tools.py +1849 -0
  88. hanus/ui.py +939 -0
  89. hanuscode-1.0.0.dist-info/METADATA +1151 -0
  90. hanuscode-1.0.0.dist-info/RECORD +93 -0
  91. hanuscode-1.0.0.dist-info/WHEEL +5 -0
  92. hanuscode-1.0.0.dist-info/entry_points.txt +2 -0
  93. hanuscode-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,391 @@
1
+ # hanus/plugin_registry.py
2
+ """
3
+ Plugin Marketplace/Registry.
4
+
5
+ Permite descubrir e instalar plugins desde GitHub u otras fuentes.
6
+ """
7
+ from __future__ import annotations
8
+ import json
9
+ import time
10
+ import urllib.request
11
+ import urllib.error
12
+ from dataclasses import dataclass, field
13
+ from pathlib import Path
14
+ from typing import Dict, List, Optional, Any
15
+ from datetime import datetime
16
+
17
+
18
+ @dataclass
19
+ class PluginInfo:
20
+ """Información de un plugin disponible."""
21
+ name: str
22
+ display_name: str
23
+ description: str
24
+ author: str
25
+ version: str
26
+ repository: str
27
+ download_url: str
28
+ category: str = "general"
29
+ tags: List[str] = field(default_factory=list)
30
+ installed: bool = False
31
+ installed_version: Optional[str] = None
32
+ stars: int = 0
33
+ updated: str = ""
34
+
35
+ def to_dict(self) -> Dict:
36
+ return {
37
+ "name": self.name,
38
+ "display_name": self.display_name,
39
+ "description": self.description,
40
+ "author": self.author,
41
+ "version": self.version,
42
+ "repository": self.repository,
43
+ "category": self.category,
44
+ "tags": self.tags,
45
+ "installed": self.installed,
46
+ "installed_version": self.installed_version,
47
+ "stars": self.stars,
48
+ "updated": self.updated,
49
+ }
50
+
51
+
52
+ class PluginRegistry:
53
+ """
54
+ Registry de plugins disponibles.
55
+
56
+ Features:
57
+ - Descubrimiento desde GitHub
58
+ - Instalación con un comando
59
+ - Actualizaciones
60
+ - Metadatos de plugins
61
+ """
62
+
63
+ # URL del registry (puede ser GitHub repo o API)
64
+ REGISTRY_URL = "https://raw.githubusercontent.com/hanuscode/hanuscode-plugins/main/registry.json"
65
+
66
+ # Fallback con plugins builtin
67
+ BUILTIN_REGISTRY = [
68
+ {
69
+ "name": "webui",
70
+ "display_name": "Web UI",
71
+ "description": "Interfaz web para HanusCode",
72
+ "author": "hanus",
73
+ "version": "1.0.0",
74
+ "category": "core",
75
+ "tags": ["ui", "web"],
76
+ },
77
+ {
78
+ "name": "cortex",
79
+ "display_name": "Cortex Memory",
80
+ "description": "Sistema de memoria semántica con knowledge graphs",
81
+ "author": "hanus",
82
+ "version": "1.0.0",
83
+ "category": "memory",
84
+ "tags": ["memory", "graph", "ai"],
85
+ },
86
+ {
87
+ "name": "search_code",
88
+ "display_name": "Code Search",
89
+ "description": "Búsqueda avanzada de código",
90
+ "author": "hanus",
91
+ "version": "1.0.0",
92
+ "category": "tools",
93
+ "tags": ["search", "code"],
94
+ },
95
+ {
96
+ "name": "git_ops",
97
+ "display_name": "Git Operations",
98
+ "description": "Operaciones de Git avanzadas",
99
+ "author": "hanus",
100
+ "version": "1.0.0",
101
+ "category": "tools",
102
+ "tags": ["git", "version-control"],
103
+ },
104
+ {
105
+ "name": "strategist",
106
+ "display_name": "Strategist",
107
+ "description": "Planificación estratégica de proyectos",
108
+ "author": "hanus",
109
+ "version": "1.0.0",
110
+ "category": "ai",
111
+ "tags": ["planning", "strategy"],
112
+ },
113
+ {
114
+ "name": "deps_check",
115
+ "display_name": "Dependency Checker",
116
+ "description": "Verificación de dependencias",
117
+ "author": "hanus",
118
+ "version": "1.0.0",
119
+ "category": "tools",
120
+ "tags": ["dependencies", "security"],
121
+ },
122
+ ]
123
+
124
+ def __init__(self, plugins_dir: Path, cache_dir: Optional[Path] = None):
125
+ self.plugins_dir = plugins_dir
126
+ self.cache_dir = cache_dir or (Path.home() / ".hanus" / "registry_cache")
127
+ self.cache_dir.mkdir(parents=True, exist_ok=True)
128
+
129
+ self._cache_file = self.cache_dir / "registry_cache.json"
130
+ self._cache: Dict[str, PluginInfo] = {}
131
+ self._last_update = 0
132
+
133
+ def fetch_registry(self, force: bool = False) -> List[PluginInfo]:
134
+ """
135
+ Obtiene el registry de plugins.
136
+
137
+ Args:
138
+ force: Forzar actualización
139
+
140
+ Returns:
141
+ Lista de plugins disponibles
142
+ """
143
+ # Verificar cache
144
+ if not force and self._cache:
145
+ if time.time() - self._last_update < 3600: # 1 hora
146
+ return list(self._cache.values())
147
+
148
+ # Intentar cargar desde disco
149
+ if not force and self._load_cache():
150
+ return list(self._cache.values())
151
+
152
+ # Intentar obtener de remoto
153
+ try:
154
+ plugins = self._fetch_remote()
155
+ self._update_cache(plugins)
156
+ return plugins
157
+ except Exception as e:
158
+ print(f"[PluginRegistry] Could not fetch remote: {e}")
159
+ # Usar builtin como fallback
160
+ plugins = [PluginInfo(
161
+ name=p["name"],
162
+ display_name=p.get("display_name", p["name"]),
163
+ description=p.get("description", ""),
164
+ author=p.get("author", "unknown"),
165
+ version=p.get("version", "1.0.0"),
166
+ repository=p.get("repository", ""),
167
+ download_url=p.get("download_url", ""),
168
+ category=p.get("category", "general"),
169
+ tags=p.get("tags", []),
170
+ ) for p in self.BUILTIN_REGISTRY]
171
+ self._update_cache(plugins)
172
+ return plugins
173
+
174
+ def search(self, query: str) -> List[PluginInfo]:
175
+ """
176
+ Busca plugins por nombre o descripción.
177
+
178
+ Args:
179
+ query: Texto de búsqueda
180
+
181
+ Returns:
182
+ Lista de plugins que coinciden
183
+ """
184
+ plugins = self.fetch_registry()
185
+ query_lower = query.lower()
186
+
187
+ results = []
188
+ for plugin in plugins:
189
+ if (query_lower in plugin.name.lower() or
190
+ query_lower in plugin.description.lower() or
191
+ query_lower in plugin.display_name.lower() or
192
+ any(query_lower in tag for tag in plugin.tags)):
193
+ results.append(plugin)
194
+
195
+ return results
196
+
197
+ def get_plugin(self, name: str) -> Optional[PluginInfo]:
198
+ """Obtiene información de un plugin específico."""
199
+ plugins = self.fetch_registry()
200
+ for plugin in plugins:
201
+ if plugin.name == name:
202
+ # Verificar si está instalado
203
+ plugin.installed = (self.plugins_dir / f"{name}.py").exists()
204
+ return plugin
205
+ return None
206
+
207
+ def install(self, name: str) -> Tuple[bool, str]:
208
+ """
209
+ Instala un plugin.
210
+
211
+ Args:
212
+ name: Nombre del plugin
213
+
214
+ Returns:
215
+ (success, message)
216
+ """
217
+ plugin = self.get_plugin(name)
218
+ if not plugin:
219
+ return False, f"Plugin '{name}' not found in registry"
220
+
221
+ if plugin.installed:
222
+ return True, f"Plugin '{name}' is already installed"
223
+
224
+ # Si hay URL de descarga
225
+ if plugin.download_url:
226
+ try:
227
+ content = self._download(plugin.download_url)
228
+ target = self.plugins_dir / f"{name}.py"
229
+ target.write_text(content, encoding="utf-8")
230
+ return True, f"Plugin '{name}' installed successfully"
231
+ except Exception as e:
232
+ return False, f"Error installing '{name}': {e}"
233
+
234
+ # Si no hay URL, crear desde template
235
+ return False, f"Plugin '{name}' has no download URL. Manual installation required."
236
+
237
+ def uninstall(self, name: str) -> Tuple[bool, str]:
238
+ """
239
+ Desinstala un plugin.
240
+
241
+ Args:
242
+ name: Nombre del plugin
243
+
244
+ Returns:
245
+ (success, message)
246
+ """
247
+ plugin_file = self.plugins_dir / f"{name}.py"
248
+
249
+ if not plugin_file.exists():
250
+ return False, f"Plugin '{name}' is not installed"
251
+
252
+ try:
253
+ plugin_file.unlink()
254
+ return True, f"Plugin '{name}' uninstalled successfully"
255
+ except Exception as e:
256
+ return False, f"Error uninstalling '{name}': {e}"
257
+
258
+ def update(self, name: str) -> Tuple[bool, str]:
259
+ """
260
+ Actualiza un plugin.
261
+
262
+ Args:
263
+ name: Nombre del plugin
264
+
265
+ Returns:
266
+ (success, message)
267
+ """
268
+ plugin = self.get_plugin(name)
269
+ if not plugin:
270
+ return False, f"Plugin '{name}' not found"
271
+
272
+ if not plugin.installed:
273
+ return False, f"Plugin '{name}' is not installed"
274
+
275
+ # Reinstalar
276
+ return self.install(name)
277
+
278
+ def list_installed(self) -> List[str]:
279
+ """Lista plugins instalados."""
280
+ if not self.plugins_dir.exists():
281
+ return []
282
+
283
+ return [
284
+ p.stem for p in self.plugins_dir.glob("*.py")
285
+ if not p.name.startswith("_")
286
+ ]
287
+
288
+ def get_stats(self) -> Dict[str, Any]:
289
+ """Obtiene estadísticas del registry."""
290
+ plugins = self.fetch_registry()
291
+ installed = self.list_installed()
292
+
293
+ return {
294
+ "total_plugins": len(plugins),
295
+ "installed": len(installed),
296
+ "categories": len(set(p.category for p in plugins)),
297
+ "last_update": datetime.fromtimestamp(self._last_update).isoformat() if self._last_update else None,
298
+ }
299
+
300
+ # ══════════════════════════════════════════════════════════════════════════
301
+ # MÉTODOS PRIVADOS
302
+ # ══════════════════════════════════════════════════════════════════════════
303
+
304
+ def _fetch_remote(self) -> List[PluginInfo]:
305
+ """Obtiene registry del servidor remoto."""
306
+ req = urllib.request.Request(
307
+ self.REGISTRY_URL,
308
+ headers={"User-Agent": "HanusCode/1.0"}
309
+ )
310
+
311
+ with urllib.request.urlopen(req, timeout=10) as response:
312
+ data = json.loads(response.read().decode("utf-8"))
313
+
314
+ plugins = []
315
+ for item in data.get("plugins", []):
316
+ plugins.append(PluginInfo(
317
+ name=item["name"],
318
+ display_name=item.get("display_name", item["name"]),
319
+ description=item.get("description", ""),
320
+ author=item.get("author", "unknown"),
321
+ version=item.get("version", "1.0.0"),
322
+ repository=item.get("repository", ""),
323
+ download_url=item.get("download_url", ""),
324
+ category=item.get("category", "general"),
325
+ tags=item.get("tags", []),
326
+ stars=item.get("stars", 0),
327
+ updated=item.get("updated", ""),
328
+ ))
329
+
330
+ return plugins
331
+
332
+ def _download(self, url: str) -> str:
333
+ """Descarga contenido de una URL."""
334
+ req = urllib.request.Request(url, headers={"User-Agent": "HanusCode/1.0"})
335
+ with urllib.request.urlopen(req, timeout=30) as response:
336
+ return response.read().decode("utf-8")
337
+
338
+ def _update_cache(self, plugins: List[PluginInfo]) -> None:
339
+ """Actualiza el cache."""
340
+ self._cache = {p.name: p for p in plugins}
341
+ self._last_update = time.time()
342
+
343
+ # Guardar a disco
344
+ try:
345
+ data = {
346
+ "plugins": [p.to_dict() for p in plugins],
347
+ "timestamp": self._last_update,
348
+ }
349
+ self._cache_file.write_text(json.dumps(data, indent=2), encoding="utf-8")
350
+ except Exception:
351
+ pass
352
+
353
+ def _load_cache(self) -> bool:
354
+ """Carga cache desde disco."""
355
+ if not self._cache_file.exists():
356
+ return False
357
+
358
+ try:
359
+ data = json.loads(self._cache_file.read_text(encoding="utf-8"))
360
+ self._last_update = data.get("timestamp", 0)
361
+
362
+ for item in data.get("plugins", []):
363
+ self._cache[item["name"]] = PluginInfo(
364
+ name=item["name"],
365
+ display_name=item.get("display_name", item["name"]),
366
+ description=item.get("description", ""),
367
+ author=item.get("author", "unknown"),
368
+ version=item.get("version", "1.0.0"),
369
+ repository=item.get("repository", ""),
370
+ download_url=item.get("download_url", ""),
371
+ category=item.get("category", "general"),
372
+ tags=item.get("tags", []),
373
+ )
374
+
375
+ return True
376
+ except Exception:
377
+ return False
378
+
379
+
380
+ # Import typing Tuple
381
+ from typing import Tuple
382
+
383
+ # Instancia global
384
+ _registry: Optional[PluginRegistry] = None
385
+
386
+
387
+ def get_registry(plugins_dir: Path = None) -> PluginRegistry:
388
+ global _registry
389
+ if _registry is None or (plugins_dir and _registry.plugins_dir != plugins_dir):
390
+ _registry = PluginRegistry(plugins_dir or Path.cwd() / "plugins")
391
+ return _registry
@@ -0,0 +1 @@
1
+ # plugins/__init__.py