hypha-debugger 0.1.0__tar.gz → 0.1.4__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.4}/PKG-INFO +32 -9
- {hypha_debugger-0.1.0 → hypha_debugger-0.1.4}/README.md +31 -8
- {hypha_debugger-0.1.0 → hypha_debugger-0.1.4}/hypha_debugger/__init__.py +1 -1
- hypha_debugger-0.1.4/hypha_debugger/__main__.py +86 -0
- {hypha_debugger-0.1.0 → hypha_debugger-0.1.4}/hypha_debugger/debugger.py +130 -23
- {hypha_debugger-0.1.0 → hypha_debugger-0.1.4}/hypha_debugger.egg-info/PKG-INFO +32 -9
- {hypha_debugger-0.1.0 → hypha_debugger-0.1.4}/hypha_debugger.egg-info/SOURCES.txt +2 -0
- hypha_debugger-0.1.4/hypha_debugger.egg-info/entry_points.txt +2 -0
- {hypha_debugger-0.1.0 → hypha_debugger-0.1.4}/pyproject.toml +4 -1
- {hypha_debugger-0.1.0 → hypha_debugger-0.1.4}/hypha_debugger/services/__init__.py +0 -0
- {hypha_debugger-0.1.0 → hypha_debugger-0.1.4}/hypha_debugger/services/execute.py +0 -0
- {hypha_debugger-0.1.0 → hypha_debugger-0.1.4}/hypha_debugger/services/filesystem.py +0 -0
- {hypha_debugger-0.1.0 → hypha_debugger-0.1.4}/hypha_debugger/services/info.py +0 -0
- {hypha_debugger-0.1.0 → hypha_debugger-0.1.4}/hypha_debugger/services/inspect_vars.py +0 -0
- {hypha_debugger-0.1.0 → hypha_debugger-0.1.4}/hypha_debugger/utils/__init__.py +0 -0
- {hypha_debugger-0.1.0 → hypha_debugger-0.1.4}/hypha_debugger/utils/env.py +0 -0
- {hypha_debugger-0.1.0 → hypha_debugger-0.1.4}/hypha_debugger.egg-info/dependency_links.txt +0 -0
- {hypha_debugger-0.1.0 → hypha_debugger-0.1.4}/hypha_debugger.egg-info/requires.txt +0 -0
- {hypha_debugger-0.1.0 → hypha_debugger-0.1.4}/hypha_debugger.egg-info/top_level.txt +0 -0
- {hypha_debugger-0.1.0 → hypha_debugger-0.1.4}/setup.cfg +0 -0
- {hypha_debugger-0.1.0 → hypha_debugger-0.1.4}/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.4
|
|
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,102 @@ 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
|
+
auth_header = f' -H "Authorization: Bearer $TOKEN"' if require_token else ""
|
|
59
|
+
lines = [
|
|
60
|
+
"# Hypha Remote Debugger — Python Process",
|
|
61
|
+
"# A debugger is attached to a running Python process.",
|
|
62
|
+
"# You can remotely inspect variables, execute code, browse files,",
|
|
63
|
+
"# and query process info via the HTTP API below.",
|
|
64
|
+
"#",
|
|
65
|
+
"# Available functions: get_process_info, execute_code, get_variable,",
|
|
66
|
+
"# list_variables, get_stack_trace, list_files, read_file, get_installed_packages",
|
|
67
|
+
"#",
|
|
68
|
+
"# All endpoints require ?_mode=last suffix. POST endpoints accept JSON body.",
|
|
69
|
+
"",
|
|
70
|
+
f'SERVICE_URL="{service_url}"',
|
|
71
|
+
]
|
|
72
|
+
if require_token:
|
|
73
|
+
lines.append(f'TOKEN="{token}"')
|
|
74
|
+
lines += [
|
|
75
|
+
"",
|
|
76
|
+
"# Get process info (PID, Python version, CWD, memory):",
|
|
77
|
+
f'curl "$SERVICE_URL/get_process_info?_mode=last"{auth_header}',
|
|
78
|
+
"",
|
|
79
|
+
"# Execute Python code remotely:",
|
|
80
|
+
f"curl -X POST \"$SERVICE_URL/execute_code?_mode=last\""
|
|
81
|
+
f"{auth_header}"
|
|
82
|
+
" -H \"Content-Type: application/json\""
|
|
83
|
+
" -d '{\"code\": \"import sys; sys.version\"}'",
|
|
84
|
+
"",
|
|
85
|
+
"# Inspect a variable:",
|
|
86
|
+
f"curl -X POST \"$SERVICE_URL/get_variable?_mode=last\""
|
|
87
|
+
f"{auth_header}"
|
|
88
|
+
" -H \"Content-Type: application/json\""
|
|
89
|
+
" -d '{\"name\": \"my_var\"}'",
|
|
90
|
+
"",
|
|
91
|
+
"# List files in working directory:",
|
|
92
|
+
f'curl "$SERVICE_URL/list_files?_mode=last"{auth_header}',
|
|
93
|
+
]
|
|
94
|
+
return "\n".join(lines)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _print_session_info(
|
|
98
|
+
server_url: str,
|
|
99
|
+
service_id: str,
|
|
100
|
+
service_url: str,
|
|
101
|
+
token: str,
|
|
102
|
+
require_token: bool = True,
|
|
103
|
+
) -> None:
|
|
41
104
|
"""Print session information with remote access URLs."""
|
|
42
105
|
print(f"[hypha-debugger] Connected to {server_url}")
|
|
43
106
|
print(f"[hypha-debugger] Service ID: {service_id}")
|
|
44
107
|
print(f"[hypha-debugger] Service URL: {service_url}")
|
|
45
|
-
|
|
108
|
+
if require_token:
|
|
109
|
+
print(f"[hypha-debugger] Token: {token}")
|
|
110
|
+
print()
|
|
111
|
+
sep = "=" * 60
|
|
112
|
+
print(f"{sep}")
|
|
113
|
+
print(" Copy the text below to your AI agent")
|
|
114
|
+
print(f"{sep}")
|
|
46
115
|
print()
|
|
47
|
-
print(
|
|
48
|
-
print(
|
|
116
|
+
print(_build_instruction_block(service_url, token, require_token))
|
|
117
|
+
print()
|
|
118
|
+
print(f"{sep}")
|
|
49
119
|
|
|
50
120
|
|
|
51
121
|
@dataclass
|
|
@@ -63,6 +133,21 @@ class DebugSession:
|
|
|
63
133
|
)
|
|
64
134
|
_thread: Optional[threading.Thread] = field(default=None, repr=False)
|
|
65
135
|
|
|
136
|
+
def print_instructions(self) -> str:
|
|
137
|
+
"""Print copy-paste instructions for remote access and return them."""
|
|
138
|
+
instructions = _build_instruction_block(
|
|
139
|
+
self.service_url, self.token, bool(self.token)
|
|
140
|
+
)
|
|
141
|
+
sep = "=" * 60
|
|
142
|
+
print(f"{sep}")
|
|
143
|
+
print(" Copy the text below to your AI agent")
|
|
144
|
+
print(f"{sep}")
|
|
145
|
+
print()
|
|
146
|
+
print(instructions)
|
|
147
|
+
print()
|
|
148
|
+
print(f"{sep}")
|
|
149
|
+
return instructions
|
|
150
|
+
|
|
66
151
|
async def serve_forever(self):
|
|
67
152
|
"""Block until disconnected (async)."""
|
|
68
153
|
try:
|
|
@@ -102,7 +187,8 @@ async def start_debugger(
|
|
|
102
187
|
token: str = "",
|
|
103
188
|
service_id: str = "py-debugger",
|
|
104
189
|
service_name: str = "Python Debugger",
|
|
105
|
-
visibility: str = "
|
|
190
|
+
visibility: str = "",
|
|
191
|
+
require_token: bool = True,
|
|
106
192
|
) -> DebugSession:
|
|
107
193
|
"""Start the Hypha debugger (async).
|
|
108
194
|
|
|
@@ -112,16 +198,26 @@ async def start_debugger(
|
|
|
112
198
|
Args:
|
|
113
199
|
server_url: Hypha server URL (required).
|
|
114
200
|
workspace: Workspace name (auto-assigned if empty).
|
|
115
|
-
token: Authentication token.
|
|
201
|
+
token: Authentication token for connecting to Hypha.
|
|
116
202
|
service_id: Service ID to register as.
|
|
117
203
|
service_name: Human-readable service name.
|
|
118
|
-
visibility: Service visibility ("public", "protected", "unlisted").
|
|
204
|
+
visibility: Service visibility override ("public", "protected", "unlisted").
|
|
205
|
+
Defaults to "protected" when require_token=True, "unlisted" when False.
|
|
206
|
+
require_token: Whether remote callers must supply a JWT token (default True).
|
|
207
|
+
When False, the service is registered as "unlisted" and the service ID
|
|
208
|
+
gets a random suffix — the URL itself acts as the secret, no token needed.
|
|
119
209
|
|
|
120
210
|
Returns:
|
|
121
211
|
A DebugSession with service_id, workspace, server, service_url, and token.
|
|
122
212
|
"""
|
|
123
213
|
from hypha_rpc import connect_to_server
|
|
124
214
|
|
|
215
|
+
# Resolve effective service_id and visibility based on require_token.
|
|
216
|
+
effective_id = service_id
|
|
217
|
+
if not require_token and service_id == "py-debugger":
|
|
218
|
+
effective_id = f"py-debugger-{secrets.token_hex(8)}"
|
|
219
|
+
effective_visibility = visibility or ("protected" if require_token else "unlisted")
|
|
220
|
+
|
|
125
221
|
connect_config = {"server_url": server_url}
|
|
126
222
|
if workspace:
|
|
127
223
|
connect_config["workspace"] = workspace
|
|
@@ -132,14 +228,14 @@ async def start_debugger(
|
|
|
132
228
|
|
|
133
229
|
service = {
|
|
134
230
|
"name": service_name,
|
|
135
|
-
"id":
|
|
231
|
+
"id": effective_id,
|
|
136
232
|
"type": "debugger",
|
|
137
233
|
"description": (
|
|
138
234
|
"Remote Python process debugger. Allows inspecting variables, "
|
|
139
235
|
"executing code, browsing files, and getting process information."
|
|
140
236
|
),
|
|
141
237
|
"config": {
|
|
142
|
-
"visibility":
|
|
238
|
+
"visibility": effective_visibility,
|
|
143
239
|
"run_in_executor": True,
|
|
144
240
|
},
|
|
145
241
|
"get_process_info": get_process_info,
|
|
@@ -153,11 +249,13 @@ async def start_debugger(
|
|
|
153
249
|
}
|
|
154
250
|
|
|
155
251
|
svc_info = await server.register_service(service)
|
|
156
|
-
actual_id = svc_info.get("id",
|
|
252
|
+
actual_id = svc_info.get("id", effective_id) if isinstance(svc_info, dict) else effective_id
|
|
157
253
|
ws = server.config.get("workspace", workspace) if hasattr(server.config, "get") else getattr(server.config, "workspace", workspace)
|
|
158
254
|
|
|
159
|
-
# Generate a token for remote access
|
|
160
|
-
session_token =
|
|
255
|
+
# Generate a token for remote access (24h expiry); skip in no-token mode.
|
|
256
|
+
session_token = ""
|
|
257
|
+
if require_token:
|
|
258
|
+
session_token = await server.generate_token({"expires_in": 86400})
|
|
161
259
|
|
|
162
260
|
# Build the HTTP service URL
|
|
163
261
|
service_url = _build_service_url(server_url, actual_id)
|
|
@@ -168,7 +266,7 @@ async def start_debugger(
|
|
|
168
266
|
ws,
|
|
169
267
|
actual_id,
|
|
170
268
|
)
|
|
171
|
-
_print_session_info(server_url, actual_id, service_url, session_token)
|
|
269
|
+
_print_session_info(server_url, actual_id, service_url, session_token, require_token)
|
|
172
270
|
|
|
173
271
|
return DebugSession(
|
|
174
272
|
service_id=actual_id,
|
|
@@ -186,7 +284,8 @@ def start_debugger_sync(
|
|
|
186
284
|
token: str = "",
|
|
187
285
|
service_id: str = "py-debugger",
|
|
188
286
|
service_name: str = "Python Debugger",
|
|
189
|
-
visibility: str = "
|
|
287
|
+
visibility: str = "",
|
|
288
|
+
require_token: bool = True,
|
|
190
289
|
) -> DebugSession:
|
|
191
290
|
"""Start the Hypha debugger (sync).
|
|
192
291
|
|
|
@@ -201,18 +300,24 @@ def start_debugger_sync(
|
|
|
201
300
|
"""
|
|
202
301
|
from hypha_rpc.sync import connect_to_server
|
|
203
302
|
|
|
303
|
+
# Resolve effective service_id and visibility based on require_token.
|
|
304
|
+
effective_id = service_id
|
|
305
|
+
if not require_token and service_id == "py-debugger":
|
|
306
|
+
effective_id = f"py-debugger-{secrets.token_hex(8)}"
|
|
307
|
+
effective_visibility = visibility or ("protected" if require_token else "unlisted")
|
|
308
|
+
|
|
204
309
|
server = connect_to_server({"server_url": server_url, **({"workspace": workspace} if workspace else {}), **({"token": token} if token else {})})
|
|
205
310
|
|
|
206
311
|
service = {
|
|
207
312
|
"name": service_name,
|
|
208
|
-
"id":
|
|
313
|
+
"id": effective_id,
|
|
209
314
|
"type": "debugger",
|
|
210
315
|
"description": (
|
|
211
316
|
"Remote Python process debugger. Allows inspecting variables, "
|
|
212
317
|
"executing code, browsing files, and getting process information."
|
|
213
318
|
),
|
|
214
319
|
"config": {
|
|
215
|
-
"visibility":
|
|
320
|
+
"visibility": effective_visibility,
|
|
216
321
|
"run_in_executor": True,
|
|
217
322
|
},
|
|
218
323
|
"get_process_info": get_process_info,
|
|
@@ -226,11 +331,13 @@ def start_debugger_sync(
|
|
|
226
331
|
}
|
|
227
332
|
|
|
228
333
|
svc_info = server.register_service(service)
|
|
229
|
-
actual_id = svc_info.get("id",
|
|
334
|
+
actual_id = svc_info.get("id", effective_id) if isinstance(svc_info, dict) else effective_id
|
|
230
335
|
ws = server.config.get("workspace", workspace) if hasattr(server.config, "get") else getattr(server.config, "workspace", workspace)
|
|
231
336
|
|
|
232
|
-
# Generate a token for remote access
|
|
233
|
-
session_token =
|
|
337
|
+
# Generate a token for remote access (24h expiry); skip in no-token mode.
|
|
338
|
+
session_token = ""
|
|
339
|
+
if require_token:
|
|
340
|
+
session_token = server.generate_token({"expires_in": 86400})
|
|
234
341
|
|
|
235
342
|
# Build the HTTP service URL
|
|
236
343
|
service_url = _build_service_url(server_url, actual_id)
|
|
@@ -241,7 +348,7 @@ def start_debugger_sync(
|
|
|
241
348
|
ws,
|
|
242
349
|
actual_id,
|
|
243
350
|
)
|
|
244
|
-
_print_session_info(server_url, actual_id, service_url, session_token)
|
|
351
|
+
_print_session_info(server_url, actual_id, service_url, session_token, require_token)
|
|
245
352
|
|
|
246
353
|
return DebugSession(
|
|
247
354
|
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.4
|
|
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.4"
|
|
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
|