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
hanus/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ """
2
+ Hanus Code — Agente de programación autónomo para la terminal.
3
+ """
4
+ __version__ = "1.0.0"
5
+ __author__ = "Hanus"
hanus/__main__.py ADDED
@@ -0,0 +1,10 @@
1
+ """Permite ejecutar: python -m hanus"""
2
+ from hanus.agent_runner import cli_main
3
+
4
+
5
+ def main():
6
+ cli_main()
7
+
8
+
9
+ if __name__ == "__main__":
10
+ main()
@@ -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