hypha-debugger 0.1.9__tar.gz → 0.1.10__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.
Files changed (22) hide show
  1. {hypha_debugger-0.1.9 → hypha_debugger-0.1.10}/PKG-INFO +1 -1
  2. {hypha_debugger-0.1.9 → hypha_debugger-0.1.10}/hypha_debugger/__init__.py +1 -1
  3. {hypha_debugger-0.1.9 → hypha_debugger-0.1.10}/hypha_debugger/services/filesystem.py +22 -34
  4. {hypha_debugger-0.1.9 → hypha_debugger-0.1.10}/hypha_debugger/services/source.py +4 -4
  5. {hypha_debugger-0.1.9 → hypha_debugger-0.1.10}/hypha_debugger.egg-info/PKG-INFO +1 -1
  6. {hypha_debugger-0.1.9 → hypha_debugger-0.1.10}/pyproject.toml +1 -1
  7. {hypha_debugger-0.1.9 → hypha_debugger-0.1.10}/tests/test_services.py +28 -8
  8. {hypha_debugger-0.1.9 → hypha_debugger-0.1.10}/README.md +0 -0
  9. {hypha_debugger-0.1.9 → hypha_debugger-0.1.10}/hypha_debugger/__main__.py +0 -0
  10. {hypha_debugger-0.1.9 → hypha_debugger-0.1.10}/hypha_debugger/debugger.py +0 -0
  11. {hypha_debugger-0.1.9 → hypha_debugger-0.1.10}/hypha_debugger/services/__init__.py +0 -0
  12. {hypha_debugger-0.1.9 → hypha_debugger-0.1.10}/hypha_debugger/services/execute.py +0 -0
  13. {hypha_debugger-0.1.9 → hypha_debugger-0.1.10}/hypha_debugger/services/info.py +0 -0
  14. {hypha_debugger-0.1.9 → hypha_debugger-0.1.10}/hypha_debugger/services/inspect_vars.py +0 -0
  15. {hypha_debugger-0.1.9 → hypha_debugger-0.1.10}/hypha_debugger/utils/__init__.py +0 -0
  16. {hypha_debugger-0.1.9 → hypha_debugger-0.1.10}/hypha_debugger/utils/env.py +0 -0
  17. {hypha_debugger-0.1.9 → hypha_debugger-0.1.10}/hypha_debugger.egg-info/SOURCES.txt +0 -0
  18. {hypha_debugger-0.1.9 → hypha_debugger-0.1.10}/hypha_debugger.egg-info/dependency_links.txt +0 -0
  19. {hypha_debugger-0.1.9 → hypha_debugger-0.1.10}/hypha_debugger.egg-info/entry_points.txt +0 -0
  20. {hypha_debugger-0.1.9 → hypha_debugger-0.1.10}/hypha_debugger.egg-info/requires.txt +0 -0
  21. {hypha_debugger-0.1.9 → hypha_debugger-0.1.10}/hypha_debugger.egg-info/top_level.txt +0 -0
  22. {hypha_debugger-0.1.9 → hypha_debugger-0.1.10}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hypha-debugger
3
- Version: 0.1.9
3
+ Version: 0.1.10
4
4
  Summary: Injectable debugger for Python processes and AI agents, powered by Hypha RPC
5
5
  Author: Amun AI AB
6
6
  License: MIT
@@ -12,5 +12,5 @@ Usage (sync):
12
12
 
13
13
  from hypha_debugger.debugger import start_debugger, start_debugger_sync, DebugSession
14
14
 
15
- __version__ = "0.1.9"
15
+ __version__ = "0.1.10"
16
16
  __all__ = ["start_debugger", "start_debugger_sync", "DebugSession", "__version__"]
@@ -1,4 +1,4 @@
1
- """File system browsing and writing service (sandboxed to CWD)."""
1
+ """File system browsing and writing service."""
2
2
 
3
3
  import os
4
4
 
@@ -10,19 +10,19 @@ try:
10
10
  def list_files(
11
11
  path: str = Field(
12
12
  default=".",
13
- description='Directory path relative to CWD. Default: "." (current directory).',
13
+ description='Directory path (absolute or relative to CWD). Default: ".".',
14
14
  ),
15
15
  pattern: str = Field(
16
16
  default="",
17
17
  description='Glob pattern to filter files, e.g. "*.py".',
18
18
  ),
19
19
  ) -> dict:
20
- """List files and directories in a path relative to the process CWD."""
20
+ """List files and directories at the given path."""
21
21
  return _list_files_impl(path, pattern)
22
22
 
23
23
  @schema_function
24
24
  def read_file(
25
- path: str = Field(..., description="File path relative to CWD."),
25
+ path: str = Field(..., description="File path (absolute or relative to CWD)."),
26
26
  max_lines: int = Field(
27
27
  default=500,
28
28
  description="Maximum number of lines to read. Default: 500.",
@@ -36,7 +36,7 @@ try:
36
36
  description="File encoding. Default: utf-8.",
37
37
  ),
38
38
  ) -> dict:
39
- """Read a file relative to the process CWD. Returns the content as a string.
39
+ """Read a file. Returns the content as a string.
40
40
 
41
41
  Use offset and max_lines to paginate through large files.
42
42
  """
@@ -44,7 +44,7 @@ try:
44
44
 
45
45
  @schema_function
46
46
  def write_file(
47
- path: str = Field(..., description="File path relative to CWD."),
47
+ path: str = Field(..., description="File path (absolute or relative to CWD)."),
48
48
  content: str = Field(..., description="Content to write to the file."),
49
49
  mode: str = Field(
50
50
  default="w",
@@ -59,47 +59,39 @@ try:
59
59
  description="File encoding. Default: utf-8.",
60
60
  ),
61
61
  ) -> dict:
62
- """Write content to a file relative to the process CWD.
62
+ """Write content to a file.
63
63
 
64
64
  Creates parent directories automatically. Use mode="a" to append.
65
- Sandboxed: cannot write outside the process CWD.
66
65
  """
67
66
  return _write_file_impl(path, content, mode, create_dirs, encoding)
68
67
 
69
68
  except ImportError:
70
69
 
71
70
  def list_files(path: str = ".", pattern: str = "") -> dict:
72
- """List files and directories in a path relative to CWD."""
71
+ """List files and directories at the given path."""
73
72
  return _list_files_impl(path, pattern)
74
73
 
75
74
  def read_file(
76
75
  path: str = "", max_lines: int = 500, offset: int = 0, encoding: str = "utf-8"
77
76
  ) -> dict:
78
- """Read a file relative to CWD."""
77
+ """Read a file."""
79
78
  return _read_file_impl(path, max_lines, offset, encoding)
80
79
 
81
80
  def write_file(
82
81
  path: str = "", content: str = "", mode: str = "w",
83
82
  create_dirs: bool = True, encoding: str = "utf-8"
84
83
  ) -> dict:
85
- """Write content to a file relative to CWD."""
84
+ """Write content to a file."""
86
85
  return _write_file_impl(path, content, mode, create_dirs, encoding)
87
86
 
88
87
 
89
- def _resolve_safe(path: str) -> str:
90
- """Resolve path and ensure it's within CWD."""
91
- cwd = os.path.realpath(os.getcwd())
92
- resolved = os.path.realpath(os.path.join(cwd, path))
93
- if not resolved.startswith(cwd):
94
- raise PermissionError(f"Access denied: path escapes CWD ({path})")
95
- return resolved
88
+ def _resolve(path: str) -> str:
89
+ """Resolve path to absolute."""
90
+ return os.path.realpath(os.path.expanduser(path))
96
91
 
97
92
 
98
93
  def _list_files_impl(path: str, pattern: str) -> dict:
99
- try:
100
- resolved = _resolve_safe(path)
101
- except PermissionError as e:
102
- return {"error": str(e)}
94
+ resolved = _resolve(path)
103
95
 
104
96
  if not os.path.isdir(resolved):
105
97
  return {"error": f"Not a directory: {path}"}
@@ -123,7 +115,7 @@ def _list_files_impl(path: str, pattern: str) -> dict:
123
115
  return {"error": f"Permission denied: {path}"}
124
116
 
125
117
  return {
126
- "path": os.path.relpath(resolved, os.getcwd()),
118
+ "path": resolved,
127
119
  "entries": entries[:500],
128
120
  "total": len(entries),
129
121
  }
@@ -144,10 +136,7 @@ def _entry_info(name: str, full_path: str) -> dict:
144
136
 
145
137
 
146
138
  def _read_file_impl(path: str, max_lines: int, offset: int, encoding: str) -> dict:
147
- try:
148
- resolved = _resolve_safe(path)
149
- except PermissionError as e:
150
- return {"error": str(e)}
139
+ resolved = _resolve(path)
151
140
 
152
141
  if not os.path.isfile(resolved):
153
142
  return {"error": f"Not a file: {path}"}
@@ -163,7 +152,7 @@ def _read_file_impl(path: str, max_lines: int, offset: int, encoding: str) -> di
163
152
  lines.append(line)
164
153
  content = "".join(lines)
165
154
  return {
166
- "path": os.path.relpath(resolved, os.getcwd()),
155
+ "path": resolved,
167
156
  "content": content,
168
157
  "lines_read": len(lines),
169
158
  "offset": offset,
@@ -179,20 +168,19 @@ def _write_file_impl(
179
168
  if mode not in ("w", "a"):
180
169
  return {"error": f'Invalid mode: {mode}. Use "w" or "a".'}
181
170
 
182
- try:
183
- resolved = _resolve_safe(path)
184
- except PermissionError as e:
185
- return {"error": str(e)}
171
+ resolved = _resolve(path)
186
172
 
187
173
  try:
188
174
  if create_dirs:
189
- os.makedirs(os.path.dirname(resolved), exist_ok=True)
175
+ parent = os.path.dirname(resolved)
176
+ if parent:
177
+ os.makedirs(parent, exist_ok=True)
190
178
 
191
179
  with open(resolved, mode, encoding=encoding) as f:
192
180
  f.write(content)
193
181
 
194
182
  return {
195
- "path": os.path.relpath(resolved, os.getcwd()),
183
+ "path": resolved,
196
184
  "bytes_written": len(content.encode(encoding)),
197
185
  "mode": mode,
198
186
  }
@@ -133,17 +133,17 @@ PID, CWD, Python version, hostname, platform, memory usage, CPU count.
133
133
  - **Example**: `curl "$SERVICE_URL/get_process_info"`
134
134
 
135
135
  ### list_files(path?, pattern?)
136
- List directory contents (sandboxed to CWD).
136
+ List directory contents. Accepts absolute or relative paths.
137
137
  - **path** (str, default `"."`), **pattern** (str): glob filter e.g. `"*.py"`.
138
138
  - **Returns**: `{path, entries: [{name, type, size?}], total}`
139
139
 
140
140
  ### read_file(path, max_lines?, offset?, encoding?)
141
- Read a file (sandboxed to CWD).
141
+ Read a file. Accepts absolute or relative paths.
142
142
  - **path** (str, required), **max_lines** (int, default `500`), **offset** (int, default `0`).
143
143
  - **Returns**: `{path, content, lines_read, offset, truncated}`
144
144
 
145
145
  ### write_file(path, content, mode?, create_dirs?, encoding?)
146
- Write/append to a file (sandboxed to CWD). Auto-creates parent dirs.
146
+ Write/append to a file. Auto-creates parent dirs. Accepts absolute or relative paths.
147
147
  - **path** (str), **content** (str), **mode** (`"w"` or `"a"`).
148
148
  - **Returns**: `{path, bytes_written, mode}`
149
149
 
@@ -169,7 +169,7 @@ Returns this document.
169
169
  ## Tips
170
170
  - **Use `execute_code` for file writes** when content has special characters —
171
171
  it avoids JSON/curl escaping issues that affect `write_file` via HTTP.
172
- - All file operations (list/read/write) are sandboxed to the process CWD.
172
+ - File operations accept absolute paths or paths relative to the process CWD.
173
173
  - POST endpoints accept JSON body. GET endpoints take no body.
174
174
  - Code execution has a 30s default timeout to prevent hangs.
175
175
  - The REPL namespace is independent from `__main__` by default.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hypha-debugger
3
- Version: 0.1.9
3
+ Version: 0.1.10
4
4
  Summary: Injectable debugger for Python processes and AI agents, powered by Hypha RPC
5
5
  Author: Amun AI AB
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "hypha-debugger"
7
- version = "0.1.9"
7
+ version = "0.1.10"
8
8
  description = "Injectable debugger for Python processes and AI agents, powered by Hypha RPC"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -185,10 +185,13 @@ def test_read_file_with_offset():
185
185
  assert result["lines_read"] <= 2
186
186
 
187
187
 
188
- def test_read_file_sandbox():
189
- result = read_file("../../etc/passwd")
190
- assert "error" in result
191
- assert "Access denied" in result["error"] or "Not a file" in result["error"]
188
+ def test_read_file_absolute():
189
+ """Can read files by absolute path."""
190
+ import pathlib
191
+ toml_path = str(pathlib.Path("pyproject.toml").resolve())
192
+ result = read_file(toml_path)
193
+ assert result.get("error") is None
194
+ assert "hypha-debugger" in result["content"]
192
195
 
193
196
 
194
197
  def test_write_file():
@@ -234,10 +237,27 @@ def test_write_file_create_dirs():
234
237
  os.chdir(old_cwd)
235
238
 
236
239
 
237
- def test_write_file_sandbox():
238
- result = write_file("../../etc/evil.txt", "pwned")
239
- assert "error" in result
240
- assert "Access denied" in result["error"]
240
+ def test_write_file_absolute():
241
+ """Can write files by absolute path."""
242
+ with tempfile.TemporaryDirectory() as tmpdir:
243
+ abs_path = os.path.join(tmpdir, "abs_test.txt")
244
+ result = write_file(abs_path, "absolute write")
245
+ assert result.get("error") is None
246
+ assert result["bytes_written"] == 14
247
+ read_result = read_file(abs_path)
248
+ assert read_result["content"] == "absolute write"
249
+
250
+
251
+ def test_list_files_absolute():
252
+ """Can list files by absolute path."""
253
+ with tempfile.TemporaryDirectory() as tmpdir:
254
+ # Create a test file
255
+ with open(os.path.join(tmpdir, "hello.txt"), "w") as f:
256
+ f.write("hi")
257
+ result = list_files(tmpdir)
258
+ assert result.get("error") is None
259
+ names = [e["name"] for e in result["entries"]]
260
+ assert "hello.txt" in names
241
261
 
242
262
 
243
263
  def test_write_file_invalid_mode():