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.
- hanus/__init__.py +5 -0
- hanus/__main__.py +10 -0
- hanus/action_handlers.py +76 -0
- hanus/action_parser.py +82 -0
- hanus/agent_runner.py +1445 -0
- hanus/analysis/__init__.py +5 -0
- hanus/analysis/debt.py +702 -0
- hanus/analysis/dependencies.py +475 -0
- hanus/cache/__init__.py +5 -0
- hanus/cache/response_cache.py +560 -0
- hanus/config.py +401 -0
- hanus/connectors/__init__.py +19 -0
- hanus/connectors/base.py +114 -0
- hanus/connectors/claude_connector.py +146 -0
- hanus/connectors/gemini_connector.py +141 -0
- hanus/connectors/glm_connector.py +160 -0
- hanus/connectors/ollama_connector.py +174 -0
- hanus/connectors/openai_connector.py +122 -0
- hanus/connectors/registry.py +26 -0
- hanus/context/__init__.py +7 -0
- hanus/context/manager.py +837 -0
- hanus/context/selective.py +626 -0
- hanus/error_recovery/__init__.py +5 -0
- hanus/error_recovery/auto_fix.py +605 -0
- hanus/hooks/__init__.py +5 -0
- hanus/hooks/manager.py +247 -0
- hanus/instincts/__init__.py +44 -0
- hanus/instincts/cli.py +372 -0
- hanus/instincts/detector.py +281 -0
- hanus/instincts/evolver.py +361 -0
- hanus/instincts/manager.py +343 -0
- hanus/instincts/types.py +253 -0
- hanus/logger.py +81 -0
- hanus/memory/__init__.py +8 -0
- hanus/memory/manager.py +265 -0
- hanus/memory/types.py +119 -0
- hanus/monitor.py +341 -0
- hanus/parallel/__init__.py +5 -0
- hanus/parallel/executor.py +300 -0
- hanus/permissions.py +182 -0
- hanus/plan/__init__.py +8 -0
- hanus/plan/mode.py +267 -0
- hanus/plan/models.py +152 -0
- hanus/plugin_manager.py +754 -0
- hanus/plugin_registry.py +391 -0
- hanus/plugins/__init__.py +1 -0
- hanus/plugins/arena.py +630 -0
- hanus/plugins/code_review.py +123 -0
- hanus/plugins/cortex.py +1750 -0
- hanus/plugins/deps_check.py +27 -0
- hanus/plugins/git_ops.py +33 -0
- hanus/plugins/metasploit.py +530 -0
- hanus/plugins/notes.py +583 -0
- hanus/plugins/search_code.py +59 -0
- hanus/plugins/searchsploit.py +495 -0
- hanus/plugins/strategist.py +175 -0
- hanus/plugins/webui.py +5200 -0
- hanus/profiles.py +479 -0
- hanus/profiles_builtin/__init__.py +0 -0
- hanus/profiles_builtin/architect/profile.yaml +12 -0
- hanus/profiles_builtin/architect/system_prompt.txt +71 -0
- hanus/profiles_builtin/deep/profile.yaml +12 -0
- hanus/profiles_builtin/deep/system_prompt.txt +66 -0
- hanus/profiles_builtin/developer/__init__.py +0 -0
- hanus/profiles_builtin/developer/profile.yaml +9 -0
- hanus/profiles_builtin/developer/system_prompt.txt +176 -0
- hanus/profiles_builtin/speed/profile.yaml +12 -0
- hanus/profiles_builtin/speed/system_prompt.txt +51 -0
- hanus/project_tools.py +177 -0
- hanus/query_engine.py +1594 -0
- hanus/rules/__init__.py +237 -0
- hanus/search/__init__.py +5 -0
- hanus/search/semantic.py +596 -0
- hanus/session_manager.py +547 -0
- hanus/skill_manager.py +702 -0
- hanus/skills/__init__.py +4 -0
- hanus/subagent/__init__.py +8 -0
- hanus/subagent/agents/__init__.py +253 -0
- hanus/subagent/manager.py +309 -0
- hanus/subagent/types.py +266 -0
- hanus/suggestions/__init__.py +5 -0
- hanus/suggestions/proactive.py +451 -0
- hanus/tasks/__init__.py +8 -0
- hanus/tasks/manager.py +330 -0
- hanus/tasks/models.py +106 -0
- hanus/terminal_prompt.py +166 -0
- hanus/tools.py +1849 -0
- hanus/ui.py +939 -0
- hanuscode-1.0.0.dist-info/METADATA +1151 -0
- hanuscode-1.0.0.dist-info/RECORD +93 -0
- hanuscode-1.0.0.dist-info/WHEEL +5 -0
- hanuscode-1.0.0.dist-info/entry_points.txt +2 -0
- hanuscode-1.0.0.dist-info/top_level.txt +1 -0
hanus/plugin_registry.py
ADDED
|
@@ -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
|