textual-debugger 0.1.2__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.
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/PKG-INFO +181 -31
- {textual_debugger-0.1.2/src/tdb → textual_debugger-0.1.4}/README.md +179 -30
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/pyproject.toml +3 -1
- {textual_debugger-0.1.2 → textual_debugger-0.1.4/src/tdb}/README.md +179 -30
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/__init__.py +1 -1
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/app.py +143 -5
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/app_handlers/dap_events.py +12 -3
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/app_handlers/inspection.py +82 -123
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/app_handlers/ui_panels.py +10 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/cli.py +159 -17
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/dap/client.py +55 -0
- textual_debugger-0.1.4/src/tdb/inspection_full.py +230 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/keybindings/__init__.py +2 -0
- textual_debugger-0.1.4/src/tdb/mcp/__init__.py +10 -0
- textual_debugger-0.1.4/src/tdb/mcp/__main__.py +6 -0
- textual_debugger-0.1.4/src/tdb/mcp/server.py +306 -0
- textual_debugger-0.1.4/src/tdb/mcp/session.py +205 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/server/app.py +14 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/server/handlers.py +156 -105
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/server/runner.py +51 -14
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/session/controller.py +51 -2
- textual_debugger-0.1.4/src/tdb/session/inspect_service.py +265 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/widgets/code_view.py +27 -3
- textual_debugger-0.1.4/src/tdb/widgets/full_contents_modal.py +223 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/widgets/variable_view.py +85 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/textual_debugger.egg-info/PKG-INFO +181 -31
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/textual_debugger.egg-info/SOURCES.txt +7 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/textual_debugger.egg-info/entry_points.txt +1 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/textual_debugger.egg-info/requires.txt +1 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/LICENSE +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/setup.cfg +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/__main__.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/_timeouts.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/app_handlers/__init__.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/app_handlers/routing.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/app_helpers.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/breakpoint_hook.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/dap/__init__.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/dap/messages.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/dap/protocol.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/dap/types.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/inspection.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/persist.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/post_mortem.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/processes_cache.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/server/__init__.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/server/event_handler.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/server/rpc_types.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/session/__init__.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/session/child_processes.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/session/event_bus.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/session/messages.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/session/post_mortem_loader.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/session/state.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/session/statement_stepper.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/session/terminal.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/session/textual_handler.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/source_analysis.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/widgets/__init__.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/widgets/_inspection_modal.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/widgets/async_tasks_modal.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/widgets/breakpoint_view.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/widgets/console_view.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/widgets/evaluate_console.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/widgets/menu_bar.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/widgets/modals.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/widgets/processes_modal.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/widgets/stack_view.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/widgets/status_bar.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/tdb/widgets/threads_modal.py +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/textual_debugger.egg-info/dependency_links.txt +0 -0
- {textual_debugger-0.1.2 → textual_debugger-0.1.4}/src/textual_debugger.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: textual-debugger
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: A TUI Python debugger based on textual and debugpy
|
|
5
5
|
Author-email: Al Danial <al.danial@gmail.com>
|
|
6
6
|
License: textual-debugger is licensed to you under the MIT/X Consortium license:
|
|
@@ -51,6 +51,7 @@ Requires-Dist: debugpy>=1.8.0
|
|
|
51
51
|
Requires-Dist: pygments>=2.17.0
|
|
52
52
|
Requires-Dist: fastapi>=0.115.0
|
|
53
53
|
Requires-Dist: uvicorn>=0.30.0
|
|
54
|
+
Requires-Dist: mcp>=1.28.0
|
|
54
55
|
Provides-Extra: dev
|
|
55
56
|
Requires-Dist: pytest; extra == "dev"
|
|
56
57
|
Requires-Dist: pytest-cov; extra == "dev"
|
|
@@ -88,9 +89,9 @@ It specifically supports modules
|
|
|
88
89
|
- `threading` (with a thread inspector)
|
|
89
90
|
- `multiprocessing` / `concurrent.futures` (with automatic child process attachment and a process inspector)
|
|
90
91
|
|
|
91
|
-
- supports remote attachment to
|
|
92
|
+
- supports remote attachment to debugpy-enabled Python programs
|
|
92
93
|
|
|
93
|
-
- includes a JSON-RPC server mode
|
|
94
|
+
- includes a JSON-RPC server mode that enables programmatic debug control, making it suitable for
|
|
94
95
|
automated, headless debugging workflows and AI-assisted debugging
|
|
95
96
|
|
|
96
97
|
- can spawn the debuggee in an external terminal to enable debugging TUI applications
|
|
@@ -182,10 +183,20 @@ tdb --no-stop-on-entry my_program.py
|
|
|
182
183
|
tdb --terminal xterm my_program.py
|
|
183
184
|
|
|
184
185
|
# attach to a remote Python program that has a debugpy server on port 5678
|
|
185
|
-
|
|
186
|
+
# (source code is automatically downloaded from the remote host)
|
|
187
|
+
tdb -r remotehost:5678
|
|
186
188
|
|
|
187
|
-
#
|
|
188
|
-
#
|
|
189
|
+
# attach to a remote Python program that has a debugpy server on port 5678
|
|
190
|
+
# and set a breakpoint where tdb and the remote program have the same
|
|
191
|
+
# source code layout
|
|
192
|
+
tdb -r remotehost:5678 -k my_program.py:42
|
|
193
|
+
|
|
194
|
+
# attach to a remote Python program that has a debugpy server on port 5678
|
|
195
|
+
# and set a breakpoint where code on the local host is at a different location
|
|
196
|
+
# than code on the remote host
|
|
197
|
+
tdb -r remotehost:5678 --local-root /my/code/dir --remote-root /app -k my_program.py:42
|
|
198
|
+
|
|
199
|
+
# separate tdb arguments from debuggee arguments with `--`
|
|
189
200
|
tdb --python /path/to/venv/bin/python -- my_program.py -k 17 --max 23.3
|
|
190
201
|
```
|
|
191
202
|
|
|
@@ -199,7 +210,7 @@ python -m tdb my_program.py
|
|
|
199
210
|
|
|
200
211
|
```
|
|
201
212
|
┌─ Header ──────────────────────────────────────────────┐
|
|
202
|
-
├─ Menu Bar (File
|
|
213
|
+
├─ Menu Bar (File / Configure / Help)───────────────────┤
|
|
203
214
|
│ │ │
|
|
204
215
|
│ Code View │ Console View (stdout) │
|
|
205
216
|
│ (source + breakpoints) ├───────────────────────────┤
|
|
@@ -270,7 +281,7 @@ Switch from Navigate back to Debug mode with `Escape`.
|
|
|
270
281
|
|
|
271
282
|
> **Note:** Many terminals send the byte sequence `ESC+f` for `Alt+F`, which Textual's
|
|
272
283
|
ANSI parser rewrites to `Ctrl+Right` (the readline "forward-word" convention).
|
|
273
|
-
`tdb` binds both so `Alt+F` works as expected
|
|
284
|
+
`tdb` binds both so `Alt+F` works as expected.
|
|
274
285
|
|
|
275
286
|
### Debugging Controls
|
|
276
287
|
|
|
@@ -288,8 +299,10 @@ those for gdb/pdb, with some aliases and extras thrown in for convenience.
|
|
|
288
299
|
| `u` / `d` | Navigate stack up (caller) / down (callee) |
|
|
289
300
|
| `j` / `k` | Move cursor down / up (with count: `5j`, `10k`) |
|
|
290
301
|
| `G` | Go to last line (with count: `42G` jumps to line 42) |
|
|
302
|
+
| `e` | Re-display the last error (traceback) |
|
|
291
303
|
| `R` | Restart the debug session |
|
|
292
|
-
| `
|
|
304
|
+
| `q q` | Quit |
|
|
305
|
+
| `Ctrl+q` | Quit |
|
|
293
306
|
|
|
294
307
|
> **Note:** `f` ("finish") and `r` ("return") are both aliases for step-out. DAP's only
|
|
295
308
|
"exit-a-function" primitive is `stepOut`, which runs the rest of the current function
|
|
@@ -310,7 +323,7 @@ print(results) # next stop lands here, not on each sub-line above
|
|
|
310
323
|
|
|
311
324
|
lands on `print(results)`, not on each interior sub-line of the `gather` call. Switch to
|
|
312
325
|
**Line** mode (Configure > Step Mode) to get debugpy's native per-line behavior, which
|
|
313
|
-
stops on each physical line
|
|
326
|
+
stops on each physical line--useful for inspecting how a complex expression is built up.
|
|
314
327
|
The choice is saved to `~/.config/tdb/config.json`.
|
|
315
328
|
|
|
316
329
|
### Breakpoints
|
|
@@ -337,6 +350,11 @@ The Variable View shows a tree of scopes (Locals, Globals) with all variables in
|
|
|
337
350
|
frame. Expand nodes to drill into complex objects. Children are loaded lazily on demand.
|
|
338
351
|
Variable values can be changed in the Evaluate Console.
|
|
339
352
|
|
|
353
|
+
Double-click a variable, or highlight the variable with the text cursor in
|
|
354
|
+
the Variables View and press `Enter`
|
|
355
|
+
to display that variable in a modal. This simplifies inspection of
|
|
356
|
+
large or deeply nested data structures.
|
|
357
|
+
|
|
340
358
|
### Call Stack
|
|
341
359
|
|
|
342
360
|
The Stack View shows the full call stack. Click a frame to navigate to its source
|
|
@@ -344,7 +362,8 @@ location and inspect its variables.
|
|
|
344
362
|
|
|
345
363
|
### Evaluate Console
|
|
346
364
|
|
|
347
|
-
A REPL at the bottom
|
|
365
|
+
A read-evaluate-print loop (REPL) at the bottom left permits
|
|
366
|
+
interactive evaluation of expressions in the current scope:
|
|
348
367
|
|
|
349
368
|
```
|
|
350
369
|
>>> len(items)
|
|
@@ -370,8 +389,11 @@ The Console View captures stdout (normal text) and stderr (red text) from the de
|
|
|
370
389
|
in real time.
|
|
371
390
|
|
|
372
391
|
If your program prints a lot, or prompts for input, or uses colors or
|
|
373
|
-
terminal control codes,
|
|
374
|
-
with `--terminal` for
|
|
392
|
+
terminal control codes, run the program in an external terminal
|
|
393
|
+
with `--terminal` for a better experience.
|
|
394
|
+
The `--terminal` switch requires a graphical environment and a compatible
|
|
395
|
+
terminal emulator.
|
|
396
|
+
|
|
375
397
|
|
|
376
398
|
### Crash Detection
|
|
377
399
|
|
|
@@ -381,6 +403,9 @@ When the debuggee raises an unhandled exception, `tdb`:
|
|
|
381
403
|
3. Populates the Stack View with the exception's call stack
|
|
382
404
|
4. Lets you press `R` to restart or `Escape` to dismiss
|
|
383
405
|
|
|
406
|
+
> Note: after dismissing the traceback modal, you
|
|
407
|
+
> can display it again by hitting `e` when focus is in the Code View.
|
|
408
|
+
|
|
384
409
|
### Post-Mortem Exception Hook
|
|
385
410
|
|
|
386
411
|
You can have `tdb` pop open automatically when *any* Python program crashes without the
|
|
@@ -389,7 +414,6 @@ need to launch through `tdb` up front. Install the hook once at the top of your
|
|
|
389
414
|
```python
|
|
390
415
|
import sys
|
|
391
416
|
import tdb
|
|
392
|
-
|
|
393
417
|
sys.excepthook = tdb.exception_hook
|
|
394
418
|
```
|
|
395
419
|
|
|
@@ -407,11 +431,11 @@ In post-mortem mode you can:
|
|
|
407
431
|
- Read the full traceback (including chained `cause`/`context` exceptions) in the Console View
|
|
408
432
|
- Jump around the source with the full Code View (syntax highlighting, goto-line, etc.)
|
|
409
433
|
|
|
410
|
-
Stepping,
|
|
411
|
-
interpreter is gone
|
|
434
|
+
Stepping, continue, breakpoints, restart, and the Evaluate View are disabled. The original
|
|
435
|
+
interpreter is gone since the view is a frozen snapshot. Press `q` to exit.
|
|
412
436
|
|
|
413
437
|
The hook is a no-op when stdin/stdout aren't a tty (e.g. when your program is piped or
|
|
414
|
-
run from cron), so it
|
|
438
|
+
run from cron), so it is safe to leave installed in production code. Snapshots are
|
|
415
439
|
written to a temp file that is deleted as soon as `tdb` exits.
|
|
416
440
|
|
|
417
441
|
Snapshot depth / breadth is capped (5 levels, 50 children per container) to keep the
|
|
@@ -422,7 +446,6 @@ memoization.
|
|
|
422
446
|
|
|
423
447
|
`tdb` has an improved implemenation of the standard `breakpoint()` function (or equivalently,
|
|
424
448
|
`pdb.set_trace()`) used to pause at a specific line to inspect, then
|
|
425
|
-
here=
|
|
426
449
|
continuing--use `tdb.breakpoint()`:
|
|
427
450
|
|
|
428
451
|
```python
|
|
@@ -445,7 +468,7 @@ spawns `python -m tdb -r <port>` as a subprocess so the TUI takes over the termi
|
|
|
445
468
|
and pauses the calling thread at the line that called `tdb.breakpoint()` (the hook
|
|
446
469
|
auto-steps out of its own helper so you land in your own frame, not inside
|
|
447
470
|
`breakpoint_hook.py`). Stepping (`n`/`s`/`o`), `continue`, and setting/removing breakpoints
|
|
448
|
-
all work normally; quitting `tdb` (`Ctrl+
|
|
471
|
+
all work normally; quitting `tdb` (`Ctrl+q`) detaches without killing the program, and
|
|
449
472
|
debugpy auto-resumes any threads still paused.
|
|
450
473
|
|
|
451
474
|
This differs from `tdb.exception_hook` in one way:
|
|
@@ -458,6 +481,12 @@ drive the user's program forward.
|
|
|
458
481
|
As with `exception_hook`, the call is a no-op when stdin/stdout aren't a tty, so it's
|
|
459
482
|
safe to leave in code paths that sometimes run headless.
|
|
460
483
|
|
|
484
|
+
Quitting `tdb` while paused in a `tdb.breakpoint()` session detaches the debugger and
|
|
485
|
+
lets the program continue running normally.
|
|
486
|
+
This behavior matches hitting `c` while in a conventional (that is, the Python
|
|
487
|
+
standard library's) `breakpoint()` session.
|
|
488
|
+
If you want to kill the program instead, use `Ctrl+c` in the terminal running the debuggee.
|
|
489
|
+
|
|
461
490
|
### Async Task Inspector
|
|
462
491
|
|
|
463
492
|
For programs using `asyncio`, the menu bar shows an **Async Tasks (N)** label with the count of
|
|
@@ -467,13 +496,17 @@ active tasks (updated each time the program stops). Click it to open a full-scre
|
|
|
467
496
|
primitive (`Lock.acquire`, `Queue.get`, `asyncio.sleep`, …), and coroutine
|
|
468
497
|
- **Right pane**: detail view with full stack trace and an expandable variable tree (same
|
|
469
498
|
as the main Variables View) for the selected task
|
|
470
|
-
- Press `g` to switch the right pane to the **wait graph**
|
|
499
|
+
- Press `g` to switch the right pane to the **wait graph** which is a tree showing
|
|
500
|
+
each blocked
|
|
471
501
|
task, the asyncio primitive it's parked on, and the task(s) holding that primitive.
|
|
472
502
|
Cycles (deadlocks) are highlighted in red both in the task table and as a "Deadlock
|
|
473
503
|
cycles" section at the top of the graph. Selecting a node in the tree highlights the
|
|
474
504
|
corresponding task in the table.
|
|
475
505
|
- Navigate with arrow keys; press `r` to refresh, `Escape` to close
|
|
476
506
|
|
|
507
|
+
> Note: Async task relationships may be directed acyclic graphs (DAGs) rather than trees
|
|
508
|
+
> but I don't know of a way to visualize DAGs in textual.
|
|
509
|
+
|
|
477
510
|
RPC equivalents:
|
|
478
511
|
|
|
479
512
|
```bash
|
|
@@ -585,6 +618,36 @@ All debugging features (breakpoints, stepping, variable inspection, threads, pro
|
|
|
585
618
|
async tasks) work in remote attach mode. The Code View automatically navigates to the
|
|
586
619
|
source file when the program stops.
|
|
587
620
|
|
|
621
|
+
**Mapping remote paths to local copies (`--local-root` / `--remote-root`):** when the
|
|
622
|
+
debuggee lives on another machine, or in a container, or simply in a different directory
|
|
623
|
+
on the same machine, the source paths it reports (and the paths it expects breakpoints
|
|
624
|
+
to refer to) won't match anything on the `tdb` host. To bridge that gap, give `tdb` one
|
|
625
|
+
or more `--local-root` / `--remote-root` pairs. Each `--local-root` points at a local
|
|
626
|
+
directory containing a copy of the code; each `--remote-root` is the corresponding path
|
|
627
|
+
on the debuggee. The two flags must be supplied in equal numbers and are paired in CLI
|
|
628
|
+
order via `zip()`, so the first `--local-root` matches the first `--remote-root`, the
|
|
629
|
+
second matches the second, and so on. `debugpy` then translates paths bidirectionally:
|
|
630
|
+
breakpoints set on a local file land on the matching remote file, and source paths
|
|
631
|
+
returned in stopped-events / stack-traces are rewritten back to the local copy so the
|
|
632
|
+
Code View loads directly from disk (no DAP `source` round-trip needed).
|
|
633
|
+
|
|
634
|
+
These flags are required whenever you want to set a `-k` breakpoint against a remote
|
|
635
|
+
debuggee whose code lives at a different path than your local copy. For example, if the
|
|
636
|
+
remote runs `program.py` at `/path/to/code/program.py` and your local copy is at
|
|
637
|
+
`/local/project/code/program.py`, set a breakpoint at line 321 with:
|
|
638
|
+
|
|
639
|
+
```bash
|
|
640
|
+
tdb -r RHOST:15678 \
|
|
641
|
+
--local-root /local/project/code \
|
|
642
|
+
--remote-root /path/to/code \
|
|
643
|
+
-k program.py:321
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
With `--local-root` set, a relative `-k FILE:LINE` is resolved by searching each
|
|
647
|
+
`--local-root` directory in CLI order (first match wins); absolute paths still work as
|
|
648
|
+
before. Multiple pairs can be supplied to mirror multiple source trees (e.g. an
|
|
649
|
+
application directory and a shared library directory) in one invocation.
|
|
650
|
+
|
|
588
651
|
### External Terminal Support
|
|
589
652
|
|
|
590
653
|
Some Python programs, notably text user interfaces, use terminal control
|
|
@@ -676,11 +739,12 @@ curl -s -X POST http://127.0.0.1:8150/rpc \
|
|
|
676
739
|
| `set_breakpoint` | `["file:line"]` or `["file:line", "condition", "hit_condition"]` | Set a breakpoint |
|
|
677
740
|
| `remove_breakpoint` | `["file:line"]` | Remove a breakpoint |
|
|
678
741
|
| `list_breakpoints` | `[]` | Show all breakpoints |
|
|
679
|
-
| `continue` | `[]` | Resume execution |
|
|
680
|
-
| `next` | `[]` | Step over |
|
|
681
|
-
| `step_in` | `[]` | Step into |
|
|
682
|
-
| `step_out` | `[]` | Step out |
|
|
683
|
-
| `pause` | `[]` | Pause execution |
|
|
742
|
+
| `continue` | `[]` or `[timeout_s]` | Resume execution; on timeout returns `"still running — call pause or wait again"` (success) |
|
|
743
|
+
| `next` | `[]` or `[timeout_s]` | Step over |
|
|
744
|
+
| `step_in` | `[]` or `[timeout_s]` | Step into |
|
|
745
|
+
| `step_out` | `[]` or `[timeout_s]` | Step out |
|
|
746
|
+
| `pause` | `[]` | Pause execution; bypasses the dispatch lock so it can interrupt an in-flight blocking action |
|
|
747
|
+
| `wait_for_stop` | `[]` or `[timeout_s]` | Wait for the next stop without issuing a step (use after `continue` returns `"still running"` to keep waiting) |
|
|
684
748
|
| `inspect` | `["expr1", "expr2", ...]` | Evaluate multiple expressions |
|
|
685
749
|
| `evaluate` | `["expression"]` | Evaluate a single expression |
|
|
686
750
|
| `stack_up` | `[]` | Move up the call stack |
|
|
@@ -709,25 +773,111 @@ curl -N http://127.0.0.1:8150/events
|
|
|
709
773
|
Events: `initialized`, `stopped`, `continued`, `terminated`, `exited`, `output`.
|
|
710
774
|
Each is JSON with `event`, `data`, and `timestamp` fields.
|
|
711
775
|
|
|
776
|
+
## MCP Integration
|
|
777
|
+
|
|
778
|
+
tdb ships a Model Context Protocol (MCP) server (`tdb-mcp`) that exposes
|
|
779
|
+
the debugger as a curated set of tools an AI agent can call. The MCP
|
|
780
|
+
server is a third in-process consumer of the same dispatch handlers the
|
|
781
|
+
TUI and the HTTP server use — so an agent gets the same lock semantics,
|
|
782
|
+
including the pause-during-continue bypass, and the same DAP-backed
|
|
783
|
+
inspection surface.
|
|
784
|
+
|
|
785
|
+
For a worked end-to-end example (prompting an agent to find two
|
|
786
|
+
runtime bugs in a sample program) see
|
|
787
|
+
[docs/tutorial-mcp-debugging.md](docs/tutorial-mcp-debugging.md) and
|
|
788
|
+
its companion `examples/sales_report_buggy.py`.
|
|
789
|
+
|
|
790
|
+
### Running the MCP server
|
|
791
|
+
|
|
792
|
+
Configure your MCP client (Claude Desktop, an IDE extension, etc.) to
|
|
793
|
+
launch `tdb-mcp` over stdio. Example `claude_desktop_config.json`:
|
|
794
|
+
|
|
795
|
+
```json
|
|
796
|
+
{
|
|
797
|
+
"mcpServers": {
|
|
798
|
+
"tdb": {
|
|
799
|
+
"command": "tdb-mcp"
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
```
|
|
804
|
+
|
|
805
|
+
Three equivalent invocation forms: `tdb-mcp` (the dedicated entry
|
|
806
|
+
point), `tdb --mcp` (the main CLI with the `--mcp` switch), and
|
|
807
|
+
`python -m tdb.mcp` (module form). Pick whichever matches how your MCP
|
|
808
|
+
client expects to launch servers.
|
|
809
|
+
|
|
810
|
+
### Tool surface (16 tools, curated)
|
|
811
|
+
|
|
812
|
+
| Cluster | Tools |
|
|
813
|
+
|---------|-------|
|
|
814
|
+
| Lifecycle | `debug_launch`, `debug_attach`, `quit` |
|
|
815
|
+
| Control | `control(action, timeout_s=30)` — `action ∈ {continue, next, step_in, step_out, pause, wait_for_stop}` |
|
|
816
|
+
| Inspection | `inspect(expressions)`, `read_source(file_path)`, `stack_trace()`, `status()`, `get_output()` |
|
|
817
|
+
| Breakpoints | `set_breakpoint(spec, condition?, hit_condition?)`, `remove_breakpoint(spec)`, `list_breakpoints()` |
|
|
818
|
+
| Differentiators | `threads(thread_id?)`, `tasks(task_name?)`, `processes(name_or_pid?)`, `wait_graph()` |
|
|
819
|
+
|
|
820
|
+
`control` is intentionally one tool that takes an action enum — the six
|
|
821
|
+
underlying RPC actions share a return shape, and agents perform
|
|
822
|
+
measurably better with a small surface than with one tool per action.
|
|
823
|
+
`threads` / `tasks` / `processes` overload list-vs-inspect via a
|
|
824
|
+
single optional argument for the same reason.
|
|
825
|
+
|
|
826
|
+
### Agent flow for a long-running step
|
|
827
|
+
|
|
828
|
+
```
|
|
829
|
+
agent → control(action="continue", timeout_s=30)
|
|
830
|
+
mcp → "still running — call pause or wait again"
|
|
831
|
+
agent → control(action="pause") # OR: control(action="wait_for_stop", timeout_s=30)
|
|
832
|
+
mcp → "<file>:<line>"
|
|
833
|
+
agent → inspect(["x", "len(buf)"])
|
|
834
|
+
mcp → "x = 7\nlen(buf) = 1024"
|
|
835
|
+
```
|
|
836
|
+
|
|
837
|
+
`pause` bypasses the dispatch lock so it can interrupt a `continue`
|
|
838
|
+
that's still mid-flight (HTTP and MCP share the same `NO_LOCK_ACTIONS`
|
|
839
|
+
policy — see `tdb/server/app.py`).
|
|
840
|
+
|
|
841
|
+
### Security caveat
|
|
842
|
+
|
|
843
|
+
`inspect` calls debugpy's `evaluate`, which is **arbitrary Python
|
|
844
|
+
execution in the debuggee process**. This is inherent to a debugger and
|
|
845
|
+
not a tdb-specific concern, but MCP clients (and the humans running
|
|
846
|
+
them) should apply appropriate permission models: don't auto-approve
|
|
847
|
+
`inspect` against untrusted expressions, and don't expose `tdb-mcp` on
|
|
848
|
+
a network — stdio transport only by design.
|
|
849
|
+
|
|
850
|
+
### Deferred / out of scope (v1)
|
|
851
|
+
|
|
852
|
+
- SSE-style event push — `control` and `wait_for_stop` make polling
|
|
853
|
+
efficient enough; events would also need uneven MCP-client support.
|
|
854
|
+
- HTTP / streamable-HTTP transports — would require auth (which the
|
|
855
|
+
HTTP RPC server also currently lacks); stdio inherits the trust of
|
|
856
|
+
the process that spawned it.
|
|
857
|
+
- Multi-session — one debug session per MCP process.
|
|
858
|
+
|
|
712
859
|
## CLI Reference
|
|
713
860
|
|
|
714
861
|
```
|
|
715
|
-
usage: tdb [-h] [-v
|
|
716
|
-
[--no-just-my-code] [--no-subprocess] [--python PYTHON]
|
|
717
|
-
[
|
|
862
|
+
usage: tdb [-h] [-v] [-r [HOST:]PORT] [--cwd CWD] [--no-stop-on-entry]
|
|
863
|
+
[--no-just-my-code] [--no-subprocess] [--python PYTHON] [--pv]
|
|
864
|
+
[--keybindings {default,vim,emacs}]
|
|
718
865
|
[--terminal {xterm,konsole,gnome-terminal,ghostty,kitty,iterm2,warp,wezterm,terminator}]
|
|
719
|
-
[--
|
|
866
|
+
[--local-root PATH] [--remote-root PATH]
|
|
867
|
+
[--server] [--headless] [-k FILE:LINE|LINE] [--server-port SERVER_PORT] [-d] [--doc-text]
|
|
720
868
|
[program] [args ...]
|
|
721
|
-
|
|
722
869
|
```
|
|
723
870
|
|
|
724
871
|
| Flag | Description |
|
|
725
872
|
|------|-------------|
|
|
726
873
|
| `-r HOST:PORT` | Attach to a remote debugpy server |
|
|
874
|
+
| `--local-root PATH` | Local directory containing a copy of remote code (repeat to mirror multiple trees). Pair with `--remote-root`; counts must match. Required when `-k` sets a breakpoint against a remote debuggee whose code lives at a different path. |
|
|
875
|
+
| `--remote-root PATH` | Remote directory matched to `--local-root` (same CLI position via `zip()`). |
|
|
727
876
|
| `-k`, `--breakpoint FILE:LINE|LINE` | Set a breakpoint (may be repeated). Passing `-k` implies `--no-stop-on-entry` so the program runs straight to the first breakpoint. |
|
|
728
877
|
| `--no-stop-on-entry` | Do not pause at the first line (default: stop on entry; automatic when `-k` is given) |
|
|
729
878
|
| `--cwd DIR` | Working directory for the debuggee |
|
|
730
879
|
| `--python PATH` | Python interpreter for the debuggee |
|
|
880
|
+
| `--pv` | Shorthand for --python .venv/bin/python |
|
|
731
881
|
| `--no-just-my-code` | Step into stdlib/site-packages code instead of skipping it
|
|
732
882
|
(default: skipped). On uncaught exceptions, the crash modal always shows the full traceback
|
|
733
883
|
including library frames, regardless of this flag. |
|
|
@@ -759,7 +909,7 @@ into) handle multi-line source statements:
|
|
|
759
909
|
| Value | Behavior |
|
|
760
910
|
|-------|----------|
|
|
761
911
|
| `"statement"` (default) | A multi-line statement (e.g. a `gather(...)` call spanning five lines) is one step. The debugger keeps issuing DAP steps until execution leaves the statement, then stops on the next logical line. |
|
|
762
|
-
| `"line"` | debugpy's native per-line behavior
|
|
912
|
+
| `"line"` | debugpy's native per-line behavior (stops on every physical line, including each interior sub-line of a multi-line expression)|
|
|
763
913
|
|
|
764
914
|
Change it from the menu (**Configure > Step Mode**); the choice is saved immediately and
|
|
765
915
|
applies to all future sessions. Breakpoint hits, exceptions, and pauses always interrupt
|