hypha-debugger 0.1.0__tar.gz → 0.1.3__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.0 → hypha_debugger-0.1.3}/PKG-INFO +32 -9
- {hypha_debugger-0.1.0 → hypha_debugger-0.1.3}/README.md +31 -8
- {hypha_debugger-0.1.0 → hypha_debugger-0.1.3}/hypha_debugger/__init__.py +1 -1
- hypha_debugger-0.1.3/hypha_debugger/__main__.py +86 -0
- {hypha_debugger-0.1.0 → hypha_debugger-0.1.3}/hypha_debugger/debugger.py +105 -23
- {hypha_debugger-0.1.0 → hypha_debugger-0.1.3}/hypha_debugger.egg-info/PKG-INFO +32 -9
- {hypha_debugger-0.1.0 → hypha_debugger-0.1.3}/hypha_debugger.egg-info/SOURCES.txt +2 -0
- hypha_debugger-0.1.3/hypha_debugger.egg-info/entry_points.txt +2 -0
- {hypha_debugger-0.1.0 → hypha_debugger-0.1.3}/pyproject.toml +4 -1
- {hypha_debugger-0.1.0 → hypha_debugger-0.1.3}/hypha_debugger/services/__init__.py +0 -0
- {hypha_debugger-0.1.0 → hypha_debugger-0.1.3}/hypha_debugger/services/execute.py +0 -0
- {hypha_debugger-0.1.0 → hypha_debugger-0.1.3}/hypha_debugger/services/filesystem.py +0 -0
- {hypha_debugger-0.1.0 → hypha_debugger-0.1.3}/hypha_debugger/services/info.py +0 -0
- {hypha_debugger-0.1.0 → hypha_debugger-0.1.3}/hypha_debugger/services/inspect_vars.py +0 -0
- {hypha_debugger-0.1.0 → hypha_debugger-0.1.3}/hypha_debugger/utils/__init__.py +0 -0
- {hypha_debugger-0.1.0 → hypha_debugger-0.1.3}/hypha_debugger/utils/env.py +0 -0
- {hypha_debugger-0.1.0 → hypha_debugger-0.1.3}/hypha_debugger.egg-info/dependency_links.txt +0 -0
- {hypha_debugger-0.1.0 → hypha_debugger-0.1.3}/hypha_debugger.egg-info/requires.txt +0 -0
- {hypha_debugger-0.1.0 → hypha_debugger-0.1.3}/hypha_debugger.egg-info/top_level.txt +0 -0
- {hypha_debugger-0.1.0 → hypha_debugger-0.1.3}/setup.cfg +0 -0
- {hypha_debugger-0.1.0 → hypha_debugger-0.1.3}/tests/test_services.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hypha-debugger
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
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
|
|
@@ -185,6 +185,20 @@ Inject into any Python process to enable remote code execution, variable inspect
|
|
|
185
185
|
pip install hypha-debugger
|
|
186
186
|
```
|
|
187
187
|
|
|
188
|
+
**CLI (simplest — just run and get instructions):**
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
hypha-debugger
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Or with options:
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
hypha-debugger --server-url https://hypha.aicell.io --service-id my-debugger
|
|
198
|
+
hypha-debugger --no-token # URL-secret mode, no auth needed
|
|
199
|
+
python -m hypha_debugger # alternative
|
|
200
|
+
```
|
|
201
|
+
|
|
188
202
|
**Async:**
|
|
189
203
|
|
|
190
204
|
```python
|
|
@@ -193,8 +207,7 @@ from hypha_debugger import start_debugger
|
|
|
193
207
|
|
|
194
208
|
async def main():
|
|
195
209
|
session = await start_debugger(server_url="https://hypha.aicell.io")
|
|
196
|
-
|
|
197
|
-
print(session.token) # JWT token
|
|
210
|
+
session.print_instructions() # print instructions anytime
|
|
198
211
|
await session.serve_forever()
|
|
199
212
|
|
|
200
213
|
asyncio.run(main())
|
|
@@ -206,20 +219,30 @@ asyncio.run(main())
|
|
|
206
219
|
from hypha_debugger import start_debugger_sync
|
|
207
220
|
|
|
208
221
|
session = start_debugger_sync(server_url="https://hypha.aicell.io")
|
|
209
|
-
#
|
|
210
|
-
print(session.service_url)
|
|
222
|
+
session.print_instructions() # print instructions anytime
|
|
211
223
|
```
|
|
212
224
|
|
|
213
225
|
### What You Get
|
|
214
226
|
|
|
227
|
+
The debugger prints copy-paste instructions on startup:
|
|
228
|
+
|
|
215
229
|
```
|
|
216
230
|
[hypha-debugger] Connected to https://hypha.aicell.io
|
|
217
|
-
[hypha-debugger] Service
|
|
218
|
-
[hypha-debugger]
|
|
219
|
-
|
|
220
|
-
|
|
231
|
+
[hypha-debugger] Service ID: ws-xxx/clientId:py-debugger
|
|
232
|
+
[hypha-debugger] Service URL: https://hypha.aicell.io/ws-xxx/services/py-debugger
|
|
233
|
+
|
|
234
|
+
SERVICE_URL="https://hypha.aicell.io/ws-xxx/services/py-debugger"
|
|
235
|
+
TOKEN="eyJ..."
|
|
236
|
+
|
|
237
|
+
# Quick test:
|
|
238
|
+
curl "$SERVICE_URL/get_process_info?_mode=last" -H "Authorization: Bearer $TOKEN"
|
|
239
|
+
|
|
240
|
+
# Execute code:
|
|
241
|
+
curl -X POST "$SERVICE_URL/execute_code?_mode=last" -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d '{"code": "import sys; sys.version"}'
|
|
221
242
|
```
|
|
222
243
|
|
|
244
|
+
Call `session.print_instructions()` anytime to reprint them.
|
|
245
|
+
|
|
223
246
|
### Service Functions (Python)
|
|
224
247
|
|
|
225
248
|
| Function | Description |
|
|
@@ -157,6 +157,20 @@ Inject into any Python process to enable remote code execution, variable inspect
|
|
|
157
157
|
pip install hypha-debugger
|
|
158
158
|
```
|
|
159
159
|
|
|
160
|
+
**CLI (simplest — just run and get instructions):**
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
hypha-debugger
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Or with options:
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
hypha-debugger --server-url https://hypha.aicell.io --service-id my-debugger
|
|
170
|
+
hypha-debugger --no-token # URL-secret mode, no auth needed
|
|
171
|
+
python -m hypha_debugger # alternative
|
|
172
|
+
```
|
|
173
|
+
|
|
160
174
|
**Async:**
|
|
161
175
|
|
|
162
176
|
```python
|
|
@@ -165,8 +179,7 @@ from hypha_debugger import start_debugger
|
|
|
165
179
|
|
|
166
180
|
async def main():
|
|
167
181
|
session = await start_debugger(server_url="https://hypha.aicell.io")
|
|
168
|
-
|
|
169
|
-
print(session.token) # JWT token
|
|
182
|
+
session.print_instructions() # print instructions anytime
|
|
170
183
|
await session.serve_forever()
|
|
171
184
|
|
|
172
185
|
asyncio.run(main())
|
|
@@ -178,20 +191,30 @@ asyncio.run(main())
|
|
|
178
191
|
from hypha_debugger import start_debugger_sync
|
|
179
192
|
|
|
180
193
|
session = start_debugger_sync(server_url="https://hypha.aicell.io")
|
|
181
|
-
#
|
|
182
|
-
print(session.service_url)
|
|
194
|
+
session.print_instructions() # print instructions anytime
|
|
183
195
|
```
|
|
184
196
|
|
|
185
197
|
### What You Get
|
|
186
198
|
|
|
199
|
+
The debugger prints copy-paste instructions on startup:
|
|
200
|
+
|
|
187
201
|
```
|
|
188
202
|
[hypha-debugger] Connected to https://hypha.aicell.io
|
|
189
|
-
[hypha-debugger] Service
|
|
190
|
-
[hypha-debugger]
|
|
191
|
-
|
|
192
|
-
|
|
203
|
+
[hypha-debugger] Service ID: ws-xxx/clientId:py-debugger
|
|
204
|
+
[hypha-debugger] Service URL: https://hypha.aicell.io/ws-xxx/services/py-debugger
|
|
205
|
+
|
|
206
|
+
SERVICE_URL="https://hypha.aicell.io/ws-xxx/services/py-debugger"
|
|
207
|
+
TOKEN="eyJ..."
|
|
208
|
+
|
|
209
|
+
# Quick test:
|
|
210
|
+
curl "$SERVICE_URL/get_process_info?_mode=last" -H "Authorization: Bearer $TOKEN"
|
|
211
|
+
|
|
212
|
+
# Execute code:
|
|
213
|
+
curl -X POST "$SERVICE_URL/execute_code?_mode=last" -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d '{"code": "import sys; sys.version"}'
|
|
193
214
|
```
|
|
194
215
|
|
|
216
|
+
Call `session.print_instructions()` anytime to reprint them.
|
|
217
|
+
|
|
195
218
|
### Service Functions (Python)
|
|
196
219
|
|
|
197
220
|
| Function | Description |
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""CLI entry point: python -m hypha_debugger
|
|
2
|
+
|
|
3
|
+
Starts a debugger session and prints instructions for remote access.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import asyncio
|
|
8
|
+
import signal
|
|
9
|
+
import sys
|
|
10
|
+
|
|
11
|
+
from hypha_debugger.debugger import start_debugger
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def main():
|
|
15
|
+
parser = argparse.ArgumentParser(
|
|
16
|
+
prog="hypha-debugger",
|
|
17
|
+
description="Start a Hypha debugger session for this Python process.",
|
|
18
|
+
)
|
|
19
|
+
parser.add_argument(
|
|
20
|
+
"--server-url",
|
|
21
|
+
default="https://hypha.aicell.io",
|
|
22
|
+
help="Hypha server URL (default: https://hypha.aicell.io)",
|
|
23
|
+
)
|
|
24
|
+
parser.add_argument(
|
|
25
|
+
"--workspace",
|
|
26
|
+
default="",
|
|
27
|
+
help="Workspace name (auto-assigned if omitted)",
|
|
28
|
+
)
|
|
29
|
+
parser.add_argument(
|
|
30
|
+
"--token",
|
|
31
|
+
default="",
|
|
32
|
+
help="Authentication token for connecting to Hypha",
|
|
33
|
+
)
|
|
34
|
+
parser.add_argument(
|
|
35
|
+
"--service-id",
|
|
36
|
+
default="py-debugger",
|
|
37
|
+
help="Service ID to register as (default: py-debugger)",
|
|
38
|
+
)
|
|
39
|
+
parser.add_argument(
|
|
40
|
+
"--service-name",
|
|
41
|
+
default="Python Debugger",
|
|
42
|
+
help="Human-readable service name (default: Python Debugger)",
|
|
43
|
+
)
|
|
44
|
+
parser.add_argument(
|
|
45
|
+
"--no-token",
|
|
46
|
+
action="store_true",
|
|
47
|
+
help="Disable token requirement (URL-secret mode)",
|
|
48
|
+
)
|
|
49
|
+
args = parser.parse_args()
|
|
50
|
+
|
|
51
|
+
async def run():
|
|
52
|
+
session = await start_debugger(
|
|
53
|
+
server_url=args.server_url,
|
|
54
|
+
workspace=args.workspace,
|
|
55
|
+
token=args.token,
|
|
56
|
+
service_id=args.service_id,
|
|
57
|
+
service_name=args.service_name,
|
|
58
|
+
require_token=not args.no_token,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
print()
|
|
62
|
+
print("[hypha-debugger] Debugger is running. Press Ctrl+C to stop.")
|
|
63
|
+
print()
|
|
64
|
+
|
|
65
|
+
# Handle graceful shutdown
|
|
66
|
+
stop = asyncio.Event()
|
|
67
|
+
|
|
68
|
+
def _signal_handler():
|
|
69
|
+
stop.set()
|
|
70
|
+
|
|
71
|
+
loop = asyncio.get_running_loop()
|
|
72
|
+
for sig in (signal.SIGINT, signal.SIGTERM):
|
|
73
|
+
loop.add_signal_handler(sig, _signal_handler)
|
|
74
|
+
|
|
75
|
+
await stop.wait()
|
|
76
|
+
print("\n[hypha-debugger] Shutting down...")
|
|
77
|
+
await session.destroy()
|
|
78
|
+
|
|
79
|
+
try:
|
|
80
|
+
asyncio.run(run())
|
|
81
|
+
except KeyboardInterrupt:
|
|
82
|
+
pass
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
if __name__ == "__main__":
|
|
86
|
+
main()
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import logging
|
|
5
|
+
import secrets
|
|
5
6
|
import threading
|
|
6
7
|
from dataclasses import dataclass, field
|
|
7
8
|
from typing import Optional
|
|
@@ -19,33 +20,84 @@ logger = logging.getLogger("hypha_debugger")
|
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
def _build_service_url(server_url: str, service_id: str) -> str:
|
|
22
|
-
"""Build
|
|
23
|
+
"""Build a stable, predictable HTTP service URL.
|
|
24
|
+
|
|
25
|
+
Strips the clientId prefix so the URL uses only the bare service name.
|
|
26
|
+
Callers append ?_mode=last to resolve the most recent instance.
|
|
23
27
|
|
|
24
28
|
Args:
|
|
25
29
|
server_url: e.g. "https://hypha.aicell.io"
|
|
26
30
|
service_id: e.g. "ws-xxx/clientId:py-debugger"
|
|
27
31
|
|
|
28
32
|
Returns:
|
|
29
|
-
e.g. "https://hypha.aicell.io/ws-xxx/services/
|
|
33
|
+
e.g. "https://hypha.aicell.io/ws-xxx/services/py-debugger"
|
|
30
34
|
"""
|
|
31
35
|
base = server_url.rstrip("/")
|
|
32
36
|
# service_id format: "workspace/clientId:svcName"
|
|
33
37
|
parts = service_id.split("/", 1)
|
|
34
38
|
if len(parts) == 2:
|
|
35
39
|
workspace, svc_part = parts
|
|
36
|
-
|
|
40
|
+
# Strip clientId: "abc123:py-debugger" → "py-debugger"
|
|
41
|
+
if ":" in svc_part:
|
|
42
|
+
svc_name = svc_part.split(":", 1)[1]
|
|
43
|
+
else:
|
|
44
|
+
svc_name = svc_part
|
|
45
|
+
return f"{base}/{workspace}/services/{svc_name}"
|
|
37
46
|
return f"{base}/services/{service_id}"
|
|
38
47
|
|
|
39
48
|
|
|
40
|
-
def
|
|
49
|
+
def _build_instruction_block(
|
|
50
|
+
service_url: str,
|
|
51
|
+
token: str,
|
|
52
|
+
require_token: bool = True,
|
|
53
|
+
) -> str:
|
|
54
|
+
"""Build the instruction block with copy-paste commands for remote access.
|
|
55
|
+
|
|
56
|
+
Returns a formatted string similar to the JS overlay instructions.
|
|
57
|
+
"""
|
|
58
|
+
if require_token:
|
|
59
|
+
return "\n".join([
|
|
60
|
+
f'SERVICE_URL="{service_url}"',
|
|
61
|
+
f'TOKEN="{token}"',
|
|
62
|
+
"",
|
|
63
|
+
"# Quick test:",
|
|
64
|
+
'curl "$SERVICE_URL/get_process_info?_mode=last" -H "Authorization: Bearer $TOKEN"',
|
|
65
|
+
"",
|
|
66
|
+
"# Execute code:",
|
|
67
|
+
"curl -X POST \"$SERVICE_URL/execute_code?_mode=last\" "
|
|
68
|
+
"-H \"Authorization: Bearer $TOKEN\" "
|
|
69
|
+
"-H \"Content-Type: application/json\" "
|
|
70
|
+
"-d '{\"code\": \"import sys; sys.version\"}'",
|
|
71
|
+
])
|
|
72
|
+
else:
|
|
73
|
+
return "\n".join([
|
|
74
|
+
f'SERVICE_URL="{service_url}"',
|
|
75
|
+
"",
|
|
76
|
+
"# Quick test (no auth required — keep URL secret):",
|
|
77
|
+
'curl "$SERVICE_URL/get_process_info?_mode=last"',
|
|
78
|
+
"",
|
|
79
|
+
"# Execute code:",
|
|
80
|
+
"curl -X POST \"$SERVICE_URL/execute_code?_mode=last\" "
|
|
81
|
+
"-H \"Content-Type: application/json\" "
|
|
82
|
+
"-d '{\"code\": \"import sys; sys.version\"}'",
|
|
83
|
+
])
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _print_session_info(
|
|
87
|
+
server_url: str,
|
|
88
|
+
service_id: str,
|
|
89
|
+
service_url: str,
|
|
90
|
+
token: str,
|
|
91
|
+
require_token: bool = True,
|
|
92
|
+
) -> None:
|
|
41
93
|
"""Print session information with remote access URLs."""
|
|
42
94
|
print(f"[hypha-debugger] Connected to {server_url}")
|
|
43
95
|
print(f"[hypha-debugger] Service ID: {service_id}")
|
|
44
96
|
print(f"[hypha-debugger] Service URL: {service_url}")
|
|
45
|
-
|
|
97
|
+
if require_token:
|
|
98
|
+
print(f"[hypha-debugger] Token: {token}")
|
|
46
99
|
print()
|
|
47
|
-
print(
|
|
48
|
-
print(f" curl '{service_url}/get_process_info' -H 'Authorization: Bearer {token}'")
|
|
100
|
+
print(_build_instruction_block(service_url, token, require_token))
|
|
49
101
|
|
|
50
102
|
|
|
51
103
|
@dataclass
|
|
@@ -63,6 +115,14 @@ class DebugSession:
|
|
|
63
115
|
)
|
|
64
116
|
_thread: Optional[threading.Thread] = field(default=None, repr=False)
|
|
65
117
|
|
|
118
|
+
def print_instructions(self) -> str:
|
|
119
|
+
"""Print copy-paste instructions for remote access and return them."""
|
|
120
|
+
instructions = _build_instruction_block(
|
|
121
|
+
self.service_url, self.token, bool(self.token)
|
|
122
|
+
)
|
|
123
|
+
print(instructions)
|
|
124
|
+
return instructions
|
|
125
|
+
|
|
66
126
|
async def serve_forever(self):
|
|
67
127
|
"""Block until disconnected (async)."""
|
|
68
128
|
try:
|
|
@@ -102,7 +162,8 @@ async def start_debugger(
|
|
|
102
162
|
token: str = "",
|
|
103
163
|
service_id: str = "py-debugger",
|
|
104
164
|
service_name: str = "Python Debugger",
|
|
105
|
-
visibility: str = "
|
|
165
|
+
visibility: str = "",
|
|
166
|
+
require_token: bool = True,
|
|
106
167
|
) -> DebugSession:
|
|
107
168
|
"""Start the Hypha debugger (async).
|
|
108
169
|
|
|
@@ -112,16 +173,26 @@ async def start_debugger(
|
|
|
112
173
|
Args:
|
|
113
174
|
server_url: Hypha server URL (required).
|
|
114
175
|
workspace: Workspace name (auto-assigned if empty).
|
|
115
|
-
token: Authentication token.
|
|
176
|
+
token: Authentication token for connecting to Hypha.
|
|
116
177
|
service_id: Service ID to register as.
|
|
117
178
|
service_name: Human-readable service name.
|
|
118
|
-
visibility: Service visibility ("public", "protected", "unlisted").
|
|
179
|
+
visibility: Service visibility override ("public", "protected", "unlisted").
|
|
180
|
+
Defaults to "protected" when require_token=True, "unlisted" when False.
|
|
181
|
+
require_token: Whether remote callers must supply a JWT token (default True).
|
|
182
|
+
When False, the service is registered as "unlisted" and the service ID
|
|
183
|
+
gets a random suffix — the URL itself acts as the secret, no token needed.
|
|
119
184
|
|
|
120
185
|
Returns:
|
|
121
186
|
A DebugSession with service_id, workspace, server, service_url, and token.
|
|
122
187
|
"""
|
|
123
188
|
from hypha_rpc import connect_to_server
|
|
124
189
|
|
|
190
|
+
# Resolve effective service_id and visibility based on require_token.
|
|
191
|
+
effective_id = service_id
|
|
192
|
+
if not require_token and service_id == "py-debugger":
|
|
193
|
+
effective_id = f"py-debugger-{secrets.token_hex(8)}"
|
|
194
|
+
effective_visibility = visibility or ("protected" if require_token else "unlisted")
|
|
195
|
+
|
|
125
196
|
connect_config = {"server_url": server_url}
|
|
126
197
|
if workspace:
|
|
127
198
|
connect_config["workspace"] = workspace
|
|
@@ -132,14 +203,14 @@ async def start_debugger(
|
|
|
132
203
|
|
|
133
204
|
service = {
|
|
134
205
|
"name": service_name,
|
|
135
|
-
"id":
|
|
206
|
+
"id": effective_id,
|
|
136
207
|
"type": "debugger",
|
|
137
208
|
"description": (
|
|
138
209
|
"Remote Python process debugger. Allows inspecting variables, "
|
|
139
210
|
"executing code, browsing files, and getting process information."
|
|
140
211
|
),
|
|
141
212
|
"config": {
|
|
142
|
-
"visibility":
|
|
213
|
+
"visibility": effective_visibility,
|
|
143
214
|
"run_in_executor": True,
|
|
144
215
|
},
|
|
145
216
|
"get_process_info": get_process_info,
|
|
@@ -153,11 +224,13 @@ async def start_debugger(
|
|
|
153
224
|
}
|
|
154
225
|
|
|
155
226
|
svc_info = await server.register_service(service)
|
|
156
|
-
actual_id = svc_info.get("id",
|
|
227
|
+
actual_id = svc_info.get("id", effective_id) if isinstance(svc_info, dict) else effective_id
|
|
157
228
|
ws = server.config.get("workspace", workspace) if hasattr(server.config, "get") else getattr(server.config, "workspace", workspace)
|
|
158
229
|
|
|
159
|
-
# Generate a token for remote access
|
|
160
|
-
session_token =
|
|
230
|
+
# Generate a token for remote access (24h expiry); skip in no-token mode.
|
|
231
|
+
session_token = ""
|
|
232
|
+
if require_token:
|
|
233
|
+
session_token = await server.generate_token({"expires_in": 86400})
|
|
161
234
|
|
|
162
235
|
# Build the HTTP service URL
|
|
163
236
|
service_url = _build_service_url(server_url, actual_id)
|
|
@@ -168,7 +241,7 @@ async def start_debugger(
|
|
|
168
241
|
ws,
|
|
169
242
|
actual_id,
|
|
170
243
|
)
|
|
171
|
-
_print_session_info(server_url, actual_id, service_url, session_token)
|
|
244
|
+
_print_session_info(server_url, actual_id, service_url, session_token, require_token)
|
|
172
245
|
|
|
173
246
|
return DebugSession(
|
|
174
247
|
service_id=actual_id,
|
|
@@ -186,7 +259,8 @@ def start_debugger_sync(
|
|
|
186
259
|
token: str = "",
|
|
187
260
|
service_id: str = "py-debugger",
|
|
188
261
|
service_name: str = "Python Debugger",
|
|
189
|
-
visibility: str = "
|
|
262
|
+
visibility: str = "",
|
|
263
|
+
require_token: bool = True,
|
|
190
264
|
) -> DebugSession:
|
|
191
265
|
"""Start the Hypha debugger (sync).
|
|
192
266
|
|
|
@@ -201,18 +275,24 @@ def start_debugger_sync(
|
|
|
201
275
|
"""
|
|
202
276
|
from hypha_rpc.sync import connect_to_server
|
|
203
277
|
|
|
278
|
+
# Resolve effective service_id and visibility based on require_token.
|
|
279
|
+
effective_id = service_id
|
|
280
|
+
if not require_token and service_id == "py-debugger":
|
|
281
|
+
effective_id = f"py-debugger-{secrets.token_hex(8)}"
|
|
282
|
+
effective_visibility = visibility or ("protected" if require_token else "unlisted")
|
|
283
|
+
|
|
204
284
|
server = connect_to_server({"server_url": server_url, **({"workspace": workspace} if workspace else {}), **({"token": token} if token else {})})
|
|
205
285
|
|
|
206
286
|
service = {
|
|
207
287
|
"name": service_name,
|
|
208
|
-
"id":
|
|
288
|
+
"id": effective_id,
|
|
209
289
|
"type": "debugger",
|
|
210
290
|
"description": (
|
|
211
291
|
"Remote Python process debugger. Allows inspecting variables, "
|
|
212
292
|
"executing code, browsing files, and getting process information."
|
|
213
293
|
),
|
|
214
294
|
"config": {
|
|
215
|
-
"visibility":
|
|
295
|
+
"visibility": effective_visibility,
|
|
216
296
|
"run_in_executor": True,
|
|
217
297
|
},
|
|
218
298
|
"get_process_info": get_process_info,
|
|
@@ -226,11 +306,13 @@ def start_debugger_sync(
|
|
|
226
306
|
}
|
|
227
307
|
|
|
228
308
|
svc_info = server.register_service(service)
|
|
229
|
-
actual_id = svc_info.get("id",
|
|
309
|
+
actual_id = svc_info.get("id", effective_id) if isinstance(svc_info, dict) else effective_id
|
|
230
310
|
ws = server.config.get("workspace", workspace) if hasattr(server.config, "get") else getattr(server.config, "workspace", workspace)
|
|
231
311
|
|
|
232
|
-
# Generate a token for remote access
|
|
233
|
-
session_token =
|
|
312
|
+
# Generate a token for remote access (24h expiry); skip in no-token mode.
|
|
313
|
+
session_token = ""
|
|
314
|
+
if require_token:
|
|
315
|
+
session_token = server.generate_token({"expires_in": 86400})
|
|
234
316
|
|
|
235
317
|
# Build the HTTP service URL
|
|
236
318
|
service_url = _build_service_url(server_url, actual_id)
|
|
@@ -241,7 +323,7 @@ def start_debugger_sync(
|
|
|
241
323
|
ws,
|
|
242
324
|
actual_id,
|
|
243
325
|
)
|
|
244
|
-
_print_session_info(server_url, actual_id, service_url, session_token)
|
|
326
|
+
_print_session_info(server_url, actual_id, service_url, session_token, require_token)
|
|
245
327
|
|
|
246
328
|
return DebugSession(
|
|
247
329
|
service_id=actual_id,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hypha-debugger
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
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
|
|
@@ -185,6 +185,20 @@ Inject into any Python process to enable remote code execution, variable inspect
|
|
|
185
185
|
pip install hypha-debugger
|
|
186
186
|
```
|
|
187
187
|
|
|
188
|
+
**CLI (simplest — just run and get instructions):**
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
hypha-debugger
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Or with options:
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
hypha-debugger --server-url https://hypha.aicell.io --service-id my-debugger
|
|
198
|
+
hypha-debugger --no-token # URL-secret mode, no auth needed
|
|
199
|
+
python -m hypha_debugger # alternative
|
|
200
|
+
```
|
|
201
|
+
|
|
188
202
|
**Async:**
|
|
189
203
|
|
|
190
204
|
```python
|
|
@@ -193,8 +207,7 @@ from hypha_debugger import start_debugger
|
|
|
193
207
|
|
|
194
208
|
async def main():
|
|
195
209
|
session = await start_debugger(server_url="https://hypha.aicell.io")
|
|
196
|
-
|
|
197
|
-
print(session.token) # JWT token
|
|
210
|
+
session.print_instructions() # print instructions anytime
|
|
198
211
|
await session.serve_forever()
|
|
199
212
|
|
|
200
213
|
asyncio.run(main())
|
|
@@ -206,20 +219,30 @@ asyncio.run(main())
|
|
|
206
219
|
from hypha_debugger import start_debugger_sync
|
|
207
220
|
|
|
208
221
|
session = start_debugger_sync(server_url="https://hypha.aicell.io")
|
|
209
|
-
#
|
|
210
|
-
print(session.service_url)
|
|
222
|
+
session.print_instructions() # print instructions anytime
|
|
211
223
|
```
|
|
212
224
|
|
|
213
225
|
### What You Get
|
|
214
226
|
|
|
227
|
+
The debugger prints copy-paste instructions on startup:
|
|
228
|
+
|
|
215
229
|
```
|
|
216
230
|
[hypha-debugger] Connected to https://hypha.aicell.io
|
|
217
|
-
[hypha-debugger] Service
|
|
218
|
-
[hypha-debugger]
|
|
219
|
-
|
|
220
|
-
|
|
231
|
+
[hypha-debugger] Service ID: ws-xxx/clientId:py-debugger
|
|
232
|
+
[hypha-debugger] Service URL: https://hypha.aicell.io/ws-xxx/services/py-debugger
|
|
233
|
+
|
|
234
|
+
SERVICE_URL="https://hypha.aicell.io/ws-xxx/services/py-debugger"
|
|
235
|
+
TOKEN="eyJ..."
|
|
236
|
+
|
|
237
|
+
# Quick test:
|
|
238
|
+
curl "$SERVICE_URL/get_process_info?_mode=last" -H "Authorization: Bearer $TOKEN"
|
|
239
|
+
|
|
240
|
+
# Execute code:
|
|
241
|
+
curl -X POST "$SERVICE_URL/execute_code?_mode=last" -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d '{"code": "import sys; sys.version"}'
|
|
221
242
|
```
|
|
222
243
|
|
|
244
|
+
Call `session.print_instructions()` anytime to reprint them.
|
|
245
|
+
|
|
223
246
|
### Service Functions (Python)
|
|
224
247
|
|
|
225
248
|
| Function | Description |
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
README.md
|
|
2
2
|
pyproject.toml
|
|
3
3
|
hypha_debugger/__init__.py
|
|
4
|
+
hypha_debugger/__main__.py
|
|
4
5
|
hypha_debugger/debugger.py
|
|
5
6
|
hypha_debugger.egg-info/PKG-INFO
|
|
6
7
|
hypha_debugger.egg-info/SOURCES.txt
|
|
7
8
|
hypha_debugger.egg-info/dependency_links.txt
|
|
9
|
+
hypha_debugger.egg-info/entry_points.txt
|
|
8
10
|
hypha_debugger.egg-info/requires.txt
|
|
9
11
|
hypha_debugger.egg-info/top_level.txt
|
|
10
12
|
hypha_debugger/services/__init__.py
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "hypha-debugger"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.3"
|
|
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"}
|
|
@@ -40,6 +40,9 @@ full = [
|
|
|
40
40
|
"psutil>=5.9",
|
|
41
41
|
]
|
|
42
42
|
|
|
43
|
+
[project.scripts]
|
|
44
|
+
hypha-debugger = "hypha_debugger.__main__:main"
|
|
45
|
+
|
|
43
46
|
[tool.setuptools.packages.find]
|
|
44
47
|
where = ["."]
|
|
45
48
|
include = ["hypha_debugger*"]
|
|
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
|