textual-debugger 0.2.0__tar.gz → 0.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/PKG-INFO +126 -9
  2. {textual_debugger-0.2.0/src/tdb → textual_debugger-0.2.2}/README.md +124 -7
  3. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/pyproject.toml +2 -2
  4. {textual_debugger-0.2.0 → textual_debugger-0.2.2/src/tdb}/README.md +124 -7
  5. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/tdb/__init__.py +1 -1
  6. textual_debugger-0.2.2/src/tdb/adapters/perl/Devel/TdbRemote.pm +93 -0
  7. textual_debugger-0.2.2/src/tdb/adapters/perl/__main__.py +23 -0
  8. textual_debugger-0.2.2/src/tdb/adapters/perl/helpers.pl +384 -0
  9. textual_debugger-0.2.2/src/tdb/adapters/perl/protocol.py +110 -0
  10. textual_debugger-0.2.2/src/tdb/adapters/perl/refs.py +34 -0
  11. textual_debugger-0.2.2/src/tdb/adapters/perl/server.py +593 -0
  12. textual_debugger-0.2.2/src/tdb/adapters/perl/session.py +247 -0
  13. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/tdb/app.py +168 -14
  14. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/tdb/cli.py +157 -57
  15. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/tdb/dap/messages.py +20 -0
  16. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/tdb/dap/types.py +3 -0
  17. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/tdb/languages/base.py +5 -0
  18. textual_debugger-0.2.2/src/tdb/languages/perl.py +98 -0
  19. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/tdb/languages/registry.py +14 -1
  20. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/tdb/mcp/session.py +3 -6
  21. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/tdb/persist.py +12 -10
  22. textual_debugger-0.2.2/src/tdb/replay.py +202 -0
  23. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/tdb/server/handlers.py +14 -1
  24. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/tdb/server/runner.py +53 -18
  25. textual_debugger-0.2.2/src/tdb/session/__init__.py +0 -0
  26. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/tdb/session/controller.py +4 -1
  27. textual_debugger-0.2.2/src/tdb/session/recorder.py +106 -0
  28. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/tdb/session/state.py +15 -0
  29. textual_debugger-0.2.2/src/tdb/widgets/__init__.py +0 -0
  30. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/tdb/widgets/code_view.py +88 -51
  31. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/textual_debugger.egg-info/PKG-INFO +126 -9
  32. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/textual_debugger.egg-info/SOURCES.txt +12 -0
  33. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/textual_debugger.egg-info/requires.txt +1 -1
  34. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/LICENSE +0 -0
  35. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/setup.cfg +0 -0
  36. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/tdb/__main__.py +0 -0
  37. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/tdb/_timeouts.py +0 -0
  38. {textual_debugger-0.2.0/src/tdb/dap → textual_debugger-0.2.2/src/tdb/adapters}/__init__.py +0 -0
  39. {textual_debugger-0.2.0/src/tdb/server → textual_debugger-0.2.2/src/tdb/adapters/perl}/__init__.py +0 -0
  40. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/tdb/app_handlers/__init__.py +0 -0
  41. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/tdb/app_handlers/dap_events.py +0 -0
  42. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/tdb/app_handlers/inspection.py +0 -0
  43. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/tdb/app_handlers/routing.py +0 -0
  44. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/tdb/app_handlers/ui_panels.py +0 -0
  45. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/tdb/app_helpers.py +0 -0
  46. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/tdb/breakpoint_hook.py +0 -0
  47. {textual_debugger-0.2.0/src/tdb/session → textual_debugger-0.2.2/src/tdb/dap}/__init__.py +0 -0
  48. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/tdb/dap/client.py +0 -0
  49. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/tdb/dap/protocol.py +0 -0
  50. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/tdb/inspection.py +0 -0
  51. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/tdb/inspection_full.py +0 -0
  52. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/tdb/keybindings/__init__.py +0 -0
  53. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/tdb/languages/__init__.py +0 -0
  54. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/tdb/languages/cpp.py +0 -0
  55. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/tdb/languages/python.py +0 -0
  56. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/tdb/mcp/__init__.py +0 -0
  57. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/tdb/mcp/__main__.py +0 -0
  58. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/tdb/mcp/server.py +0 -0
  59. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/tdb/post_mortem.py +0 -0
  60. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/tdb/processes_cache.py +0 -0
  61. {textual_debugger-0.2.0/src/tdb/widgets → textual_debugger-0.2.2/src/tdb/server}/__init__.py +0 -0
  62. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/tdb/server/app.py +0 -0
  63. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/tdb/server/event_handler.py +0 -0
  64. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/tdb/server/rpc_types.py +0 -0
  65. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/tdb/session/child_processes.py +0 -0
  66. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/tdb/session/event_bus.py +0 -0
  67. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/tdb/session/inspect_service.py +0 -0
  68. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/tdb/session/messages.py +0 -0
  69. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/tdb/session/post_mortem_loader.py +0 -0
  70. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/tdb/session/statement_stepper.py +0 -0
  71. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/tdb/session/terminal.py +0 -0
  72. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/tdb/session/textual_handler.py +0 -0
  73. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/tdb/source_analysis.py +0 -0
  74. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/tdb/widgets/_inspection_modal.py +0 -0
  75. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/tdb/widgets/async_tasks_modal.py +0 -0
  76. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/tdb/widgets/breakpoint_view.py +0 -0
  77. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/tdb/widgets/console_view.py +0 -0
  78. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/tdb/widgets/evaluate_console.py +0 -0
  79. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/tdb/widgets/full_contents_modal.py +0 -0
  80. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/tdb/widgets/menu_bar.py +0 -0
  81. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/tdb/widgets/modals.py +0 -0
  82. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/tdb/widgets/processes_modal.py +0 -0
  83. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/tdb/widgets/stack_view.py +0 -0
  84. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/tdb/widgets/status_bar.py +0 -0
  85. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/tdb/widgets/threads_modal.py +0 -0
  86. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/tdb/widgets/variable_view.py +0 -0
  87. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/textual_debugger.egg-info/dependency_links.txt +0 -0
  88. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/src/textual_debugger.egg-info/entry_points.txt +0 -0
  89. {textual_debugger-0.2.0 → textual_debugger-0.2.2}/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.2.0
3
+ Version: 0.2.2
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,7 +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
+ Requires-Dist: mcp<2.0.0,>=1.28.0
55
55
  Provides-Extra: dev
56
56
  Requires-Dist: pytest; extra == "dev"
57
57
  Requires-Dist: pytest-cov; extra == "dev"
@@ -194,6 +194,10 @@ tdb --lang python ./mytool
194
194
  # program runs to the first breakpoint)
195
195
  tdb -k 20 -k 35 -k module.py:14 my_program.py arg1 arg2
196
196
 
197
+ # run straight to line 20 without saving the breakpoint for future
198
+ # sessions (-t is -k minus the persistence)
199
+ tdb -t 20 my_program.py
200
+
197
201
  # use a specific virtualenv
198
202
  tdb --python /path/to/venv/bin/python my_program.py
199
203
 
@@ -239,14 +243,15 @@ languages are supported out of the box:
239
243
  |----------|------------|------------------------|---------------|
240
244
  | Python | `debugpy` (default) | installed with `textual-debugger` | everything in this README |
241
245
  | C / C++ (any native binary) | `gdb` (default), `lldb-dap` (alternate) | `gdb -i dap` requires GDB ≥ 14; `lldb-dap` ships with LLVM ≥ 17 (e.g. `apt install lldb`) | core debugging: breakpoints, stepping, stack, variables, evaluate console |
246
+ | Perl | perl-tdb (bundled) | needs perl ≥ 5.18 on PATH (or `{"adapters": {"perl": ...}}`) | core debugging + remote attach |
242
247
 
243
248
  ### Language detection and selection
244
249
 
245
250
  The language is auto-detected from the debug target:
246
251
 
247
- 1. File extension: `.py` → Python.
252
+ 1. File extension: `.py` → Python; `.pl` / `.pm` / `.t` → Perl.
248
253
  2. Native executables (ELF, Mach-O, PE magic bytes) → C/C++.
249
- 3. A `#!...python` shebang → Python.
254
+ 3. A `#!...python` or `#!...perl` shebang → Python / Perl respectively.
250
255
  4. C/C++/Rust *source* files (`.c`, `.cpp`, `.rs`, …) produce an error with a
251
256
  hint: compile with debug info (`g++ -g -O0`) and debug the binary.
252
257
  5. Anything else produces an error naming the `--lang` override.
@@ -282,9 +287,11 @@ Python-specific features are hidden or return a clear "not supported for this
282
287
  language" message when debugging other languages: statement-granularity
283
288
  stepping (non-Python languages always step per line), the async task /
284
289
  process inspectors and wait graph, the evaluate console's trailing-`?` help,
285
- remote attach (`-r`), `--python`/`--pv`, `--no-subprocess`,
286
- automatic child-process attachment, and the post-mortem / `tdb.breakpoint()`
287
- hooks (those hooks live inside Python programs by nature). `--terminal` is
290
+ `--python`/`--pv`, `--no-subprocess`, automatic child-process attachment, and
291
+ the post-mortem / `tdb.breakpoint()` hooks (those hooks live inside Python
292
+ programs by nature). Remote attach (`-r`) also works for Perl (see
293
+ [Perl](#perl) — `Devel::TdbRemote` in place of `debugpy.listen()`), but not
294
+ for C/C++. `--terminal` is
288
295
  currently ignored for non-Python targets.
289
296
 
290
297
  ### C/C++ tips
@@ -303,6 +310,72 @@ currently ignored for non-Python targets.
303
310
  `print x` rather than bare `x` (bare `x` collides with GDB's
304
311
  examine-memory command). `lldb-dap` evaluates bare expressions directly.
305
312
 
313
+ ### Perl
314
+
315
+ `tdb` bundles its own Perl adapter (`perl-tdb`) — no separate adapter install
316
+ needed, just a `perl` ≥ 5.18 on `PATH`. It drives stock `perl5db` under the
317
+ hood, so it works with any Perl already on the system.
318
+
319
+ **Launching a script:**
320
+
321
+ ```bash
322
+ tdb script.pl
323
+ ```
324
+
325
+ **Remote attach:** useful when the Perl process is already running (a long-
326
+ lived service, a process started by something other than `tdb`) or lives on
327
+ another host/container. Add three lines to the target program, with the
328
+ `use` line first so the debugger is armed before any of your code compiles:
329
+
330
+ ```perl
331
+ use Devel::TdbRemote; # FIRST line of your program
332
+ ...
333
+ Devel::TdbRemote::listen(5678); # non-blocking
334
+ Devel::TdbRemote::wait_for_client(); # blocks until tdb connects
335
+ ```
336
+
337
+ Then attach from `tdb`, forcing the language since there's no local `program`
338
+ argument for `tdb` to detect it from:
339
+
340
+ ```bash
341
+ tdb --lang perl -r host:5678
342
+ ```
343
+
344
+ **Arming caveat:** only code *compiled after* the debugger is armed can be
345
+ stepped into or breakpointed. That's why `use Devel::TdbRemote;` must be the
346
+ first line of the program. If you can't edit the first line (e.g. a wrapper
347
+ script controls startup), arm it before Perl even parses your file instead:
348
+ `perl -d:TdbRemote prog.pl`, or set `PERL5OPT=-d:TdbRemote` in the
349
+ environment that launches the debuggee.
350
+
351
+ **Copying the adapter to a remote host:** `Devel::TdbRemote` and its helper
352
+ script are plain files, not a CPAN install — copy both onto the remote
353
+ machine and point `PERL5LIB` at the directory that contains them:
354
+
355
+ ```bash
356
+ # From a checkout or an installed wheel's site-packages/tdb/adapters/perl:
357
+ scp -r Devel/TdbRemote.pm helpers.pl remote-host:/opt/tdb-perl/
358
+ # On the remote host:
359
+ export PERL5LIB=/opt/tdb-perl:$PERL5LIB
360
+ ```
361
+
362
+ (`Devel/TdbRemote.pm` locates `helpers.pl` next to itself at runtime, so keep
363
+ the two files in the same relative layout shown above — `helpers.pl` is a
364
+ sibling of the `Devel/` directory, not inside it.)
365
+
366
+ **PadWalker (optional but recommended):** inspecting lexical (`my`)
367
+ variables in the *current* frame always works. Lexicals in outer/caller
368
+ frames need the `PadWalker` module installed on the debuggee's Perl; without
369
+ it, tdb falls back to a read-only pad walk that can't reach fully into
370
+ enclosing scopes, and outer-frame variable listings degrade accordingly.
371
+ Install with `cpanm PadWalker` (or your distro's package) for full fidelity.
372
+
373
+ **Pause is unavailable in attach mode.** Launch-mode sessions (`tdb
374
+ script.pl`) support pausing a running program at any time. Remote-attach
375
+ sessions don't — debugpy-style asynchronous pause needs a control channel
376
+ `Devel::TdbRemote` doesn't implement yet; `pause` in attach mode returns a
377
+ clear "not available" error instead of hanging.
378
+
306
379
  ## Layout
307
380
 
308
381
  ```
@@ -893,6 +966,40 @@ curl -N http://127.0.0.1:8150/events
893
966
  Events: `initialized`, `stopped`, `continued`, `terminated`, `exited`, `output`.
894
967
  Each is JSON with `event`, `data`, and `timestamp` fields.
895
968
 
969
+ ## Recording and replaying sessions
970
+
971
+ `tdb --record session.jsonl prog.py` runs a normal TUI session and captures
972
+ your debugging actions — breakpoints (including `-k`/`-t` and persisted
973
+ ones), stepping, continue/pause, Evaluate-console entries, stack-frame
974
+ navigation, variable expansion, restart, quit — to `session.jsonl` as
975
+ JSON-RPC commands. Works with launch mode (any language) and `-r`
976
+ remote attach.
977
+
978
+ Replay it two ways:
979
+
980
+ - `tdb --replay session.jsonl` — one command: launches the recorded
981
+ program headless, feeds every recorded command through the same RPC
982
+ dispatch `tdb --server` uses, and prints a transcript (recorded time,
983
+ command, verbatim result, interleaved program output). Exit code 0 iff
984
+ every command succeeded. Add `--timing` to reproduce the original
985
+ pacing, `--replay-timeout S` to bound each stop-wait (default 30 s).
986
+ - Against a live server: start `tdb --server prog.py`, then feed line 2
987
+ onward of the file to `POST /rpc` — each line is already a valid
988
+ request body:
989
+
990
+ tail -n +2 session.jsonl | while read line; do
991
+ curl -s -X POST -H 'Content-Type: application/json' \
992
+ -d "$line" http://127.0.0.1:8150/rpc
993
+ done
994
+
995
+ (On Windows, an equivalent loop in Python: read the file, skip the
996
+ first line, `requests.post` each remaining line.)
997
+
998
+ Not captured: pure viewing (scrolling, search, modals, thread/task
999
+ lists), breakpoint enable/disable toggles, variable expansions when the
1000
+ adapter reports no `evaluateName` (currently the Perl adapter), and
1001
+ File > Open program switches.
1002
+
896
1003
  ## MCP Integration
897
1004
 
898
1005
  tdb ships a Model Context Protocol (MCP) server (`tdb-mcp`) that exposes
@@ -1002,11 +1109,12 @@ usage: tdb [-h] [-v] [-r [HOST:]PORT] [--cwd CWD] [--no-stop-on-entry]
1002
1109
  | `--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. |
1003
1110
  | `--remote-root PATH` | Remote directory matched to `--local-root` (same CLI position via `zip()`). |
1004
1111
  | `-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. |
1112
+ | `-t`, `--to-line FILE:LINE|LINE` | Like `-k`, but the breakpoint is not saved to the breakpoints file — it just takes you to that spot in the code for this session (may be repeated). |
1005
1113
  | `--no-stop-on-entry` | Do not pause at the first line (default: stop on entry; automatic when `-k` is given) |
1006
1114
  | `--cwd DIR` | Working directory for the debuggee |
1007
1115
  | `--python PATH` | Python interpreter for the debuggee (Python targets only) |
1008
1116
  | `--pv` | Shorthand for --python .venv/bin/python |
1009
- | `--lang LANGUAGE` | Debuggee language (`python`, `cpp`); default: auto-detect from the target |
1117
+ | `--lang LANGUAGE` | Debuggee language (`python`, `cpp`, `perl`); default: auto-detect from the target |
1010
1118
  | `--adapter ADAPTER` | Debug adapter within the language (e.g. `--lang cpp --adapter lldb-dap`); default: the language's standard adapter |
1011
1119
  | `--no-just-my-code` | Step into stdlib/site-packages code instead of skipping it
1012
1120
  (default: skipped). On uncaught exceptions, the crash modal always shows the full traceback
@@ -1035,8 +1143,17 @@ executable path (`{"adapters": {"lldb-dap": "/opt/llvm/bin/lldb-dap"}}`), and
1035
1143
  `default_adapters` picks a language's default adapter
1036
1144
  (`{"default_adapters": {"cpp": "lldb-dap"}}`).
1037
1145
 
1146
+ **Perl is a special case:** `perl-tdb` is tdb's own bundled adapter (always
1147
+ found — it's Python code, not an external executable), so
1148
+ `adapters.perl` doesn't select an adapter binary. Instead it names the
1149
+ *Perl interpreter* tdb should spawn to run the debuggee:
1150
+ `{"adapters": {"perl": "/path/to/perl"}}`. Use this when the `perl` on
1151
+ `PATH` is too old (< 5.18) or you need a specific `perlbrew`/`plenv` version.
1152
+
1038
1153
  Breakpoints are saved on exit and restored when debugging a program in the same
1039
- directory. Each project's breakpoints are independent.
1154
+ directory. Each project's breakpoints are independent. Breakpoints set with
1155
+ `-t`/`--to-line` are the exception: they behave like `-k` breakpoints during
1156
+ the session but are never saved.
1040
1157
 
1041
1158
  **Step mode** (`step_mode` in `config.json`) controls how `n` (step over) and `s` (step
1042
1159
  into) handle multi-line source statements:
@@ -128,6 +128,10 @@ tdb --lang python ./mytool
128
128
  # program runs to the first breakpoint)
129
129
  tdb -k 20 -k 35 -k module.py:14 my_program.py arg1 arg2
130
130
 
131
+ # run straight to line 20 without saving the breakpoint for future
132
+ # sessions (-t is -k minus the persistence)
133
+ tdb -t 20 my_program.py
134
+
131
135
  # use a specific virtualenv
132
136
  tdb --python /path/to/venv/bin/python my_program.py
133
137
 
@@ -173,14 +177,15 @@ languages are supported out of the box:
173
177
  |----------|------------|------------------------|---------------|
174
178
  | Python | `debugpy` (default) | installed with `textual-debugger` | everything in this README |
175
179
  | C / C++ (any native binary) | `gdb` (default), `lldb-dap` (alternate) | `gdb -i dap` requires GDB ≥ 14; `lldb-dap` ships with LLVM ≥ 17 (e.g. `apt install lldb`) | core debugging: breakpoints, stepping, stack, variables, evaluate console |
180
+ | Perl | perl-tdb (bundled) | needs perl ≥ 5.18 on PATH (or `{"adapters": {"perl": ...}}`) | core debugging + remote attach |
176
181
 
177
182
  ### Language detection and selection
178
183
 
179
184
  The language is auto-detected from the debug target:
180
185
 
181
- 1. File extension: `.py` → Python.
186
+ 1. File extension: `.py` → Python; `.pl` / `.pm` / `.t` → Perl.
182
187
  2. Native executables (ELF, Mach-O, PE magic bytes) → C/C++.
183
- 3. A `#!...python` shebang → Python.
188
+ 3. A `#!...python` or `#!...perl` shebang → Python / Perl respectively.
184
189
  4. C/C++/Rust *source* files (`.c`, `.cpp`, `.rs`, …) produce an error with a
185
190
  hint: compile with debug info (`g++ -g -O0`) and debug the binary.
186
191
  5. Anything else produces an error naming the `--lang` override.
@@ -216,9 +221,11 @@ Python-specific features are hidden or return a clear "not supported for this
216
221
  language" message when debugging other languages: statement-granularity
217
222
  stepping (non-Python languages always step per line), the async task /
218
223
  process inspectors and wait graph, the evaluate console's trailing-`?` help,
219
- remote attach (`-r`), `--python`/`--pv`, `--no-subprocess`,
220
- automatic child-process attachment, and the post-mortem / `tdb.breakpoint()`
221
- hooks (those hooks live inside Python programs by nature). `--terminal` is
224
+ `--python`/`--pv`, `--no-subprocess`, automatic child-process attachment, and
225
+ the post-mortem / `tdb.breakpoint()` hooks (those hooks live inside Python
226
+ programs by nature). Remote attach (`-r`) also works for Perl (see
227
+ [Perl](#perl) — `Devel::TdbRemote` in place of `debugpy.listen()`), but not
228
+ for C/C++. `--terminal` is
222
229
  currently ignored for non-Python targets.
223
230
 
224
231
  ### C/C++ tips
@@ -237,6 +244,72 @@ currently ignored for non-Python targets.
237
244
  `print x` rather than bare `x` (bare `x` collides with GDB's
238
245
  examine-memory command). `lldb-dap` evaluates bare expressions directly.
239
246
 
247
+ ### Perl
248
+
249
+ `tdb` bundles its own Perl adapter (`perl-tdb`) — no separate adapter install
250
+ needed, just a `perl` ≥ 5.18 on `PATH`. It drives stock `perl5db` under the
251
+ hood, so it works with any Perl already on the system.
252
+
253
+ **Launching a script:**
254
+
255
+ ```bash
256
+ tdb script.pl
257
+ ```
258
+
259
+ **Remote attach:** useful when the Perl process is already running (a long-
260
+ lived service, a process started by something other than `tdb`) or lives on
261
+ another host/container. Add three lines to the target program, with the
262
+ `use` line first so the debugger is armed before any of your code compiles:
263
+
264
+ ```perl
265
+ use Devel::TdbRemote; # FIRST line of your program
266
+ ...
267
+ Devel::TdbRemote::listen(5678); # non-blocking
268
+ Devel::TdbRemote::wait_for_client(); # blocks until tdb connects
269
+ ```
270
+
271
+ Then attach from `tdb`, forcing the language since there's no local `program`
272
+ argument for `tdb` to detect it from:
273
+
274
+ ```bash
275
+ tdb --lang perl -r host:5678
276
+ ```
277
+
278
+ **Arming caveat:** only code *compiled after* the debugger is armed can be
279
+ stepped into or breakpointed. That's why `use Devel::TdbRemote;` must be the
280
+ first line of the program. If you can't edit the first line (e.g. a wrapper
281
+ script controls startup), arm it before Perl even parses your file instead:
282
+ `perl -d:TdbRemote prog.pl`, or set `PERL5OPT=-d:TdbRemote` in the
283
+ environment that launches the debuggee.
284
+
285
+ **Copying the adapter to a remote host:** `Devel::TdbRemote` and its helper
286
+ script are plain files, not a CPAN install — copy both onto the remote
287
+ machine and point `PERL5LIB` at the directory that contains them:
288
+
289
+ ```bash
290
+ # From a checkout or an installed wheel's site-packages/tdb/adapters/perl:
291
+ scp -r Devel/TdbRemote.pm helpers.pl remote-host:/opt/tdb-perl/
292
+ # On the remote host:
293
+ export PERL5LIB=/opt/tdb-perl:$PERL5LIB
294
+ ```
295
+
296
+ (`Devel/TdbRemote.pm` locates `helpers.pl` next to itself at runtime, so keep
297
+ the two files in the same relative layout shown above — `helpers.pl` is a
298
+ sibling of the `Devel/` directory, not inside it.)
299
+
300
+ **PadWalker (optional but recommended):** inspecting lexical (`my`)
301
+ variables in the *current* frame always works. Lexicals in outer/caller
302
+ frames need the `PadWalker` module installed on the debuggee's Perl; without
303
+ it, tdb falls back to a read-only pad walk that can't reach fully into
304
+ enclosing scopes, and outer-frame variable listings degrade accordingly.
305
+ Install with `cpanm PadWalker` (or your distro's package) for full fidelity.
306
+
307
+ **Pause is unavailable in attach mode.** Launch-mode sessions (`tdb
308
+ script.pl`) support pausing a running program at any time. Remote-attach
309
+ sessions don't — debugpy-style asynchronous pause needs a control channel
310
+ `Devel::TdbRemote` doesn't implement yet; `pause` in attach mode returns a
311
+ clear "not available" error instead of hanging.
312
+
240
313
  ## Layout
241
314
 
242
315
  ```
@@ -827,6 +900,40 @@ curl -N http://127.0.0.1:8150/events
827
900
  Events: `initialized`, `stopped`, `continued`, `terminated`, `exited`, `output`.
828
901
  Each is JSON with `event`, `data`, and `timestamp` fields.
829
902
 
903
+ ## Recording and replaying sessions
904
+
905
+ `tdb --record session.jsonl prog.py` runs a normal TUI session and captures
906
+ your debugging actions — breakpoints (including `-k`/`-t` and persisted
907
+ ones), stepping, continue/pause, Evaluate-console entries, stack-frame
908
+ navigation, variable expansion, restart, quit — to `session.jsonl` as
909
+ JSON-RPC commands. Works with launch mode (any language) and `-r`
910
+ remote attach.
911
+
912
+ Replay it two ways:
913
+
914
+ - `tdb --replay session.jsonl` — one command: launches the recorded
915
+ program headless, feeds every recorded command through the same RPC
916
+ dispatch `tdb --server` uses, and prints a transcript (recorded time,
917
+ command, verbatim result, interleaved program output). Exit code 0 iff
918
+ every command succeeded. Add `--timing` to reproduce the original
919
+ pacing, `--replay-timeout S` to bound each stop-wait (default 30 s).
920
+ - Against a live server: start `tdb --server prog.py`, then feed line 2
921
+ onward of the file to `POST /rpc` — each line is already a valid
922
+ request body:
923
+
924
+ tail -n +2 session.jsonl | while read line; do
925
+ curl -s -X POST -H 'Content-Type: application/json' \
926
+ -d "$line" http://127.0.0.1:8150/rpc
927
+ done
928
+
929
+ (On Windows, an equivalent loop in Python: read the file, skip the
930
+ first line, `requests.post` each remaining line.)
931
+
932
+ Not captured: pure viewing (scrolling, search, modals, thread/task
933
+ lists), breakpoint enable/disable toggles, variable expansions when the
934
+ adapter reports no `evaluateName` (currently the Perl adapter), and
935
+ File > Open program switches.
936
+
830
937
  ## MCP Integration
831
938
 
832
939
  tdb ships a Model Context Protocol (MCP) server (`tdb-mcp`) that exposes
@@ -936,11 +1043,12 @@ usage: tdb [-h] [-v] [-r [HOST:]PORT] [--cwd CWD] [--no-stop-on-entry]
936
1043
  | `--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. |
937
1044
  | `--remote-root PATH` | Remote directory matched to `--local-root` (same CLI position via `zip()`). |
938
1045
  | `-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. |
1046
+ | `-t`, `--to-line FILE:LINE|LINE` | Like `-k`, but the breakpoint is not saved to the breakpoints file — it just takes you to that spot in the code for this session (may be repeated). |
939
1047
  | `--no-stop-on-entry` | Do not pause at the first line (default: stop on entry; automatic when `-k` is given) |
940
1048
  | `--cwd DIR` | Working directory for the debuggee |
941
1049
  | `--python PATH` | Python interpreter for the debuggee (Python targets only) |
942
1050
  | `--pv` | Shorthand for --python .venv/bin/python |
943
- | `--lang LANGUAGE` | Debuggee language (`python`, `cpp`); default: auto-detect from the target |
1051
+ | `--lang LANGUAGE` | Debuggee language (`python`, `cpp`, `perl`); default: auto-detect from the target |
944
1052
  | `--adapter ADAPTER` | Debug adapter within the language (e.g. `--lang cpp --adapter lldb-dap`); default: the language's standard adapter |
945
1053
  | `--no-just-my-code` | Step into stdlib/site-packages code instead of skipping it
946
1054
  (default: skipped). On uncaught exceptions, the crash modal always shows the full traceback
@@ -969,8 +1077,17 @@ executable path (`{"adapters": {"lldb-dap": "/opt/llvm/bin/lldb-dap"}}`), and
969
1077
  `default_adapters` picks a language's default adapter
970
1078
  (`{"default_adapters": {"cpp": "lldb-dap"}}`).
971
1079
 
1080
+ **Perl is a special case:** `perl-tdb` is tdb's own bundled adapter (always
1081
+ found — it's Python code, not an external executable), so
1082
+ `adapters.perl` doesn't select an adapter binary. Instead it names the
1083
+ *Perl interpreter* tdb should spawn to run the debuggee:
1084
+ `{"adapters": {"perl": "/path/to/perl"}}`. Use this when the `perl` on
1085
+ `PATH` is too old (< 5.18) or you need a specific `perlbrew`/`plenv` version.
1086
+
972
1087
  Breakpoints are saved on exit and restored when debugging a program in the same
973
- directory. Each project's breakpoints are independent.
1088
+ directory. Each project's breakpoints are independent. Breakpoints set with
1089
+ `-t`/`--to-line` are the exception: they behave like `-k` breakpoints during
1090
+ the session but are never saved.
974
1091
 
975
1092
  **Step mode** (`step_mode` in `config.json`) controls how `n` (step over) and `s` (step
976
1093
  into) handle multi-line source statements:
@@ -25,7 +25,7 @@
25
25
  "pygments>=2.17.0",
26
26
  "fastapi>=0.115.0",
27
27
  "uvicorn>=0.30.0",
28
- "mcp>=1.28.0",
28
+ "mcp>=1.28.0,<2.0.0",
29
29
  ]
30
30
 
31
31
  # extras needed to run the examples and tests
@@ -57,7 +57,7 @@
57
57
  where = ["src"]
58
58
 
59
59
  [tool.setuptools.package-data]
60
- tdb = ["README.md"]
60
+ tdb = ["README.md", "adapters/perl/helpers.pl", "adapters/perl/Devel/TdbRemote.pm"]
61
61
 
62
62
  [tool.pytest.ini_options]
63
63
  testpaths = ["tests"]
@@ -128,6 +128,10 @@ tdb --lang python ./mytool
128
128
  # program runs to the first breakpoint)
129
129
  tdb -k 20 -k 35 -k module.py:14 my_program.py arg1 arg2
130
130
 
131
+ # run straight to line 20 without saving the breakpoint for future
132
+ # sessions (-t is -k minus the persistence)
133
+ tdb -t 20 my_program.py
134
+
131
135
  # use a specific virtualenv
132
136
  tdb --python /path/to/venv/bin/python my_program.py
133
137
 
@@ -173,14 +177,15 @@ languages are supported out of the box:
173
177
  |----------|------------|------------------------|---------------|
174
178
  | Python | `debugpy` (default) | installed with `textual-debugger` | everything in this README |
175
179
  | C / C++ (any native binary) | `gdb` (default), `lldb-dap` (alternate) | `gdb -i dap` requires GDB ≥ 14; `lldb-dap` ships with LLVM ≥ 17 (e.g. `apt install lldb`) | core debugging: breakpoints, stepping, stack, variables, evaluate console |
180
+ | Perl | perl-tdb (bundled) | needs perl ≥ 5.18 on PATH (or `{"adapters": {"perl": ...}}`) | core debugging + remote attach |
176
181
 
177
182
  ### Language detection and selection
178
183
 
179
184
  The language is auto-detected from the debug target:
180
185
 
181
- 1. File extension: `.py` → Python.
186
+ 1. File extension: `.py` → Python; `.pl` / `.pm` / `.t` → Perl.
182
187
  2. Native executables (ELF, Mach-O, PE magic bytes) → C/C++.
183
- 3. A `#!...python` shebang → Python.
188
+ 3. A `#!...python` or `#!...perl` shebang → Python / Perl respectively.
184
189
  4. C/C++/Rust *source* files (`.c`, `.cpp`, `.rs`, …) produce an error with a
185
190
  hint: compile with debug info (`g++ -g -O0`) and debug the binary.
186
191
  5. Anything else produces an error naming the `--lang` override.
@@ -216,9 +221,11 @@ Python-specific features are hidden or return a clear "not supported for this
216
221
  language" message when debugging other languages: statement-granularity
217
222
  stepping (non-Python languages always step per line), the async task /
218
223
  process inspectors and wait graph, the evaluate console's trailing-`?` help,
219
- remote attach (`-r`), `--python`/`--pv`, `--no-subprocess`,
220
- automatic child-process attachment, and the post-mortem / `tdb.breakpoint()`
221
- hooks (those hooks live inside Python programs by nature). `--terminal` is
224
+ `--python`/`--pv`, `--no-subprocess`, automatic child-process attachment, and
225
+ the post-mortem / `tdb.breakpoint()` hooks (those hooks live inside Python
226
+ programs by nature). Remote attach (`-r`) also works for Perl (see
227
+ [Perl](#perl) — `Devel::TdbRemote` in place of `debugpy.listen()`), but not
228
+ for C/C++. `--terminal` is
222
229
  currently ignored for non-Python targets.
223
230
 
224
231
  ### C/C++ tips
@@ -237,6 +244,72 @@ currently ignored for non-Python targets.
237
244
  `print x` rather than bare `x` (bare `x` collides with GDB's
238
245
  examine-memory command). `lldb-dap` evaluates bare expressions directly.
239
246
 
247
+ ### Perl
248
+
249
+ `tdb` bundles its own Perl adapter (`perl-tdb`) — no separate adapter install
250
+ needed, just a `perl` ≥ 5.18 on `PATH`. It drives stock `perl5db` under the
251
+ hood, so it works with any Perl already on the system.
252
+
253
+ **Launching a script:**
254
+
255
+ ```bash
256
+ tdb script.pl
257
+ ```
258
+
259
+ **Remote attach:** useful when the Perl process is already running (a long-
260
+ lived service, a process started by something other than `tdb`) or lives on
261
+ another host/container. Add three lines to the target program, with the
262
+ `use` line first so the debugger is armed before any of your code compiles:
263
+
264
+ ```perl
265
+ use Devel::TdbRemote; # FIRST line of your program
266
+ ...
267
+ Devel::TdbRemote::listen(5678); # non-blocking
268
+ Devel::TdbRemote::wait_for_client(); # blocks until tdb connects
269
+ ```
270
+
271
+ Then attach from `tdb`, forcing the language since there's no local `program`
272
+ argument for `tdb` to detect it from:
273
+
274
+ ```bash
275
+ tdb --lang perl -r host:5678
276
+ ```
277
+
278
+ **Arming caveat:** only code *compiled after* the debugger is armed can be
279
+ stepped into or breakpointed. That's why `use Devel::TdbRemote;` must be the
280
+ first line of the program. If you can't edit the first line (e.g. a wrapper
281
+ script controls startup), arm it before Perl even parses your file instead:
282
+ `perl -d:TdbRemote prog.pl`, or set `PERL5OPT=-d:TdbRemote` in the
283
+ environment that launches the debuggee.
284
+
285
+ **Copying the adapter to a remote host:** `Devel::TdbRemote` and its helper
286
+ script are plain files, not a CPAN install — copy both onto the remote
287
+ machine and point `PERL5LIB` at the directory that contains them:
288
+
289
+ ```bash
290
+ # From a checkout or an installed wheel's site-packages/tdb/adapters/perl:
291
+ scp -r Devel/TdbRemote.pm helpers.pl remote-host:/opt/tdb-perl/
292
+ # On the remote host:
293
+ export PERL5LIB=/opt/tdb-perl:$PERL5LIB
294
+ ```
295
+
296
+ (`Devel/TdbRemote.pm` locates `helpers.pl` next to itself at runtime, so keep
297
+ the two files in the same relative layout shown above — `helpers.pl` is a
298
+ sibling of the `Devel/` directory, not inside it.)
299
+
300
+ **PadWalker (optional but recommended):** inspecting lexical (`my`)
301
+ variables in the *current* frame always works. Lexicals in outer/caller
302
+ frames need the `PadWalker` module installed on the debuggee's Perl; without
303
+ it, tdb falls back to a read-only pad walk that can't reach fully into
304
+ enclosing scopes, and outer-frame variable listings degrade accordingly.
305
+ Install with `cpanm PadWalker` (or your distro's package) for full fidelity.
306
+
307
+ **Pause is unavailable in attach mode.** Launch-mode sessions (`tdb
308
+ script.pl`) support pausing a running program at any time. Remote-attach
309
+ sessions don't — debugpy-style asynchronous pause needs a control channel
310
+ `Devel::TdbRemote` doesn't implement yet; `pause` in attach mode returns a
311
+ clear "not available" error instead of hanging.
312
+
240
313
  ## Layout
241
314
 
242
315
  ```
@@ -827,6 +900,40 @@ curl -N http://127.0.0.1:8150/events
827
900
  Events: `initialized`, `stopped`, `continued`, `terminated`, `exited`, `output`.
828
901
  Each is JSON with `event`, `data`, and `timestamp` fields.
829
902
 
903
+ ## Recording and replaying sessions
904
+
905
+ `tdb --record session.jsonl prog.py` runs a normal TUI session and captures
906
+ your debugging actions — breakpoints (including `-k`/`-t` and persisted
907
+ ones), stepping, continue/pause, Evaluate-console entries, stack-frame
908
+ navigation, variable expansion, restart, quit — to `session.jsonl` as
909
+ JSON-RPC commands. Works with launch mode (any language) and `-r`
910
+ remote attach.
911
+
912
+ Replay it two ways:
913
+
914
+ - `tdb --replay session.jsonl` — one command: launches the recorded
915
+ program headless, feeds every recorded command through the same RPC
916
+ dispatch `tdb --server` uses, and prints a transcript (recorded time,
917
+ command, verbatim result, interleaved program output). Exit code 0 iff
918
+ every command succeeded. Add `--timing` to reproduce the original
919
+ pacing, `--replay-timeout S` to bound each stop-wait (default 30 s).
920
+ - Against a live server: start `tdb --server prog.py`, then feed line 2
921
+ onward of the file to `POST /rpc` — each line is already a valid
922
+ request body:
923
+
924
+ tail -n +2 session.jsonl | while read line; do
925
+ curl -s -X POST -H 'Content-Type: application/json' \
926
+ -d "$line" http://127.0.0.1:8150/rpc
927
+ done
928
+
929
+ (On Windows, an equivalent loop in Python: read the file, skip the
930
+ first line, `requests.post` each remaining line.)
931
+
932
+ Not captured: pure viewing (scrolling, search, modals, thread/task
933
+ lists), breakpoint enable/disable toggles, variable expansions when the
934
+ adapter reports no `evaluateName` (currently the Perl adapter), and
935
+ File > Open program switches.
936
+
830
937
  ## MCP Integration
831
938
 
832
939
  tdb ships a Model Context Protocol (MCP) server (`tdb-mcp`) that exposes
@@ -936,11 +1043,12 @@ usage: tdb [-h] [-v] [-r [HOST:]PORT] [--cwd CWD] [--no-stop-on-entry]
936
1043
  | `--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. |
937
1044
  | `--remote-root PATH` | Remote directory matched to `--local-root` (same CLI position via `zip()`). |
938
1045
  | `-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. |
1046
+ | `-t`, `--to-line FILE:LINE|LINE` | Like `-k`, but the breakpoint is not saved to the breakpoints file — it just takes you to that spot in the code for this session (may be repeated). |
939
1047
  | `--no-stop-on-entry` | Do not pause at the first line (default: stop on entry; automatic when `-k` is given) |
940
1048
  | `--cwd DIR` | Working directory for the debuggee |
941
1049
  | `--python PATH` | Python interpreter for the debuggee (Python targets only) |
942
1050
  | `--pv` | Shorthand for --python .venv/bin/python |
943
- | `--lang LANGUAGE` | Debuggee language (`python`, `cpp`); default: auto-detect from the target |
1051
+ | `--lang LANGUAGE` | Debuggee language (`python`, `cpp`, `perl`); default: auto-detect from the target |
944
1052
  | `--adapter ADAPTER` | Debug adapter within the language (e.g. `--lang cpp --adapter lldb-dap`); default: the language's standard adapter |
945
1053
  | `--no-just-my-code` | Step into stdlib/site-packages code instead of skipping it
946
1054
  (default: skipped). On uncaught exceptions, the crash modal always shows the full traceback
@@ -969,8 +1077,17 @@ executable path (`{"adapters": {"lldb-dap": "/opt/llvm/bin/lldb-dap"}}`), and
969
1077
  `default_adapters` picks a language's default adapter
970
1078
  (`{"default_adapters": {"cpp": "lldb-dap"}}`).
971
1079
 
1080
+ **Perl is a special case:** `perl-tdb` is tdb's own bundled adapter (always
1081
+ found — it's Python code, not an external executable), so
1082
+ `adapters.perl` doesn't select an adapter binary. Instead it names the
1083
+ *Perl interpreter* tdb should spawn to run the debuggee:
1084
+ `{"adapters": {"perl": "/path/to/perl"}}`. Use this when the `perl` on
1085
+ `PATH` is too old (< 5.18) or you need a specific `perlbrew`/`plenv` version.
1086
+
972
1087
  Breakpoints are saved on exit and restored when debugging a program in the same
973
- directory. Each project's breakpoints are independent.
1088
+ directory. Each project's breakpoints are independent. Breakpoints set with
1089
+ `-t`/`--to-line` are the exception: they behave like `-k` breakpoints during
1090
+ the session but are never saved.
974
1091
 
975
1092
  **Step mode** (`step_mode` in `config.json`) controls how `n` (step over) and `s` (step
976
1093
  into) handle multi-line source statements:
@@ -1,6 +1,6 @@
1
1
  from tdb.breakpoint_hook import breakpoint
2
2
  from tdb.post_mortem import exception_hook
3
3
 
4
- __version__ = "0.2.0"
4
+ __version__ = "0.2.2" # trailing digit odd == dev version
5
5
 
6
6
  __all__ = ["breakpoint", "exception_hook"]