hypha-debugger 0.1.9__tar.gz → 0.1.11__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.
- {hypha_debugger-0.1.9 → hypha_debugger-0.1.11}/PKG-INFO +1 -1
- {hypha_debugger-0.1.9 → hypha_debugger-0.1.11}/hypha_debugger/__init__.py +1 -1
- {hypha_debugger-0.1.9 → hypha_debugger-0.1.11}/hypha_debugger/__main__.py +5 -2
- {hypha_debugger-0.1.9 → hypha_debugger-0.1.11}/hypha_debugger/services/filesystem.py +22 -34
- {hypha_debugger-0.1.9 → hypha_debugger-0.1.11}/hypha_debugger/services/source.py +4 -4
- {hypha_debugger-0.1.9 → hypha_debugger-0.1.11}/hypha_debugger.egg-info/PKG-INFO +1 -1
- {hypha_debugger-0.1.9 → hypha_debugger-0.1.11}/pyproject.toml +1 -1
- {hypha_debugger-0.1.9 → hypha_debugger-0.1.11}/tests/test_services.py +28 -8
- {hypha_debugger-0.1.9 → hypha_debugger-0.1.11}/README.md +0 -0
- {hypha_debugger-0.1.9 → hypha_debugger-0.1.11}/hypha_debugger/debugger.py +0 -0
- {hypha_debugger-0.1.9 → hypha_debugger-0.1.11}/hypha_debugger/services/__init__.py +0 -0
- {hypha_debugger-0.1.9 → hypha_debugger-0.1.11}/hypha_debugger/services/execute.py +0 -0
- {hypha_debugger-0.1.9 → hypha_debugger-0.1.11}/hypha_debugger/services/info.py +0 -0
- {hypha_debugger-0.1.9 → hypha_debugger-0.1.11}/hypha_debugger/services/inspect_vars.py +0 -0
- {hypha_debugger-0.1.9 → hypha_debugger-0.1.11}/hypha_debugger/utils/__init__.py +0 -0
- {hypha_debugger-0.1.9 → hypha_debugger-0.1.11}/hypha_debugger/utils/env.py +0 -0
- {hypha_debugger-0.1.9 → hypha_debugger-0.1.11}/hypha_debugger.egg-info/SOURCES.txt +0 -0
- {hypha_debugger-0.1.9 → hypha_debugger-0.1.11}/hypha_debugger.egg-info/dependency_links.txt +0 -0
- {hypha_debugger-0.1.9 → hypha_debugger-0.1.11}/hypha_debugger.egg-info/entry_points.txt +0 -0
- {hypha_debugger-0.1.9 → hypha_debugger-0.1.11}/hypha_debugger.egg-info/requires.txt +0 -0
- {hypha_debugger-0.1.9 → hypha_debugger-0.1.11}/hypha_debugger.egg-info/top_level.txt +0 -0
- {hypha_debugger-0.1.9 → hypha_debugger-0.1.11}/setup.cfg +0 -0
|
@@ -69,8 +69,11 @@ def main():
|
|
|
69
69
|
stop.set()
|
|
70
70
|
|
|
71
71
|
loop = asyncio.get_running_loop()
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
try:
|
|
73
|
+
for sig in (signal.SIGINT, signal.SIGTERM):
|
|
74
|
+
loop.add_signal_handler(sig, _signal_handler)
|
|
75
|
+
except NotImplementedError:
|
|
76
|
+
pass
|
|
74
77
|
|
|
75
78
|
await stop.wait()
|
|
76
79
|
print("\n[hypha-debugger] Shutting down...")
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""File system browsing and writing service
|
|
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: "."
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
84
|
+
"""Write content to a file."""
|
|
86
85
|
return _write_file_impl(path, content, mode, create_dirs, encoding)
|
|
87
86
|
|
|
88
87
|
|
|
89
|
-
def
|
|
90
|
-
"""Resolve path
|
|
91
|
-
|
|
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
|
-
|
|
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":
|
|
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
|
-
|
|
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":
|
|
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
|
-
|
|
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.
|
|
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":
|
|
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
|
|
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
|
|
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
|
|
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
|
-
-
|
|
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.
|
|
@@ -185,10 +185,13 @@ def test_read_file_with_offset():
|
|
|
185
185
|
assert result["lines_read"] <= 2
|
|
186
186
|
|
|
187
187
|
|
|
188
|
-
def
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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():
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|