debuganything 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.
- debuganything/__init__.py +157 -0
- debuganything/_specs.py +43 -0
- debuganything/crewai.py +51 -0
- debuganything/langchain.py +48 -0
- debuganything/llamaindex.py +33 -0
- debuganything-1.0.0.dist-info/METADATA +112 -0
- debuganything-1.0.0.dist-info/RECORD +9 -0
- debuganything-1.0.0.dist-info/WHEEL +4 -0
- debuganything-1.0.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"""debuganything — Debug Anything from Python.
|
|
2
|
+
|
|
3
|
+
Thin, dependency-free wrapper around the Debug Anything CLI (``npx -y debuganything``). Every
|
|
4
|
+
method returns the Debug Anything ``ToolResult`` dict: ``findings`` cite ``evidence`` ids,
|
|
5
|
+
``limitations`` say what could not be observed, ``artifacts`` point at raw
|
|
6
|
+
files under ``~/.debuganything/sessions/<id>/``.
|
|
7
|
+
|
|
8
|
+
from debuganything import DebugAnything
|
|
9
|
+
debuganything = DEA()
|
|
10
|
+
s = da.attach(12345)
|
|
11
|
+
print(da.profile_cpu(s, duration_s=10)["findings"])
|
|
12
|
+
da.detach(s)
|
|
13
|
+
|
|
14
|
+
Framework adapters: ``debuganything.langchain``, ``debuganything.llamaindex``,
|
|
15
|
+
``debuganything.crewai``. Agents that speak MCP can skip this package and run
|
|
16
|
+
``npx -y debuganything serve`` directly.
|
|
17
|
+
"""
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import json
|
|
21
|
+
import os
|
|
22
|
+
import shutil
|
|
23
|
+
import subprocess
|
|
24
|
+
from typing import Any, Dict, List, Optional, Sequence
|
|
25
|
+
|
|
26
|
+
__version__ = "0.1.1"
|
|
27
|
+
__all__ = ["DebugAnything", "DebugAnythingError", "TOOLS", "ToolResult"]
|
|
28
|
+
|
|
29
|
+
ToolResult = Dict[str, Any]
|
|
30
|
+
|
|
31
|
+
TOOLS: Dict[str, str] = {
|
|
32
|
+
"readiness": "OS, arch, which providers are usable, what is missing and how to install it, permission status. Call first.",
|
|
33
|
+
"attach": "Resolve a target (pid, container, pod, or a command to spawn) and open a session. Returns session_id.",
|
|
34
|
+
"detach": "Stop captures, write the report, keep artifacts.",
|
|
35
|
+
"list_processes": "ps-style list (pid, comm, cpu, mem, user, container) with optional substring filter.",
|
|
36
|
+
"trace_syscalls": "Linux strace for N seconds: top syscalls by time/count, slowest calls, error counts, blocked waits.",
|
|
37
|
+
"profile_cpu": "CPU profile (py-spy / perf / sample): top frames, hottest stacks, flamegraph.svg artifact.",
|
|
38
|
+
"dump_stacks": "Every thread's stack; flags lock waits and likely deadlocks.",
|
|
39
|
+
"inspect_sockets": "Listening ports, connections by remote, CLOSE_WAIT/TIME_WAIT pileups, fd count vs ulimit.",
|
|
40
|
+
"inspect_files": "Open fds by type, fd growth over a window, largest and deleted-but-open files.",
|
|
41
|
+
"capture_traffic": "tcpdump scoped to the target's ports (needs consent=True): per-remote bytes, RSTs, retransmits, DNS.",
|
|
42
|
+
"snapshot_memory": "RSS series; Node heap snapshot with top retainers; Python stacks with locals.",
|
|
43
|
+
"diff_runs": "Diff two prior runs (e.g. snapshot_memory#1 vs #2): what grew, appeared, disappeared.",
|
|
44
|
+
"container_info": "docker inspect/stats/top/logs, cgroup limits, OOM kills, restarts.",
|
|
45
|
+
"report": "Markdown report of the whole session.",
|
|
46
|
+
"import_artifact": "Analyze raw tool output you captured yourself (strace, fs_usage, perf script, py-spy dump, sample, gdb, lldb, lsof -F, tcpdump text) into Debug Anything evidence and findings.",
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class DebugAnythingError(RuntimeError):
|
|
51
|
+
"""Raised when the Debug Anything CLI cannot be executed at all (Node/npx missing, bad JSON)."""
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class DebugAnything:
|
|
55
|
+
"""Client for the Debug Anything CLI.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
command: how to invoke DebugAnything. Defaults to ``["npx", "-y", "debuganything@latest"]``;
|
|
59
|
+
pass ``["debuganything"]`` if installed globally. ``DEA_COMMAND`` env var (shell-split) overrides.
|
|
60
|
+
timeout: subprocess timeout in seconds (Debug Anything itself caps tools at 60 s).
|
|
61
|
+
raise_on_error: raise ``DebugAnythingError`` when ``ok`` is false instead of returning the result.
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
def __init__(self, command: Optional[Sequence[str]] = None, timeout: float = 120.0, raise_on_error: bool = False) -> None:
|
|
65
|
+
env_cmd = os.environ.get("DEA_COMMAND")
|
|
66
|
+
if command is None and env_cmd:
|
|
67
|
+
command = env_cmd.split()
|
|
68
|
+
if command is None:
|
|
69
|
+
command = ["debuganything"] if shutil.which("debuganything") else ["npx", "-y", "debuganything@latest"]
|
|
70
|
+
self.command: List[str] = list(command)
|
|
71
|
+
self.timeout = timeout
|
|
72
|
+
self.raise_on_error = raise_on_error
|
|
73
|
+
|
|
74
|
+
# -- core ---------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
def call(self, tool: str, **args: Any) -> ToolResult:
|
|
77
|
+
"""Call any Debug Anything tool by name. Returns the ToolResult dict."""
|
|
78
|
+
if tool not in TOOLS:
|
|
79
|
+
raise DebugAnythingError(f"unknown tool {tool!r}; known: {', '.join(TOOLS)}")
|
|
80
|
+
argv = [*self.command, "call", tool, json.dumps({k: v for k, v in args.items() if v is not None})]
|
|
81
|
+
try:
|
|
82
|
+
proc = subprocess.run(argv, capture_output=True, text=True, timeout=self.timeout, check=False)
|
|
83
|
+
except FileNotFoundError as e:
|
|
84
|
+
raise DebugAnythingError(f"cannot run {argv[0]!r}: install Node.js >= 22 (https://nodejs.org) or set DEA_COMMAND") from e
|
|
85
|
+
except subprocess.TimeoutExpired as e:
|
|
86
|
+
raise DebugAnythingError(f"{tool} timed out after {self.timeout}s") from e
|
|
87
|
+
out = proc.stdout.strip()
|
|
88
|
+
start = out.find("{")
|
|
89
|
+
if start < 0:
|
|
90
|
+
raise DebugAnythingError(f"Debug Anything produced no JSON for {tool}: {proc.stderr.strip()[:500]}")
|
|
91
|
+
try:
|
|
92
|
+
result: ToolResult = json.loads(out[start:])
|
|
93
|
+
except json.JSONDecodeError as e:
|
|
94
|
+
raise DebugAnythingError(f"bad JSON from Debug Anything for {tool}: {e}") from e
|
|
95
|
+
if self.raise_on_error and not result.get("ok", False):
|
|
96
|
+
raise DebugAnythingError(result.get("error") or f"{tool} failed")
|
|
97
|
+
return result
|
|
98
|
+
|
|
99
|
+
# -- convenience --------------------------------------------------------
|
|
100
|
+
|
|
101
|
+
def readiness(self) -> ToolResult:
|
|
102
|
+
return self.call("readiness")
|
|
103
|
+
|
|
104
|
+
def list_processes(self, filter: Optional[str] = None, limit: int = 100) -> ToolResult:
|
|
105
|
+
return self.call("list_processes", filter=filter, limit=limit)
|
|
106
|
+
|
|
107
|
+
def attach(self, target: Any, kind: Optional[str] = None) -> str:
|
|
108
|
+
"""Attach and return the session_id. Raises DebugAnythingError if attach failed."""
|
|
109
|
+
r = self.call("attach", target=str(target), kind=kind)
|
|
110
|
+
if not r.get("ok"):
|
|
111
|
+
raise DebugAnythingError(r.get("error") or "attach failed")
|
|
112
|
+
return str(r["session_id"])
|
|
113
|
+
|
|
114
|
+
def detach(self, session_id: str) -> ToolResult:
|
|
115
|
+
return self.call("detach", session_id=session_id)
|
|
116
|
+
|
|
117
|
+
def trace_syscalls(self, session_id: str, duration_s: int = 10, categories: Optional[Sequence[str]] = None) -> ToolResult:
|
|
118
|
+
return self.call("trace_syscalls", session_id=session_id, duration_s=duration_s, categories=list(categories) if categories else None)
|
|
119
|
+
|
|
120
|
+
def profile_cpu(self, session_id: str, duration_s: int = 15) -> ToolResult:
|
|
121
|
+
return self.call("profile_cpu", session_id=session_id, duration_s=duration_s)
|
|
122
|
+
|
|
123
|
+
def dump_stacks(self, session_id: str) -> ToolResult:
|
|
124
|
+
return self.call("dump_stacks", session_id=session_id)
|
|
125
|
+
|
|
126
|
+
def inspect_sockets(self, session_id: str) -> ToolResult:
|
|
127
|
+
return self.call("inspect_sockets", session_id=session_id)
|
|
128
|
+
|
|
129
|
+
def inspect_files(self, session_id: str, window_s: int = 3) -> ToolResult:
|
|
130
|
+
return self.call("inspect_files", session_id=session_id, window_s=window_s)
|
|
131
|
+
|
|
132
|
+
def capture_traffic(self, session_id: str, duration_s: int = 10, filter: Optional[str] = None, consent: bool = False) -> ToolResult:
|
|
133
|
+
"""Requires consent=True — ask the user first; captures can contain secrets."""
|
|
134
|
+
return self.call("capture_traffic", session_id=session_id, duration_s=duration_s, filter=filter, consent=consent)
|
|
135
|
+
|
|
136
|
+
def snapshot_memory(self, session_id: str, heap: bool = True, window_s: int = 3) -> ToolResult:
|
|
137
|
+
return self.call("snapshot_memory", session_id=session_id, heap=heap, window_s=window_s)
|
|
138
|
+
|
|
139
|
+
def diff_runs(self, session_id: str, a: str, b: str) -> ToolResult:
|
|
140
|
+
return self.call("diff_runs", session_id=session_id, a=a, b=b)
|
|
141
|
+
|
|
142
|
+
def container_info(self, session_id: str) -> ToolResult:
|
|
143
|
+
return self.call("container_info", session_id=session_id)
|
|
144
|
+
|
|
145
|
+
def report(self, session_id: str, out: Optional[str] = None) -> ToolResult:
|
|
146
|
+
return self.call("report", session_id=session_id, out=out)
|
|
147
|
+
|
|
148
|
+
def import_artifact(self, session_id: str, tool: str, path: str, duration_s: Optional[int] = None) -> ToolResult:
|
|
149
|
+
"""Analyze output you captured yourself (e.g. `sudo strace …` or `sudo fs_usage …`)."""
|
|
150
|
+
return self.call("import_artifact", session_id=session_id, tool=tool, path=path, duration_s=duration_s)
|
|
151
|
+
|
|
152
|
+
# -- helpers ------------------------------------------------------------
|
|
153
|
+
|
|
154
|
+
@staticmethod
|
|
155
|
+
def findings(result: ToolResult) -> List[str]:
|
|
156
|
+
"""Human-readable finding lines with their evidence ids."""
|
|
157
|
+
return [f"[{f['severity']}] {f['summary']} ({f['confidence']}; {','.join(f['evidence_ids'])})" for f in result.get("findings", [])]
|
debuganything/_specs.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Shared argument schemas for framework adapters (mirrors src/contracts.ts ToolArgs)."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from typing import Any, Dict
|
|
5
|
+
|
|
6
|
+
SESSION = {"session_id": {"type": "string", "description": "session id from attach"}}
|
|
7
|
+
|
|
8
|
+
SPECS: Dict[str, Dict[str, Any]] = {
|
|
9
|
+
"readiness": {},
|
|
10
|
+
"attach": {
|
|
11
|
+
"target": {"type": "string", "description": "pid, container name, k8s pod (ns/name) or a command line"},
|
|
12
|
+
"kind": {"type": "string", "enum": ["pid", "container", "pod", "command"], "description": "default: auto-detect"},
|
|
13
|
+
},
|
|
14
|
+
"detach": {**SESSION},
|
|
15
|
+
"list_processes": {"filter": {"type": "string"}, "limit": {"type": "integer", "default": 100}},
|
|
16
|
+
"trace_syscalls": {**SESSION, "duration_s": {"type": "integer", "default": 10, "maximum": 60}, "categories": {"type": "array", "items": {"type": "string", "enum": ["network", "file", "process", "signal", "memory", "all"]}}},
|
|
17
|
+
"profile_cpu": {**SESSION, "duration_s": {"type": "integer", "default": 15, "maximum": 60}},
|
|
18
|
+
"dump_stacks": {**SESSION},
|
|
19
|
+
"inspect_sockets": {**SESSION},
|
|
20
|
+
"inspect_files": {**SESSION, "window_s": {"type": "integer", "default": 3}},
|
|
21
|
+
"capture_traffic": {**SESSION, "duration_s": {"type": "integer", "default": 10, "maximum": 30}, "filter": {"type": "string"}, "consent": {"type": "boolean", "description": "REQUIRED true; ask the user first"}},
|
|
22
|
+
"snapshot_memory": {**SESSION, "heap": {"type": "boolean", "default": True}, "window_s": {"type": "integer", "default": 3}},
|
|
23
|
+
"diff_runs": {**SESSION, "a": {"type": "string", "description": "earlier run id, e.g. snapshot_memory#2"}, "b": {"type": "string", "description": "later run id"}},
|
|
24
|
+
"container_info": {**SESSION},
|
|
25
|
+
"report": {**SESSION, "out": {"type": "string", "description": "optional path to also write the markdown to"}},
|
|
26
|
+
"import_artifact": {**SESSION, "tool": {"type": "string", "enum": ["strace", "fs_usage", "perf_script", "folded", "pyspy_dump", "sample", "gdb", "lldb", "lsof", "tcpdump"]}, "path": {"type": "string", "description": "raw output file you captured"}, "duration_s": {"type": "integer"}},
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
REQUIRED: Dict[str, list] = {
|
|
30
|
+
"import_artifact": ["session_id", "tool", "path"],
|
|
31
|
+
"attach": ["target"],
|
|
32
|
+
"diff_runs": ["session_id", "a", "b"],
|
|
33
|
+
"capture_traffic": ["session_id", "consent"],
|
|
34
|
+
}
|
|
35
|
+
for _name, _spec in SPECS.items():
|
|
36
|
+
if "session_id" in _spec and _name not in REQUIRED:
|
|
37
|
+
REQUIRED[_name] = ["session_id"]
|
|
38
|
+
REQUIRED.setdefault("readiness", [])
|
|
39
|
+
REQUIRED.setdefault("list_processes", [])
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def json_schema(tool: str) -> Dict[str, Any]:
|
|
43
|
+
return {"type": "object", "properties": SPECS[tool], "required": REQUIRED.get(tool, [])}
|
debuganything/crewai.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""CrewAI tools for DebugAnything.
|
|
2
|
+
|
|
3
|
+
from debuganything.crewai import dea_tools
|
|
4
|
+
agent = Agent(role="SRE", goal="diagnose the slow API with evidence", tools=dea_tools(), ...)
|
|
5
|
+
|
|
6
|
+
Requires ``pip install debuganything[crewai]``.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
from typing import Any, List, Optional, Type
|
|
12
|
+
|
|
13
|
+
from . import DebugAnything, TOOLS
|
|
14
|
+
from ._specs import SPECS, REQUIRED
|
|
15
|
+
|
|
16
|
+
try:
|
|
17
|
+
from crewai.tools import BaseTool
|
|
18
|
+
from pydantic import BaseModel, Field, create_model
|
|
19
|
+
except ImportError as e: # pragma: no cover
|
|
20
|
+
raise ImportError("pip install 'debuganything[crewai]'") from e
|
|
21
|
+
|
|
22
|
+
_PY = {"string": str, "integer": int, "boolean": bool, "array": List[str]}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _model(tool: str) -> Type[BaseModel]:
|
|
26
|
+
fields: dict = {}
|
|
27
|
+
for name, spec in SPECS[tool].items():
|
|
28
|
+
typ = _PY.get(spec.get("type", "string"), str)
|
|
29
|
+
required = name in REQUIRED.get(tool, [])
|
|
30
|
+
fields[name] = (typ if required else Optional[typ], Field(... if required else spec.get("default", None), description=spec.get("description", "")))
|
|
31
|
+
return create_model(f"Dea{tool.title().replace('_', '')}Args", **fields) # type: ignore[call-overload]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def dea_tools(client: Optional[DebugAnything] = None, include: Optional[List[str]] = None) -> List["BaseTool"]:
|
|
35
|
+
debuganything = client or DEA()
|
|
36
|
+
out: List[BaseTool] = []
|
|
37
|
+
for tool_name, desc in TOOLS.items():
|
|
38
|
+
if include and tool_name not in include:
|
|
39
|
+
continue
|
|
40
|
+
|
|
41
|
+
class _Tool(BaseTool):
|
|
42
|
+
name: str = f"dea_{tool_name}"
|
|
43
|
+
description: str = desc + " Findings cite evidence ids; never state a cause without one."
|
|
44
|
+
args_schema: Type[BaseModel] = _model(tool_name)
|
|
45
|
+
_da_tool: str = tool_name
|
|
46
|
+
|
|
47
|
+
def _run(self, **kwargs: Any) -> str:
|
|
48
|
+
return json.dumps(_client.call(self._da_tool, **kwargs))
|
|
49
|
+
|
|
50
|
+
out.append(_Tool())
|
|
51
|
+
return out
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""LangChain tools for DebugAnything.
|
|
2
|
+
|
|
3
|
+
from debuganything.langchain import dea_tools
|
|
4
|
+
tools = dea_tools() # 14 StructuredTools, one per Debug Anything tool
|
|
5
|
+
agent = create_react_agent(llm, tools)
|
|
6
|
+
|
|
7
|
+
Each tool returns the ToolResult as a JSON string. Requires ``pip install debuganything[langchain]``.
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
from typing import Any, List, Optional, Type
|
|
13
|
+
|
|
14
|
+
from . import DebugAnything, TOOLS
|
|
15
|
+
from ._specs import SPECS, REQUIRED
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
from langchain_core.tools import StructuredTool
|
|
19
|
+
from pydantic import BaseModel, Field, create_model
|
|
20
|
+
except ImportError as e: # pragma: no cover
|
|
21
|
+
raise ImportError("pip install 'debuganything[langchain]'") from e
|
|
22
|
+
|
|
23
|
+
_PY = {"string": str, "integer": int, "boolean": bool, "array": List[str]}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _model(tool: str) -> Type[BaseModel]:
|
|
27
|
+
fields: dict = {}
|
|
28
|
+
for name, spec in SPECS[tool].items():
|
|
29
|
+
typ = _PY.get(spec.get("type", "string"), str)
|
|
30
|
+
required = name in REQUIRED.get(tool, [])
|
|
31
|
+
default = ... if required else spec.get("default", None)
|
|
32
|
+
fields[name] = (typ if required else Optional[typ], Field(default, description=spec.get("description", "")))
|
|
33
|
+
return create_model(f"Dea{tool.title().replace('_', '')}Args", **fields) # type: ignore[call-overload]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def dea_tools(client: Optional[DebugAnything] = None, include: Optional[List[str]] = None) -> List["StructuredTool"]:
|
|
37
|
+
"""Build one LangChain StructuredTool per Debug Anything tool."""
|
|
38
|
+
debuganything = client or DEA()
|
|
39
|
+
out: List[StructuredTool] = []
|
|
40
|
+
for name, desc in TOOLS.items():
|
|
41
|
+
if include and name not in include:
|
|
42
|
+
continue
|
|
43
|
+
|
|
44
|
+
def _run(_name: str = name, **kwargs: Any) -> str:
|
|
45
|
+
return json.dumps(_client.call(_name, **kwargs))
|
|
46
|
+
|
|
47
|
+
out.append(StructuredTool.from_function(func=_run, name=f"dea_{name}", description=desc + " Findings cite evidence ids; never state a cause without one.", args_schema=_model(name)))
|
|
48
|
+
return out
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""LlamaIndex FunctionTools for DebugAnything.
|
|
2
|
+
|
|
3
|
+
from debuganything.llamaindex import dea_tools
|
|
4
|
+
agent = ReActAgent.from_tools(dea_tools(), llm=llm)
|
|
5
|
+
|
|
6
|
+
Requires ``pip install debuganything[llamaindex]``.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
from typing import Any, List, Optional
|
|
12
|
+
|
|
13
|
+
from . import DebugAnything, TOOLS
|
|
14
|
+
|
|
15
|
+
try:
|
|
16
|
+
from llama_index.core.tools import FunctionTool
|
|
17
|
+
except ImportError as e: # pragma: no cover
|
|
18
|
+
raise ImportError("pip install 'debuganything[llamaindex]'") from e
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def dea_tools(client: Optional[DebugAnything] = None, include: Optional[List[str]] = None) -> List["FunctionTool"]:
|
|
22
|
+
debuganything = client or DEA()
|
|
23
|
+
out: List[FunctionTool] = []
|
|
24
|
+
for name, desc in TOOLS.items():
|
|
25
|
+
if include and name not in include:
|
|
26
|
+
continue
|
|
27
|
+
|
|
28
|
+
def _run(_name: str = name, **kwargs: Any) -> str:
|
|
29
|
+
"""Run a Debug Anything tool. Pass arguments as keyword arguments (session_id, duration_s, ...)."""
|
|
30
|
+
return json.dumps(_client.call(_name, **kwargs))
|
|
31
|
+
|
|
32
|
+
out.append(FunctionTool.from_defaults(fn=_run, name=f"dea_{name}", description=desc + " Findings cite evidence ids."))
|
|
33
|
+
return out
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: debuganything
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Debug Anything from Python agents: observe running processes (strace, py-spy, perf, lsof, tcpdump, gdb/lldb, heap snapshots) and get evidence-backed findings. Wraps the Debug Anything CLI/MCP server; tool adapters for LangChain, LlamaIndex and CrewAI.
|
|
5
|
+
Project-URL: Homepage, https://github.com/shreyassaw5394/debuganything
|
|
6
|
+
Project-URL: Documentation, https://github.com/shreyassaw5394/debuganything/tree/main/docs
|
|
7
|
+
Project-URL: Issues, https://github.com/shreyassaw5394/debuganything/issues
|
|
8
|
+
Author: Shreyas Sawant
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: agents,crewai,debugging,langchain,llamaindex,mcp,observability,perf,profiling,py-spy,strace
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Topic :: Software Development :: Debuggers
|
|
15
|
+
Classifier: Topic :: System :: Monitoring
|
|
16
|
+
Requires-Python: >=3.9
|
|
17
|
+
Provides-Extra: crewai
|
|
18
|
+
Requires-Dist: crewai>=0.30; extra == 'crewai'
|
|
19
|
+
Requires-Dist: pydantic>=2; extra == 'crewai'
|
|
20
|
+
Provides-Extra: dev
|
|
21
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
22
|
+
Provides-Extra: langchain
|
|
23
|
+
Requires-Dist: langchain-core>=0.2; extra == 'langchain'
|
|
24
|
+
Requires-Dist: pydantic>=2; extra == 'langchain'
|
|
25
|
+
Provides-Extra: llamaindex
|
|
26
|
+
Requires-Dist: llama-index-core>=0.10; extra == 'llamaindex'
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
<p align="center"><a href="https://debuganything.dev"><img src="https://raw.githubusercontent.com/shreyassaw5394/debuganything/main/assets/debuganything-logo.png" alt="Debug Anything" width="560"></a></p>
|
|
30
|
+
|
|
31
|
+
# debuganything (Python)
|
|
32
|
+
|
|
33
|
+
**Debug Anything from Python.** Observe a running process, container or pod —
|
|
34
|
+
syscalls, CPU profiles, thread stacks, sockets, packets, heap retainers — and
|
|
35
|
+
get findings that cite evidence. This package wraps the
|
|
36
|
+
[DebugAnything](https://github.com/shreyassaw5394/debuganything) CLI (Node ≥ 22 required:
|
|
37
|
+
`npx -y debuganything` is used automatically) and ships tool adapters for
|
|
38
|
+
LangChain, LlamaIndex and CrewAI.
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install debuganything # core (no deps)
|
|
42
|
+
pip install 'debuganything[langchain]' # + LangChain StructuredTools
|
|
43
|
+
pip install 'debuganything[llamaindex]' # + LlamaIndex FunctionTools
|
|
44
|
+
pip install 'debuganything[crewai]' # + CrewAI BaseTools
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Plain Python
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from debuganything import DebugAnything
|
|
51
|
+
|
|
52
|
+
debuganything = DEA() # uses `debuganything` if installed, else `npx -y debuganything@latest`
|
|
53
|
+
print(da.readiness()["findings"])
|
|
54
|
+
s = da.attach(12345) # pid, "my-container", "ns/pod", or a command
|
|
55
|
+
before = da.snapshot_memory(s)
|
|
56
|
+
# ... exercise the target ...
|
|
57
|
+
after = da.snapshot_memory(s)
|
|
58
|
+
diff = da.diff_runs(s, before["run_id"], after["run_id"])
|
|
59
|
+
for line in DebugAnything.findings(diff):
|
|
60
|
+
print(line) # [high] heap_retainer: name=Array grew 2.0 KB → 50.1 MB (observed; e4,e1)
|
|
61
|
+
da.report(s, out="report.md")
|
|
62
|
+
da.detach(s)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Every call returns the Debug Anything `ToolResult` dict (`ok`, `findings`, `evidence`,
|
|
66
|
+
`limitations`, `suggested_next`, `artifacts`). `capture_traffic` needs
|
|
67
|
+
`consent=True` — ask your user first.
|
|
68
|
+
|
|
69
|
+
## LangChain
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
from debuganything.langchain import dea_tools
|
|
73
|
+
from langgraph.prebuilt import create_react_agent
|
|
74
|
+
|
|
75
|
+
agent = create_react_agent(llm, dea_tools())
|
|
76
|
+
agent.invoke({"messages": [("user", "pid 12345 is slow — attach and tell me why, with evidence")]})
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## LlamaIndex
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
from debuganything.llamaindex import dea_tools
|
|
83
|
+
from llama_index.core.agent import ReActAgent
|
|
84
|
+
|
|
85
|
+
agent = ReActAgent.from_tools(dea_tools(), llm=llm, verbose=True)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## CrewAI
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
from debuganything.crewai import dea_tools
|
|
92
|
+
from crewai import Agent
|
|
93
|
+
|
|
94
|
+
sre = Agent(role="SRE", goal="Diagnose the hung worker with evidence, never guess", tools=dea_tools(), llm=llm)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Or just use MCP
|
|
98
|
+
|
|
99
|
+
Any framework with MCP support (LangChain `langchain-mcp-adapters`, LlamaIndex
|
|
100
|
+
`McpToolSpec`, CrewAI `MCPServerAdapter`, Claude Code, Codex, Cursor…) can run
|
|
101
|
+
Debug Anything directly: `{"command": "npx", "args": ["-y", "debuganything@latest", "serve"]}`.
|
|
102
|
+
Same 14 tools, same ToolResult, plus the guided prompts.
|
|
103
|
+
|
|
104
|
+
## Safety
|
|
105
|
+
|
|
106
|
+
Debug Anything is read-only, never runs sudo, caps every tool at 60 s, redacts secrets in
|
|
107
|
+
what it returns, and keeps raw artifacts under `~/.debuganything/sessions/`. See
|
|
108
|
+
[docs/safety.md](https://github.com/shreyassaw5394/debuganything/blob/main/docs/safety.md).
|
|
109
|
+
|
|
110
|
+
## License and disclaimer
|
|
111
|
+
|
|
112
|
+
MIT. Provided "as is", without warranty of any kind; the authors accept no liability for any damage arising from use. Only observe systems you own or are explicitly authorized to debug — you are solely responsible for authorization and legal compliance. Findings are automated diagnostics, not professional advice. See the full [DISCLAIMER](https://github.com/shreyassaw5394/debuganything/blob/main/DISCLAIMER.md).
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
debuganything/__init__.py,sha256=b2GKdTl1BUYji2EL8TJ4gHRRflfhcfJwgjm2oC_1Rhk,8583
|
|
2
|
+
debuganything/_specs.py,sha256=YPUuXDSmXxEJV_SHgTXZ0ZlGeDQwI1LZNwcnWC1PR2w,2675
|
|
3
|
+
debuganything/crewai.py,sha256=UJA8aSncxYvXSt1CkIKMv70L0sSZl53EB9Ch2-uJHD4,1901
|
|
4
|
+
debuganything/langchain.py,sha256=PVBxQjPl6U9MyeqSZwAgn6G9POZ8Aj5VKhyDq7ZFbzI,1994
|
|
5
|
+
debuganything/llamaindex.py,sha256=buvST_cEoNvgXyFbPBCRKDiDA610O7nhpo2XDPWXNcE,1175
|
|
6
|
+
debuganything-1.0.0.dist-info/METADATA,sha256=Gh7kRWbsw1JLx3D9sztK-Ce_pvFKVYNc4GY37-cwOUM,4877
|
|
7
|
+
debuganything-1.0.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
8
|
+
debuganything-1.0.0.dist-info/licenses/LICENSE,sha256=V9bU4now33bic6AzH8t3Xn0qMirFwm_2n_dwAPnYzh8,1092
|
|
9
|
+
debuganything-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Shreyas Sawant and DEA contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|