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/__init__.py
ADDED
hanus/__main__.py
ADDED
hanus/action_handlers.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# action_handlers.py — Adaptador legacy de acciones XML → ToolExecutor
|
|
2
|
+
"""
|
|
3
|
+
Mantiene ActionDispatcher para compatibilidad con plugins XML legacy.
|
|
4
|
+
En v2.0 el flujo principal va por QueryEngine + ToolExecutor directo.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
import json
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Dict, Optional, TYPE_CHECKING
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from hanus.config import HanusConfig
|
|
13
|
+
from hanus.ui import UI
|
|
14
|
+
|
|
15
|
+
from hanus.tools import ToolExecutor
|
|
16
|
+
from hanus.permissions import PermissionManager, PermissionMode
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ActionDispatcher:
|
|
20
|
+
def __init__(self, config: "HanusConfig", ui: "UI", task_manager=None):
|
|
21
|
+
self.config = config
|
|
22
|
+
self.ui = ui
|
|
23
|
+
perms = PermissionManager(PermissionMode.BYPASS)
|
|
24
|
+
self.executor = ToolExecutor(config.root_dir, perms, task_manager=task_manager)
|
|
25
|
+
self._notes = _NotesStore(config.root_dir / "agent_notes.json")
|
|
26
|
+
|
|
27
|
+
def handle_read(self, action: Dict) -> Optional[str]:
|
|
28
|
+
result = self.executor.execute("read_file", {"path": action.get("path","")})
|
|
29
|
+
if not result.success: return None
|
|
30
|
+
content = result.output
|
|
31
|
+
MAX = self.config.max_file_read_chars
|
|
32
|
+
if len(content) > MAX: content = content[:MAX] + "\n…(truncado)"
|
|
33
|
+
reason = action.get("reason","")
|
|
34
|
+
header = f"[Archivo: {action.get('path','?')}]" + (f" ({reason})" if reason else "")
|
|
35
|
+
return f"{header}\n```\n{content}\n```"
|
|
36
|
+
|
|
37
|
+
def handle_write(self, action: Dict) -> bool:
|
|
38
|
+
t = action.get("type","write_file")
|
|
39
|
+
tool_map = {"write_file":"write_file","create_file":"create_file","append_to_file":"append_to_file"}
|
|
40
|
+
result = self.executor.execute(tool_map.get(t,"write_file"), {
|
|
41
|
+
"path": action.get("path",""), "content": action.get("content","")
|
|
42
|
+
})
|
|
43
|
+
if result.success: self.ui.success(f" ✓ {action.get('path','?')}")
|
|
44
|
+
else: self.ui.error(result.error)
|
|
45
|
+
return result.success
|
|
46
|
+
|
|
47
|
+
def handle_exec(self, action: Dict) -> Optional[str]:
|
|
48
|
+
if action.get("type") == "exec_cmd":
|
|
49
|
+
r = self.executor.execute("exec_cmd", {"cmd": action.get("cmd","")})
|
|
50
|
+
else:
|
|
51
|
+
r = self.executor.execute("exec_file", {"path": action.get("path","")})
|
|
52
|
+
return r.to_str()
|
|
53
|
+
|
|
54
|
+
def handle_note(self, action: Dict) -> Optional[str]:
|
|
55
|
+
typ = action.get("type"); name = action.get("name","").strip()
|
|
56
|
+
notes = self._notes.load()
|
|
57
|
+
if typ == "list_note":
|
|
58
|
+
return "Notas:\n" + "\n".join(f" • {k}" for k in notes) if notes else "Sin notas."
|
|
59
|
+
if not name: return None
|
|
60
|
+
if typ == "add_note":
|
|
61
|
+
notes[name] = action.get("data","").strip(); self._notes.save(notes)
|
|
62
|
+
return f"Nota '{name}' guardada."
|
|
63
|
+
if typ == "read_note":
|
|
64
|
+
return f"[Nota: {name}]\n{notes.get(name,'(no existe)')}"
|
|
65
|
+
return None
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class _NotesStore:
|
|
69
|
+
def __init__(self, path: Path): self.path = path
|
|
70
|
+
def load(self) -> Dict:
|
|
71
|
+
if not self.path.exists(): return {}
|
|
72
|
+
try: return json.loads(self.path.read_text(encoding="utf-8"))
|
|
73
|
+
except: return {}
|
|
74
|
+
def save(self, notes: Dict):
|
|
75
|
+
try: self.path.write_text(json.dumps(notes, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
76
|
+
except: pass
|
hanus/action_parser.py
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# action_parser.py — Parser de acciones XML (compatibilidad legacy)
|
|
2
|
+
"""
|
|
3
|
+
En v2.0 el bucle agéntico usa tool_calls nativos del modelo.
|
|
4
|
+
Este módulo se mantiene para plugins que emitan respuestas en formato XML.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
import re
|
|
8
|
+
import json
|
|
9
|
+
from typing import List, Dict
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def parse_actions(text: str) -> List[Dict]:
|
|
13
|
+
actions: List[Dict] = []
|
|
14
|
+
actions.extend(_parse_json(text))
|
|
15
|
+
actions.extend(_parse_xml_blocks(text))
|
|
16
|
+
actions.extend(_parse_xml_selfclose(text))
|
|
17
|
+
actions.extend(_parse_plugin(text))
|
|
18
|
+
seen, unique = set(), []
|
|
19
|
+
for a in actions:
|
|
20
|
+
key = a.get("raw", str(a))
|
|
21
|
+
if key not in seen:
|
|
22
|
+
seen.add(key); unique.append(a)
|
|
23
|
+
return unique
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _parse_json(text: str) -> List[Dict]:
|
|
27
|
+
try:
|
|
28
|
+
data = json.loads(text.strip())
|
|
29
|
+
if not isinstance(data, dict): return []
|
|
30
|
+
_map = {
|
|
31
|
+
"create_file": lambda d: {"type": "create_file", "path": d.get("path"), "content": d.get("content","")},
|
|
32
|
+
"write_file": lambda d: {"type": "write_file", "path": d.get("path"), "content": d.get("content","")},
|
|
33
|
+
"exec_cmd": lambda d: {"type": "exec_cmd", "cmd": d.get("cmd") or d.get("command"), "reason": d.get("reason","")},
|
|
34
|
+
}
|
|
35
|
+
actions = []
|
|
36
|
+
for key, builder in _map.items():
|
|
37
|
+
if key in data and isinstance(data[key], dict):
|
|
38
|
+
a = builder(data[key]); a["raw"] = text; actions.append(a)
|
|
39
|
+
return actions
|
|
40
|
+
except Exception:
|
|
41
|
+
return []
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _parse_xml_blocks(text: str) -> List[Dict]:
|
|
45
|
+
actions = []
|
|
46
|
+
TAGS = r"write_file|create_file|append_to_file|exec_cmd|exec_file"
|
|
47
|
+
for m in re.finditer(r'<(' + TAGS + r')\b([^>]*)>(.*?)</\1>', text, re.DOTALL | re.I):
|
|
48
|
+
tag = m.group(1).lower(); attrs = _attrs(m.group(2)); content = _unfence(m.group(3).strip())
|
|
49
|
+
a = {"type": tag, "raw": m.group(0)}; a.update(attrs)
|
|
50
|
+
if tag == "exec_cmd": a["cmd"] = attrs.get("cmd") or attrs.get("command") or content
|
|
51
|
+
else: a["path"] = attrs.get("path",""); a["content"] = content
|
|
52
|
+
actions.append(a)
|
|
53
|
+
return actions
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _parse_xml_selfclose(text: str) -> List[Dict]:
|
|
57
|
+
actions = []
|
|
58
|
+
for m in re.finditer(r'<(read_file|exec_cmd|exec_file)\b([^>]*)/?>', text, re.I):
|
|
59
|
+
tag = m.group(1).lower(); attrs = _attrs(m.group(2))
|
|
60
|
+
a = {"type": tag, "raw": m.group(0)}; a.update(attrs)
|
|
61
|
+
if tag == "exec_cmd": a["cmd"] = attrs.get("cmd") or attrs.get("command","")
|
|
62
|
+
actions.append(a)
|
|
63
|
+
return actions
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _parse_plugin(text: str) -> List[Dict]:
|
|
67
|
+
actions = []
|
|
68
|
+
for m in re.finditer(r'<run_plugin\b([^>]*)/?>', text, re.I):
|
|
69
|
+
attrs = _attrs(m.group(1)); a = {"type": "run_plugin", "raw": m.group(0)}; a.update(attrs); actions.append(a)
|
|
70
|
+
return actions
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _attrs(s: str) -> Dict[str, str]:
|
|
74
|
+
return {m.group(1): m.group(2) for m in re.finditer(r'(\w+)\s*=\s*["\']([^"\']*)["\']', s or "")}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _unfence(content: str) -> str:
|
|
78
|
+
content = content.strip()
|
|
79
|
+
if content.startswith("```"):
|
|
80
|
+
lines = content.splitlines()
|
|
81
|
+
if len(lines) > 2: return "\n".join(lines[1:-1]).strip()
|
|
82
|
+
return content
|