debuganything 1.0.0__tar.gz

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.
@@ -0,0 +1,21 @@
1
+ node_modules/
2
+ dist/
3
+ coverage/
4
+ *.log
5
+ .DS_Store
6
+ examples/**/node_modules/
7
+ examples/**/__pycache__/
8
+ *.pcap
9
+ *.heapsnapshot
10
+ .vhs-out/
11
+ .lab-dea/
12
+ .lab-dea/
13
+ python/dist/
14
+ python/**/__pycache__/
15
+ python/*.egg-info/
16
+ _site/
17
+ .vercel
18
+ .env*
19
+
20
+ # fixtures are real captured tool output; never ignore them
21
+ !tests/fixtures/**
@@ -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.
@@ -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,84 @@
1
+ <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>
2
+
3
+ # debuganything (Python)
4
+
5
+ **Debug Anything from Python.** Observe a running process, container or pod —
6
+ syscalls, CPU profiles, thread stacks, sockets, packets, heap retainers — and
7
+ get findings that cite evidence. This package wraps the
8
+ [DebugAnything](https://github.com/shreyassaw5394/debuganything) CLI (Node ≥ 22 required:
9
+ `npx -y debuganything` is used automatically) and ships tool adapters for
10
+ LangChain, LlamaIndex and CrewAI.
11
+
12
+ ```bash
13
+ pip install debuganything # core (no deps)
14
+ pip install 'debuganything[langchain]' # + LangChain StructuredTools
15
+ pip install 'debuganything[llamaindex]' # + LlamaIndex FunctionTools
16
+ pip install 'debuganything[crewai]' # + CrewAI BaseTools
17
+ ```
18
+
19
+ ## Plain Python
20
+
21
+ ```python
22
+ from debuganything import DebugAnything
23
+
24
+ debuganything = DEA() # uses `debuganything` if installed, else `npx -y debuganything@latest`
25
+ print(da.readiness()["findings"])
26
+ s = da.attach(12345) # pid, "my-container", "ns/pod", or a command
27
+ before = da.snapshot_memory(s)
28
+ # ... exercise the target ...
29
+ after = da.snapshot_memory(s)
30
+ diff = da.diff_runs(s, before["run_id"], after["run_id"])
31
+ for line in DebugAnything.findings(diff):
32
+ print(line) # [high] heap_retainer: name=Array grew 2.0 KB → 50.1 MB (observed; e4,e1)
33
+ da.report(s, out="report.md")
34
+ da.detach(s)
35
+ ```
36
+
37
+ Every call returns the Debug Anything `ToolResult` dict (`ok`, `findings`, `evidence`,
38
+ `limitations`, `suggested_next`, `artifacts`). `capture_traffic` needs
39
+ `consent=True` — ask your user first.
40
+
41
+ ## LangChain
42
+
43
+ ```python
44
+ from debuganything.langchain import dea_tools
45
+ from langgraph.prebuilt import create_react_agent
46
+
47
+ agent = create_react_agent(llm, dea_tools())
48
+ agent.invoke({"messages": [("user", "pid 12345 is slow — attach and tell me why, with evidence")]})
49
+ ```
50
+
51
+ ## LlamaIndex
52
+
53
+ ```python
54
+ from debuganything.llamaindex import dea_tools
55
+ from llama_index.core.agent import ReActAgent
56
+
57
+ agent = ReActAgent.from_tools(dea_tools(), llm=llm, verbose=True)
58
+ ```
59
+
60
+ ## CrewAI
61
+
62
+ ```python
63
+ from debuganything.crewai import dea_tools
64
+ from crewai import Agent
65
+
66
+ sre = Agent(role="SRE", goal="Diagnose the hung worker with evidence, never guess", tools=dea_tools(), llm=llm)
67
+ ```
68
+
69
+ ## Or just use MCP
70
+
71
+ Any framework with MCP support (LangChain `langchain-mcp-adapters`, LlamaIndex
72
+ `McpToolSpec`, CrewAI `MCPServerAdapter`, Claude Code, Codex, Cursor…) can run
73
+ Debug Anything directly: `{"command": "npx", "args": ["-y", "debuganything@latest", "serve"]}`.
74
+ Same 14 tools, same ToolResult, plus the guided prompts.
75
+
76
+ ## Safety
77
+
78
+ Debug Anything is read-only, never runs sudo, caps every tool at 60 s, redacts secrets in
79
+ what it returns, and keeps raw artifacts under `~/.debuganything/sessions/`. See
80
+ [docs/safety.md](https://github.com/shreyassaw5394/debuganything/blob/main/docs/safety.md).
81
+
82
+ ## License and disclaimer
83
+
84
+ 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,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", [])]
@@ -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, [])}
@@ -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,34 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "debuganything"
7
+ version = "1.0.0"
8
+ description = "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."
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.9"
12
+ authors = [{ name = "Shreyas Sawant" }]
13
+ keywords = ["debugging", "observability", "profiling", "agents", "mcp", "langchain", "llamaindex", "crewai", "strace", "py-spy", "perf"]
14
+ classifiers = [
15
+ "Programming Language :: Python :: 3",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Topic :: Software Development :: Debuggers",
18
+ "Topic :: System :: Monitoring",
19
+ ]
20
+ dependencies = []
21
+
22
+ [project.optional-dependencies]
23
+ langchain = ["langchain-core>=0.2", "pydantic>=2"]
24
+ llamaindex = ["llama-index-core>=0.10"]
25
+ crewai = ["crewai>=0.30", "pydantic>=2"]
26
+ dev = ["pytest"]
27
+
28
+ [project.urls]
29
+ Homepage = "https://github.com/shreyassaw5394/debuganything"
30
+ Documentation = "https://github.com/shreyassaw5394/debuganything/tree/main/docs"
31
+ Issues = "https://github.com/shreyassaw5394/debuganything/issues"
32
+
33
+ [tool.hatch.build.targets.wheel]
34
+ packages = ["debuganything"]
@@ -0,0 +1,49 @@
1
+ import json
2
+ import subprocess
3
+ import sys
4
+ from unittest import mock
5
+
6
+ import pytest
7
+
8
+ from debuganything import DebugAnything, DebugAnythingError, TOOLS
9
+ from debuganything._specs import SPECS, REQUIRED, json_schema
10
+
11
+
12
+ def test_specs_cover_every_tool():
13
+ assert set(SPECS) == set(TOOLS)
14
+ for t in TOOLS:
15
+ assert json_schema(t)["type"] == "object"
16
+ assert REQUIRED["capture_traffic"] == ["session_id", "consent"]
17
+
18
+
19
+ def test_call_builds_json_args_and_parses_result():
20
+ fake = subprocess.CompletedProcess(args=[], returncode=0, stdout=json.dumps({"ok": True, "tool": "readiness", "findings": [], "evidence": []}), stderr="")
21
+ with mock.patch("subprocess.run", return_value=fake) as run:
22
+ r = DebugAnything(command=["debuganything"]).call("trace_syscalls", session_id="s", duration_s=5, categories=None)
23
+ assert r["ok"] is True
24
+ argv = run.call_args[0][0]
25
+ assert argv[:3] == ["debuganything", "call", "trace_syscalls"]
26
+ assert json.loads(argv[3]) == {"session_id": "s", "duration_s": 5}
27
+
28
+
29
+ def test_unknown_tool_rejected():
30
+ with pytest.raises(DebugAnythingError):
31
+ DebugAnything(command=["debuganything"]).call("rm_rf")
32
+
33
+
34
+ def test_attach_returns_session_id():
35
+ fake = subprocess.CompletedProcess(args=[], returncode=0, stdout=json.dumps({"ok": True, "session_id": "abc"}), stderr="")
36
+ with mock.patch("subprocess.run", return_value=fake):
37
+ assert DebugAnything(command=["debuganything"]).attach(123) == "abc"
38
+
39
+
40
+ def test_missing_node_gives_helpful_error():
41
+ with mock.patch("subprocess.run", side_effect=FileNotFoundError()):
42
+ with pytest.raises(DebugAnythingError, match="Node.js"):
43
+ DebugAnything(command=["definitely-not-here"]).readiness()
44
+
45
+
46
+ @pytest.mark.skipif(sys.platform == "win32", reason="not supported")
47
+ def test_findings_formatter():
48
+ r = {"findings": [{"severity": "high", "summary": "x", "confidence": "observed", "evidence_ids": ["e1"]}]}
49
+ assert DebugAnything.findings(r) == ["[high] x (observed; e1)"]