textual-debugger 0.2.8__tar.gz → 0.2.12__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 (159) hide show
  1. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/PKG-INFO +422 -84
  2. {textual_debugger-0.2.8/src/tdb → textual_debugger-0.2.12}/README.md +419 -81
  3. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/pyproject.toml +3 -3
  4. {textual_debugger-0.2.8 → textual_debugger-0.2.12/src/tdb}/README.md +419 -81
  5. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/__init__.py +1 -1
  6. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/_timeouts.py +11 -0
  7. textual_debugger-0.2.12/src/tdb/adapters/perl/padwalker.py +286 -0
  8. textual_debugger-0.2.12/src/tdb/adapters/perl/padwalker_src/Makefile.PL +38 -0
  9. textual_debugger-0.2.12/src/tdb/adapters/perl/padwalker_src/PadWalker.pm +160 -0
  10. textual_debugger-0.2.12/src/tdb/adapters/perl/padwalker_src/PadWalker.xs +645 -0
  11. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/adapters/perl/session.py +15 -0
  12. textual_debugger-0.2.12/src/tdb/adapters/powershell/__init__.py +3 -0
  13. textual_debugger-0.2.12/src/tdb/adapters/powershell/__main__.py +23 -0
  14. textual_debugger-0.2.12/src/tdb/adapters/powershell/locate.py +121 -0
  15. textual_debugger-0.2.12/src/tdb/adapters/powershell/output.py +80 -0
  16. textual_debugger-0.2.12/src/tdb/adapters/powershell/server.py +913 -0
  17. textual_debugger-0.2.12/src/tdb/adapters/powershell/tdb_launch.ps1 +14 -0
  18. textual_debugger-0.2.12/src/tdb/adapters/seqs.py +62 -0
  19. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/app.py +31 -16
  20. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/app_handlers/dap_events.py +3 -0
  21. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/app_handlers/inspection.py +71 -1
  22. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/app_handlers/routing.py +36 -0
  23. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/app_handlers/ui_panels.py +39 -0
  24. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/app_helpers.py +51 -0
  25. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/breakpoint_hook.py +16 -1
  26. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/cli.py +398 -26
  27. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/dap/client.py +77 -1
  28. textual_debugger-0.2.12/src/tdb/eval_mode.py +314 -0
  29. textual_debugger-0.2.12/src/tdb/examine.py +302 -0
  30. textual_debugger-0.2.12/src/tdb/go_concurrency/analyzer.py +112 -0
  31. textual_debugger-0.2.12/src/tdb/go_concurrency/classifier.py +59 -0
  32. textual_debugger-0.2.12/src/tdb/go_concurrency/collector.py +122 -0
  33. textual_debugger-0.2.12/src/tdb/go_concurrency/models.py +123 -0
  34. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/languages/base.py +25 -0
  35. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/languages/errors.py +210 -0
  36. textual_debugger-0.2.12/src/tdb/languages/go.py +217 -0
  37. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/languages/ocaml.py +78 -3
  38. textual_debugger-0.2.12/src/tdb/languages/powershell.py +124 -0
  39. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/languages/registry.py +26 -3
  40. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/mcp/server.py +24 -9
  41. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/mcp/session.py +1 -1
  42. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/replay.py +28 -8
  43. textual_debugger-0.2.12/src/tdb/replay_tui.py +311 -0
  44. textual_debugger-0.2.12/src/tdb/run_mode.py +514 -0
  45. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/server/handlers.py +24 -3
  46. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/server/runner.py +4 -0
  47. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/session/controller.py +173 -19
  48. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/session/inspect_service.py +12 -3
  49. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/session/state.py +7 -0
  50. textual_debugger-0.2.12/src/tdb/widgets/__init__.py +0 -0
  51. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/widgets/evaluate_console.py +12 -7
  52. textual_debugger-0.2.12/src/tdb/widgets/goroutines_modal.py +354 -0
  53. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/textual_debugger.egg-info/PKG-INFO +422 -84
  54. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/textual_debugger.egg-info/SOURCES.txt +22 -0
  55. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/textual_debugger.egg-info/requires.txt +2 -2
  56. textual_debugger-0.2.8/src/tdb/run_mode.py +0 -271
  57. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/LICENSE +0 -0
  58. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/setup.cfg +0 -0
  59. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/__main__.py +0 -0
  60. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/adapters/__init__.py +0 -0
  61. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/adapters/bash/__init__.py +0 -0
  62. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/adapters/bash/__main__.py +0 -0
  63. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/adapters/bash/declares.py +0 -0
  64. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/adapters/bash/server.py +0 -0
  65. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/adapters/bash/session.py +0 -0
  66. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/adapters/bash/tdb_harness.sh +0 -0
  67. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/adapters/ocaml/__init__.py +0 -0
  68. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/adapters/ocaml/lldb_formatters.py +0 -0
  69. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/adapters/perl/Devel/TdbCompile.pm +0 -0
  70. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/adapters/perl/Devel/TdbRemote.pm +0 -0
  71. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/adapters/perl/__init__.py +0 -0
  72. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/adapters/perl/__main__.py +0 -0
  73. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/adapters/perl/helpers.pl +0 -0
  74. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/adapters/perl/protocol.py +0 -0
  75. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/adapters/perl/refs.py +0 -0
  76. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/adapters/perl/server.py +0 -0
  77. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/adapters/ruby/__init__.py +0 -0
  78. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/adapters/ruby/__main__.py +0 -0
  79. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/adapters/ruby/server.py +0 -0
  80. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/adapters/tcsh/__init__.py +0 -0
  81. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/adapters/tcsh/__main__.py +0 -0
  82. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/adapters/tcsh/breakpoints.py +0 -0
  83. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/adapters/tcsh/cli.py +0 -0
  84. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/adapters/tcsh/guardian.py +0 -0
  85. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/adapters/tcsh/inspect.py +0 -0
  86. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/adapters/tcsh/instrumenter.py +0 -0
  87. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/adapters/tcsh/models.py +0 -0
  88. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/adapters/tcsh/protocol.py +0 -0
  89. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/adapters/tcsh/runtime.py +0 -0
  90. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/adapters/tcsh/scanner.py +0 -0
  91. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/adapters/tcsh/server.py +0 -0
  92. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/adapters/tcsh/session.py +0 -0
  93. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/adapters/tcsh/transport.py +0 -0
  94. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/app_handlers/__init__.py +0 -0
  95. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/dap/__init__.py +0 -0
  96. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/dap/messages.py +0 -0
  97. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/dap/protocol.py +0 -0
  98. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/dap/reverse.py +0 -0
  99. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/dap/types.py +0 -0
  100. {textual_debugger-0.2.8/src/tdb/server → textual_debugger-0.2.12/src/tdb/go_concurrency}/__init__.py +0 -0
  101. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/inspection.py +0 -0
  102. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/inspection_full.py +0 -0
  103. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/keybindings/__init__.py +0 -0
  104. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/languages/__init__.py +0 -0
  105. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/languages/bash.py +0 -0
  106. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/languages/cpp.py +0 -0
  107. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/languages/perl.py +0 -0
  108. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/languages/python.py +0 -0
  109. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/languages/ruby.py +0 -0
  110. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/languages/rust.py +0 -0
  111. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/languages/tcsh.py +0 -0
  112. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/mcp/__init__.py +0 -0
  113. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/mcp/__main__.py +0 -0
  114. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/persist.py +0 -0
  115. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/post_mortem.py +0 -0
  116. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/processes_cache.py +0 -0
  117. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/rust_concurrency/__init__.py +0 -0
  118. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/rust_concurrency/analyzer.py +0 -0
  119. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/rust_concurrency/classifier.py +0 -0
  120. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/rust_concurrency/collector.py +0 -0
  121. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/rust_concurrency/models.py +0 -0
  122. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/rust_concurrency/probes/__init__.py +0 -0
  123. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/rust_concurrency/probes/base.py +0 -0
  124. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/rust_concurrency/probes/gdb.py +0 -0
  125. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/rust_concurrency/probes/gdb_script.py +0 -0
  126. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/rust_concurrency/probes/lldb.py +0 -0
  127. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/rust_concurrency/probes/lldb_script.py +0 -0
  128. {textual_debugger-0.2.8/src/tdb/session → textual_debugger-0.2.12/src/tdb/server}/__init__.py +0 -0
  129. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/server/app.py +0 -0
  130. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/server/event_handler.py +0 -0
  131. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/server/rpc_types.py +0 -0
  132. {textual_debugger-0.2.8/src/tdb/widgets → textual_debugger-0.2.12/src/tdb/session}/__init__.py +0 -0
  133. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/session/child_processes.py +0 -0
  134. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/session/errors.py +0 -0
  135. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/session/event_bus.py +0 -0
  136. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/session/messages.py +0 -0
  137. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/session/post_mortem_loader.py +0 -0
  138. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/session/recorder.py +0 -0
  139. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/session/statement_stepper.py +0 -0
  140. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/session/terminal.py +0 -0
  141. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/session/textual_handler.py +0 -0
  142. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/source_analysis.py +0 -0
  143. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/widgets/_inspection_modal.py +0 -0
  144. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/widgets/async_tasks_modal.py +0 -0
  145. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/widgets/breakpoint_view.py +0 -0
  146. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/widgets/code_view.py +0 -0
  147. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/widgets/console_view.py +0 -0
  148. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/widgets/full_contents_modal.py +0 -0
  149. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/widgets/menu_bar.py +0 -0
  150. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/widgets/modals.py +0 -0
  151. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/widgets/processes_modal.py +0 -0
  152. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/widgets/rust_concurrency_modal.py +0 -0
  153. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/widgets/stack_view.py +0 -0
  154. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/widgets/status_bar.py +0 -0
  155. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/widgets/threads_modal.py +0 -0
  156. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/tdb/widgets/variable_view.py +0 -0
  157. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/textual_debugger.egg-info/dependency_links.txt +0 -0
  158. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/src/textual_debugger.egg-info/entry_points.txt +0 -0
  159. {textual_debugger-0.2.8 → textual_debugger-0.2.12}/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.8
3
+ Version: 0.2.12
4
4
  Summary: A TUI debugger for Python, Perl, and other languages. Based on textual.
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:
@@ -49,11 +49,11 @@ Requires-Python: >=3.11
49
49
  Description-Content-Type: text/markdown
50
50
  License-File: LICENSE
51
51
  Requires-Dist: textual>=8.0.0
52
- Requires-Dist: debugpy>=1.8.0
52
+ Requires-Dist: debugpy>=1.8.12
53
53
  Requires-Dist: pygments>=2.17.0
54
54
  Requires-Dist: fastapi>=0.115.0
55
55
  Requires-Dist: uvicorn>=0.30.0
56
- Requires-Dist: mcp<2.0.0,>=1.28.0
56
+ Requires-Dist: mcp<3.0.0,>=2.0.0
57
57
  Provides-Extra: dev
58
58
  Requires-Dist: pytest; extra == "dev"
59
59
  Requires-Dist: pytest-cov; extra == "dev"
@@ -79,6 +79,8 @@ with a Debug Adapter Protocol (DAP) implementation. In addition to Python,
79
79
  - Tcsh (via source instrumentation of a stock `tcsh`)
80
80
  - Ruby (via the [debug gem](https://github.com/ruby/debug)'s `rdbg`; debug ≥ 1.9)
81
81
  - OCaml (native via `lldb-dap`/`gdb`, bytecode via [ocamlearlybird](https://github.com/hackwaly/ocamlearlybird))
82
+ - Go (via [Delve](https://github.com/go-delve/delve)'s `dlv dap`)
83
+ - PowerShell 7 (via [PowerShell Editor Services](https://github.com/PowerShell/PowerShellEditorServices); Linux/macOS, Windows experimental)
82
84
 
83
85
  `tdb` is built with [textual](https://github.com/Textualize/textual) and speaks
84
86
  DAP to a pluggable debug adapter.
@@ -99,8 +101,9 @@ MIT License. Copyright 2026 by Al Danial.
99
101
  the richest feature set), C/C++ (via `gdb -i dap` or `lldb-dap`), Rust (via `gdb` or
100
102
  `lldb-dap`), Perl (via `perl -d`),
101
103
  Bash (via bash's own `DEBUG` trap), Tcsh (via source instrumentation of a
102
- stock `tcsh`), Ruby (via the debug gem's `rdbg`), and OCaml (native via
103
- `lldb-dap`/`gdb`, bytecode via ocamlearlybird). Most languages are
104
+ stock `tcsh`), Ruby (via the debug gem's `rdbg`), OCaml (native via
105
+ `lldb-dap`/`gdb`, bytecode via ocamlearlybird), Go (via Delve's `dlv
106
+ dap`), and PowerShell 7 (via PowerShell Editor Services). Most languages are
104
107
  auto-detected from the target; Rust intentionally requires `--lang rust`
105
108
  (ref. [Multi-Language Debugging](#multi-language-debugging)).
106
109
 
@@ -110,9 +113,9 @@ It specifically supports modules
110
113
  - `threading` (with a thread inspector)
111
114
  - `multiprocessing` / `concurrent.futures` (with automatic child process attachment and a process inspector)
112
115
 
113
- - supports remote attachment to Python, Perl, Ruby, Rust, and C/C++ programs
116
+ - supports remote attachment to Python, Perl, Ruby, Rust, C/C++, and Go programs
114
117
 
115
- - includes a JSON-RPC server mode, an MCP mode, and a `SKILL.md` file that enable
118
+ - includes a JSON-RPC server mode, an MCP (v2) mode, and a `SKILL.md` file that enable
116
119
  programmatic debug control, making it suitable for
117
120
  automated, headless debugging workflows and AI-assisted debugging
118
121
 
@@ -126,28 +129,6 @@ to have `tdb` pop open automatically at the first uncaught exception
126
129
  making it suitable for operation in non-graphical environments (mouse support is
127
130
  available in graphical environments)
128
131
 
129
- ## Acknowledgments
130
-
131
- Thank you:
132
-
133
- - Will McGugan for the amazing `textual` module.
134
- `tdb` would be a pale shadow of itself had I used any other TUI framework.
135
- Fantastic work, Will.
136
-
137
- - Microsoft for the Debug Adapter Protocol (DAP) and releasing
138
- its implementation in `debugpy` and the Python Debugger extension for Visual Studio Code
139
- as open source.
140
-
141
- - Anthropic, for providing access to Claude Code through the
142
- [Claude for Open Source](https://claude.com/contact-sales/claude-for-oss) program.
143
- `tdb` was made almost entirely with Claude Code.
144
-
145
- - OpenAI, for providing access to Codex through the
146
- [Codex for Open Source](https://developers.openai.com/community/codex-for-oss) program.
147
-
148
- This project was inspired by Andreas Klöckner's excellent [pudb](https://pypi.org/project/pudb/)
149
- Python debugger.
150
-
151
132
  ## Gallery
152
133
  <p align="center">
153
134
  <img src="https://github.com/AlDanial/tdb/blob/main/gallery/async_breakpoint.png" alt="at breakpoint" width="300">
@@ -187,6 +168,10 @@ uvx --from textual-debugger tdb my_program.py
187
168
  # show comprehensive documentation in a terminal-based Markdown viewer
188
169
  tdb --doc
189
170
 
171
+ # show where tdb is installed, where its bundled Perl PadWalker module
172
+ # lives, and the Help > About text
173
+ tdb --info
174
+
190
175
  # debug a script (stops at first line by default)
191
176
  tdb my_program.py
192
177
 
@@ -212,6 +197,10 @@ tdb -k 20 -k 35 -k module.py:14 my_program.py arg1 arg2
212
197
  # sessions (-t is -k minus the persistence)
213
198
  tdb -t 20 my_program.py
214
199
 
200
+ # no TUI: run at (almost) full speed, printing nr each time line 25 is
201
+ # reached, then continuing (-e may be repeated; see "Eval Mode" below)
202
+ tdb --eval my_program.py:25 "print(f'{nr=}')" my_program.py
203
+
215
204
  # use a specific virtualenv
216
205
  tdb --python /path/to/venv/bin/python my_program.py
217
206
 
@@ -238,6 +227,11 @@ tdb -r remotehost:5678 -k my_program.py:42
238
227
  # than code on the remote host
239
228
  tdb -r remotehost:5678 --local-root /my/code/dir --remote-root /app -k my_program.py:42
240
229
 
230
+ # attach to a program that pauses itself (debugpy.breakpoint() right after
231
+ # debugpy.wait_for_client()); without this flag tdb pauses the program on
232
+ # attach, which can leave it suspended after tdb quits
233
+ tdb -r remotehost:5678 --no-pause-on-attach
234
+
241
235
  # separate tdb arguments from debuggee arguments with `--`
242
236
  tdb --python /path/to/venv/bin/python -- my_program.py -k 17 --max 23.3
243
237
  ```
@@ -250,7 +244,7 @@ python -m tdb my_program.py
250
244
 
251
245
  ## Multi-Language Debugging
252
246
 
253
- `tdb` debugs any language that has a Debug Adapter Protocol backend. Eight
247
+ `tdb` debugs any language that has a Debug Adapter Protocol backend. Ten
254
248
  languages are supported out of the box:
255
249
 
256
250
  | Language | Adapter(s) | Dependencies | Feature level |
@@ -262,33 +256,42 @@ languages are supported out of the box:
262
256
  | Bash | bash-tdb (bundled) | bash ≥ 4.4 on PATH | core debugging (no remote attach) |
263
257
  | Tcsh | tcsh-tdb (bundled) | tcsh on PATH | core debugging (no remote attach, no conditional breakpoints, no pause) |
264
258
  | Ruby | `rdbg` (the [debug gem](https://github.com/ruby/debug)) | `rdbg` ≥ 1.9 on PATH | core debugging + remote attach |
265
- | OCaml (native executable) | `lldb-dap` (default), `gdb` (alternate) | OCaml ≥ 4.12; `lldb-dap` ships with LLVM ≥ 17; `gdb -i dap` requires GDB ≥ 14 | core debugging + domains-as-threads; Variables view shows no OCaml locals (upstream DWARF limitation); evaluate console is lldb/C-level, not OCaml |
266
- | OCaml (bytecode executable) | `ocamlearlybird` (default) | OCaml ≥ 4.12; `opam install earlybird` | core debugging + rich OCaml locals; no pause/`--run`, no evaluate responses, no fatal-error modal (ocamlearlybird 1.3.6 limitations); single-domain only |
259
+ | OCaml (native executable) | `lldb-dap` (default), `gdb` (alternate) | `lldb-dap` ships with LLVM ≥ 17; `gdb -i dap` requires GDB ≥ 14 | core debugging + domains-as-threads; Variables view shows no OCaml locals (upstream DWARF limitation); evaluate console is lldb/C-level, not OCaml |
260
+ | OCaml (bytecode executable) | `ocamlearlybird` (default) | `opam install earlybird` | core debugging + rich OCaml locals; no pause/`--run`, no evaluate responses, no fatal-error modal (ocamlearlybird 1.3.6 limitations); single-domain only |
261
+ | Go | `dlv` (the [Delve](https://github.com/go-delve/delve) DAP server) | Delve ≥ 1.21 on PATH (`go install github.com/go-delve/delve/cmd/dlv@latest`) | core debugging + goroutine inspection (wait graph, findings) + remote attach (no `--terminal`) |
262
+ | PowerShell 7 | `pses` (PowerShell Editor Services, driven by tdb's bundled proxy) | `pwsh` ≥ 7.2 on PATH + the PSES module (see [PowerShell](#powershell)) | core debugging (breakpoints incl. conditional/hit-count/log, stepping, stack, variables, evaluate console) + `--run`; no `--terminal`, no remote attach; Windows untested |
267
263
 
268
264
  ### Language detection and selection
269
265
 
270
266
  The language is auto-detected from the debug target:
271
267
 
272
- 1. File extension: `.py` → Python; `.pl` / `.pm` / `.t` → Perl; `.sh` / `.bash`
273
- → Bash; `.csh` / `.tcsh` → Tcsh; `.rb` → Ruby. (`.ml` / `.mli` do *not*
274
- auto-select OCaml, see point 5.)
275
- 2. Native executables (ELF, Mach-O, PE magic bytes) → C/C++, unless
276
- byte-sniffing finds an OCaml marker, either a native binary's
268
+ 1. A directory → Go, if it contains any `*.go` file (a Go package
269
+ directory, e.g. `tdb ./pkg`); otherwise an error naming `--lang`.
270
+ 2. File extension: `.py` → Python; `.go` → Go; `.pl` / `.pm` / `.t` → Perl;
271
+ `.sh` / `.bash` → Bash; `.csh` / `.tcsh` → Tcsh; `.rb` → Ruby; `.ps1` /
272
+ `.psm1` → PowerShell. (`.ml` / `.mli` do *not* auto-select OCaml, see
273
+ point 6.)
274
+ 3. Native executables (ELF, Mach-O, PE magic bytes) → C/C++, unless
275
+ byte-checking finds an OCaml marker (a native binary's
277
276
  `caml_program`/`caml_startup` runtime symbols, a bytecode file's
278
- trailing `Caml1999` marker, or a `#!...ocamlrun` shebang, in which case
279
- → OCaml (native or bytecode respectively). A stripped native OCaml
280
- binary that byte-sniffing can't identify falls back to C/C++; force it
281
- with `--lang ocaml`. Rust is never inferred from an executable; select
282
- it explicitly with `--lang rust`.
283
- 3. A `#!...python`, `#!...perl`, `#!...bash`, `#!...csh`/`#!...tcsh`, or
284
- `#!...ruby` shebang → Python / Perl / Bash / Tcsh / Ruby respectively.
285
- 4. C/C++/Rust *source* files (`.c`, `.cpp`, `.rs`, …) produce an error with a
277
+ trailing `Caml1999` marker, or a `#!...ocamlrun` shebang) → OCaml
278
+ (native or bytecode respectively), or a Go buildinfo blob (the marker
279
+ `go version` itself locates, scanned in the first 16MB of the file) →
280
+ Go. A stripped native OCaml binary, or a Go binary whose buildinfo
281
+ blob sits beyond the 16MB scan window, that byte-checking can't
282
+ identify falls back to C/C++; force it with `--lang ocaml` or `--lang
283
+ go` respectively. Rust is never inferred from an executable; select it
284
+ explicitly with `--lang rust`.
285
+ 4. A `#!...python`, `#!...perl`, `#!...bash`, `#!...csh`/`#!...tcsh`,
286
+ `#!...ruby`, or `#!...pwsh` shebang → Python / Perl / Bash / Tcsh / Ruby /
287
+ PowerShell respectively.
288
+ 5. C/C++/Rust *source* files (`.c`, `.cpp`, `.rs`, …) produce an error with a
286
289
  hint: compile with debug info (`g++ -g -O0`) and debug the binary.
287
- 5. `.ml` / `.mli` (OCaml source) produce an error too: build the project
290
+ 6. `.ml` / `.mli` (OCaml source) produce an error too: build the project
288
291
  first (dune's dev profile keeps debug info) and pass the **built
289
292
  executable** to `tdb` (`tdb ./_build/default/bin/main.exe`), never the
290
293
  `.ml` file.
291
- 6. Anything else produces an error naming the `--lang` override.
294
+ 7. Anything else produces an error naming the `--lang` override.
292
295
 
293
296
  `--lang` forces the language; `--adapter` picks a non-default adapter within
294
297
  it (`tdb --lang cpp --adapter lldb-dap ./myprog`). Rust always requires the
@@ -316,23 +319,29 @@ non-standard location, or change a language's default adapter, add to
316
319
  Core debugging works identically for every language: breakpoints (incl.
317
320
  conditions and persistence), stepping, continue/pause, run-to-cursor, stack
318
321
  navigation, variable inspection, the evaluate console, syntax highlighting,
319
- and the JSON-RPC / MCP programmatic modes.
322
+ and the JSON-RPC / MCP (v2) programmatic modes.
320
323
 
321
324
  Python-specific features are hidden or return "not supported for this
322
325
  language" message when debugging other languages: statement-granularity
323
- stepping (non-Python languages always step per line), the async task /
324
- process inspectors and wait graph, the evaluate console's trailing-`?` help,
326
+ stepping (non-Python languages step at their debugger's native granularity
327
+ — per line, except PowerShell, whose debugger stops per statement, see
328
+ [PowerShell](#powershell)), the async task / process inspectors and wait
329
+ graph, the evaluate console's trailing-`?` help,
325
330
  `--python`/`--pv`, `--no-subprocess`, automatic child-process attachment, and
326
331
  the post-mortem / `tdb.breakpoint()` hooks (those hooks live inside Python
327
332
  programs by nature). Remote attach (`-r`) also works for Perl (see
328
333
  [Perl](#perl), `Devel::TdbRemote` in place of `debugpy.listen()`), Ruby
329
- (see [Ruby](#ruby), `rdbg --open` in place of `debugpy.listen()`), and for
334
+ (see [Ruby](#ruby), `rdbg --open` in place of `debugpy.listen()`), Go (see
335
+ [Go](#go), `dlv dap --listen` in place of `debugpy.listen()`), and for
330
336
  Rust and C/C++ native binaries (against a gdbserver/lldb-server stub, with
331
337
  a local symbol-bearing executable; see [Rust](#rust) — the same flags work
332
- for a C/C++ target without `--lang`), but not for Bash, Tcsh, or OCaml. `--terminal` works for every launch-mode
338
+ for a C/C++ target without `--lang`), but not for Bash, Tcsh, OCaml, or
339
+ PowerShell. `--terminal` works for every launch-mode
333
340
  language — Python, Perl, Bash, Tcsh, Ruby, and C/C++, OCaml native, or Rust
334
341
  sessions via `--adapter lldb-dap` — see
335
- [External Terminal Support](#external-terminal-support).
342
+ [External Terminal Support](#external-terminal-support). Go and PowerShell do
343
+ not support `--terminal` yet (`dlv dap` and PSES have no terminal-routing
344
+ mode).
336
345
 
337
346
  **Bash limitations (v1):** the bash adapter uses bash's own `DEBUG` trap and
338
347
  has a smaller feature set than Python:
@@ -397,7 +406,30 @@ picture:
397
406
  reaches `tdb`). Run the program outside the debugger to see the
398
407
  backtrace instead.
399
408
  - No Windows support, no remote attach, and a stripped native binary that
400
- byte-sniffing can't identify needs `--lang ocaml`.
409
+ byte-checking can't identify needs `--lang ocaml`.
410
+
411
+ **Go limitations (v1):**
412
+
413
+ - `--terminal` is not supported (`dlv dap` has no terminal-routing mode).
414
+ - The Goroutines workspace's wait graph shows channel-send/-receive,
415
+ mutex, and `WaitGroup` edges; a goroutine parked in a `select` is
416
+ classified but contributes no wait edge (Delve can't tell which of the
417
+ `select`'s cases it's waiting on).
418
+ - No mutex-holder identification: Go's `sync.Mutex` doesn't record an
419
+ owner at the runtime level, so unlike Python's Lock/Semaphore inspector
420
+ the wait graph can name what a goroutine is blocked on but never who
421
+ holds it.
422
+ - Goroutines spawned by `exec.Command` (child OS processes) are not
423
+ automatically attached or debugged, unlike Python's automatic
424
+ multiprocessing child attach.
425
+ - Detection of a Go binary via buildinfo check scans only the first
426
+ 16MB of the file; a huge binary whose buildinfo blob sits beyond that
427
+ falls back to C/C++ — force it with `--lang go`.
428
+ - `-a`/`--attach`'s language auto-detection (reading the target pid's
429
+ buildinfo from `/proc/PID/exe`) is Linux-only; on macOS/Windows pass
430
+ `--lang go` alongside `-a`.
431
+
432
+ See [Go](#go) below for launch details.
401
433
 
402
434
  ### C/C++ tips
403
435
 
@@ -491,6 +523,24 @@ metadata for broader primitives and stronger ownership evidence.
491
523
  `perl` ≥ 5.18 on `PATH`. It drives stock `perl5db` under the
492
524
  hood, so it works with any Perl already on the system.
493
525
 
526
+ The [PadWalker](https://metacpan.org/pod/PadWalker) module v2.5, by Robin Houston,
527
+ is bundled with `tdb`. PadWalker provides a more complete view of lexical variables
528
+ in outer/caller frames and therefore provides a richer debugging experience.
529
+ PadWalker is an XS (compiled) module, so the bundled copy has to be built
530
+ against the exact perl that runs your program. `tdb` does this for you on the
531
+ first Perl launch: if the perl in use cannot already load PadWalker (from CPAN
532
+ or a distro package), `tdb` compiles the bundled sources, caches the result
533
+ per interpreter under its config directory (`~/.config/tdb/padwalker/` on
534
+ Linux/macOS, `%APPDATA%\\tdb\\padwalker\\` on Windows), and prepends that
535
+ directory to the debuggee's `PERL5LIB`. The build takes about a second and
536
+ needs perl's headers (`libperl-dev` on Debian/Ubuntu, `perl-devel` on
537
+ Fedora/RHEL) plus a C compiler; Strawberry Perl on Windows ships both. When
538
+ the build is not possible, `tdb` prints a one-line notice on the console and
539
+ falls back to a read-only pad walk, so outer-frame lexicals degrade but
540
+ debugging otherwise proceeds. `tdb --info` reports the source directory, the
541
+ cache directory, and whether a build exists for the `perl` on your `PATH`.
542
+ `TDB_PADWALKER_CACHE` overrides the cache location.
543
+
494
544
  **Launching a script:**
495
545
 
496
546
  ```bash
@@ -587,13 +637,6 @@ export PERL5LIB=/opt/tdb-perl:$PERL5LIB
587
637
  the two files in the same relative layout shown above; `helpers.pl` is a
588
638
  sibling of the `Devel/` directory, not inside it.)
589
639
 
590
- **PadWalker (optional but recommended):** inspecting lexical (`my`)
591
- variables in the *current* frame always works. Lexicals in outer/caller
592
- frames need the `PadWalker` module installed on the debuggee's Perl; without
593
- it, tdb falls back to a read-only pad walk that can't reach fully into
594
- enclosing scopes, and outer-frame variable listings degrade accordingly.
595
- Install with `cpanm PadWalker` (or your distro's package) for full fidelity.
596
-
597
640
  **Pause is unavailable in attach mode.** Launch-mode sessions (`tdb
598
641
  script.pl`) support pausing a running program at any time. Remote-attach
599
642
  sessions don't. Asynchronous pause (as Python gets via `debugpy`) needs a control channel
@@ -703,18 +746,35 @@ does). Always pass the **built executable** to `tdb`, never the `.ml`
703
746
  source, as in `tdb ./_build/default/bin/main.exe`, not `tdb main.ml` (which
704
747
  errors with this exact guidance).
705
748
 
749
+ Earlybird additionally as a fragile relative path matching so for
750
+ best results provide a fully quallified path to the source files.
751
+
752
+ ```
753
+ ocamlc -g -o my_program.byte "$PWD/my_program.ml"
754
+ ```
755
+
756
+ Then, to ensure transition from bytecode to OCaml source in the
757
+ Code view, provide an explicit temporary breakpoint (`-t`)
758
+ at the first line of execution:
759
+
760
+ ```
761
+ tdb --lang ocaml --adapter ocamlearlybird -t "$PWD/my_program.ml:2" ./my_program.byte
762
+ ```
763
+
706
764
  **OCaml version:** tdb requires **OCaml ≥ 4.12**. Older runtimes print
707
765
  `Printexc` backtraces without function names, which the fatal-error
708
766
  modal's parser does not understand (frames would be dropped and the
709
767
  modal would wrongly suggest recompiling with `-g`).
710
768
 
711
- ```bash
712
- tdb ./_build/default/bin/main.exe # native, lldb-dap
713
- tdb --adapter gdb ./_build/default/bin/main.exe # native, gdb
714
- tdb ./_build/default/bin/main.byte # bytecode, ocamlearlybird
715
- ```
769
+ With native GDB, the default entry stop is translated to the first
770
+ executable line in project OCaml source; the generated C/ELF startup code is
771
+ not shown.
772
+ Unfortunately OCaml's native debug information does not always identify
773
+ tihs line correctly.
774
+ `--no-stop-on-entry` and explicit CLI breakpoints retain their
775
+ usual behavior.
716
776
 
717
- A stripped native binary that byte-sniffing can't identify as OCaml falls
777
+ A stripped native binary that byte-checking can't identify as OCaml falls
718
778
  back to C/C++; force it with `--lang ocaml`. Override the adapter or point
719
779
  at a non-`PATH` install in `config.json` (see
720
780
  [Configuration](#configuration)):
@@ -774,6 +834,139 @@ sessions can't `pause` a running program (so `--run` is unavailable for
774
834
  bytecode. Native sessions support `--run` normally); `Unix.fork` and Eio
775
835
  fibers are out of scope (only OCaml 5 domains are presented as threads).
776
836
 
837
+ ### Go
838
+
839
+ `tdb` debugs Go through [Delve](https://github.com/go-delve/delve)'s own
840
+ DAP server, `dlv dap` (Delve ≥ 1.21). Install it with:
841
+
842
+ ```bash
843
+ go install github.com/go-delve/delve/cmd/dlv@latest
844
+ ```
845
+
846
+ `dlv` must be on `PATH`, or point tdb at it with
847
+ `{"adapters": {"dlv": "/path/to/dlv"}}` in `config.json`.
848
+
849
+ **Launch modes** — `tdb` picks the right Delve mode automatically:
850
+
851
+ ```bash
852
+ tdb main.go # source file: `go run`-style debug mode
853
+ tdb ./pkg # package directory: `go run`-style debug mode
854
+ tdb ./binary # pre-built Go binary (buildinfo-detected): exec mode
855
+ tdb --test ./pkg # Go test package: Delve test mode
856
+ tdb -a PID # attach to a running local Go process by pid
857
+ tdb -r host:port --lang go # attach to a `dlv dap --listen` already running remotely
858
+ ```
859
+
860
+ `tdb --test ./pkg` debugs the package's tests under Delve's `test` mode;
861
+ pass test-binary flags after `--`, e.g. `tdb --test ./pkg -- -run TestFoo`.
862
+ `-a`/`--attach` currently supports Go only; on Linux, and only on Linux,
863
+ `tdb` can identify the target as Go from `/proc/PID/exe`'s buildinfo
864
+ without `--lang` (elsewhere pass `--lang go` alongside `-a`). Attaching
865
+ stops the process immediately so you get control right away.
866
+
867
+ **Remote attach:** start Delve's own DAP server against your program
868
+ first —
869
+
870
+ ```bash
871
+ dlv dap --listen=host:port ./binary
872
+ ```
873
+
874
+ — then connect from `tdb`:
875
+
876
+ ```bash
877
+ tdb -r host:port --lang go
878
+ ```
879
+
880
+ Do not expose the port to an untrusted network; prefer an SSH tunnel.
881
+
882
+ **Goroutine inspection:** in a Go session the Threads action (`Alt+T`, or
883
+ the **Goroutines (N)** menu label) opens the Goroutines workspace while
884
+ the debuggee is stopped — a fresh, bounded snapshot of goroutines, wait
885
+ edges, and findings, the same shape as the Rust Concurrency workspace.
886
+ Goroutines parked entirely in Go-runtime internals (GC workers,
887
+ finalizers, netpoll) are hidden by default; press `a` to reveal them
888
+ alongside the rest. The **Wait Graph** tab groups goroutines by the
889
+ channel, mutex, or `WaitGroup` they're blocked on; the **Findings** tab
890
+ surfaces stuck channels, mutex convoys, and likely leaks with a
891
+ confidence level (`confirmed` vs `probable`) — Go mutexes never record an
892
+ owner, so, unlike Python's Lock/Semaphore inspector, findings can name
893
+ what's blocked but not who holds it. The snapshot collects up to 150
894
+ goroutines; a busier program reports the header
895
+ `Goroutines (150) — N more not collected` rather than silently truncating.
896
+ Refresh (`r`) after the next stop to collect a new snapshot.
897
+
898
+ **Limitations (v1):** `--terminal` is not supported (`dlv dap` has no
899
+ terminal-routing mode); a goroutine parked in a `select` is classified but
900
+ contributes no wait edge (Delve can't tell which case it's waiting on);
901
+ no mutex-holder identification (impossible for Go's runtime); goroutines
902
+ spawned indirectly via `exec.Command` child OS processes are not
903
+ automatically attached; buildinfo-based binary detection scans only the
904
+ first 16MB of the file (`--lang go` overrides for anything larger); `-a`'s
905
+ `/proc`-based language auto-detection is Linux-only (pass `--lang go`
906
+ alongside `-a` on macOS/Windows).
907
+
908
+ ### PowerShell
909
+
910
+ tdb debugs PowerShell 7 scripts through [PowerShell Editor Services](https://github.com/PowerShell/PowerShellEditorServices)
911
+ (PSES), the Debug Adapter Protocol server behind the VS Code PowerShell
912
+ extension. You need `pwsh` (>= 7.2; https://aka.ms/powershell) on PATH
913
+ and the PSES module. tdb looks for the module in this order:
914
+
915
+ 1. `{"adapters": {"pses": "/path/to/PowerShellEditorServices"}}` in `config.json`
916
+ 2. the `TDB_PSES_PATH` environment variable
917
+ 3. the copy bundled with an installed VS Code PowerShell extension
918
+ (`~/.vscode/extensions/ms-vscode.powershell-*/modules/PowerShellEditorServices`)
919
+
920
+ Without VS Code, download the release once:
921
+
922
+ ```bash
923
+ mkdir -p ~/.local/share/tdb/pses && cd ~/.local/share/tdb/pses
924
+ curl -sLO https://github.com/PowerShell/PowerShellEditorServices/releases/download/v4.7.0/PowerShellEditorServices.zip
925
+ unzip -q PowerShellEditorServices.zip
926
+ export TDB_PSES_PATH=~/.local/share/tdb/pses/PowerShellEditorServices
927
+ ```
928
+
929
+ A `pwsh` that isn't on PATH can be named with `{"adapters": {"pwsh": "/path/to/pwsh"}}`.
930
+
931
+ ```bash
932
+ tdb script.ps1 # launch, stop at the first statement
933
+ tdb --run script.ps1 # run immediately, debug on demand (pause works)
934
+ ```
935
+
936
+ Notes:
937
+
938
+ - Entry stop lands on the first statement of your script (comments and
939
+ function definitions are skipped): PSES has no native `stopOnEntry` for
940
+ this launch shape, so tdb sets a synthetic breakpoint on the bundled
941
+ launcher, steps in, and clears it — no line-1 breakpoint appears in the
942
+ Breakpoint view.
943
+ - The script runs inside that small bundled launcher (`tdb_launch.ps1`) so
944
+ tdb can report the script's exit code. In the Stack view, top to bottom:
945
+ PSES's `<Breakpoint>` marker frame (the frame to inspect — it tracks the
946
+ accurate current position), your script's own frames, the launcher's
947
+ `<ScriptBlock>`, and a source-less `Interactive Session` frame at the
948
+ bottom.
949
+ - Stepping is statement-granular, as in VS Code: a line containing a
950
+ `$( ... )` subexpression takes two `next` presses to clear, and `stepIn`
951
+ first lands on the called function's declaration line before its body.
952
+ - An uncaught terminating error ends the script with exit code 1 and opens
953
+ the fatal-error modal from pwsh's concise error text. Non-terminating
954
+ errors (`Write-Error`, failing cmdlets without `-ErrorAction Stop`) print
955
+ the same concise error block to the Console as stderr and let the script
956
+ continue with exit code 0, as in pwsh itself; there is no break-on-error
957
+ yet. The error block names the launcher's `& $Script` line rather than
958
+ your script's actual line for some error kinds (a known cosmetic
959
+ limitation).
960
+ - The fatal-error modal is gated on a non-zero exit code, since pwsh prints
961
+ the identical block for terminating and non-terminating errors. A script
962
+ that reports a non-terminating error and then `exit 3` can therefore still
963
+ raise the modal, showing the last error block that names a file of yours;
964
+ blocks attributed to tdb's own `tdb_launch.ps1` are skipped, so a bare
965
+ `Write-Error` before the `exit` opens no modal at all.
966
+ - `--terminal` and remote attach are not supported for PowerShell yet.
967
+ - Windows PowerShell 5.1 is not supported (pwsh 7 only). Running tdb *on*
968
+ Windows against pwsh is designed for but not yet verified (experimental).
969
+
777
970
  ## Layout
778
971
 
779
972
  ```
@@ -900,7 +1093,9 @@ stops on each physical line--useful for inspecting how a complex expression is b
900
1093
  The choice is saved to `~/.config/tdb/config.json`.
901
1094
 
902
1095
  Statement mode requires a source-language model and is currently Python-only;
903
- other languages always step per line (the Step Mode menu says so if you try).
1096
+ other languages step at their debugger's native granularity — per line,
1097
+ except PowerShell, whose debugger stops per statement, see
1098
+ [PowerShell](#powershell) (the Step Mode menu says so if you try).
904
1099
 
905
1100
  ### Breakpoints
906
1101
 
@@ -1266,12 +1461,14 @@ sessions via `--adapter lldb-dap`. Neither `gdb -i dap` (the default C/C++
1266
1461
  adapter, and an alternate for native OCaml) nor `ocamlearlybird` (the
1267
1462
  bytecode OCaml adapter) has any terminal integration -- `tdb` rejects
1268
1463
  `--terminal` up front for both, with an error pointing at `--adapter
1269
- lldb-dap` instead. `--terminal` also only applies when `tdb` launches the
1270
- program itself; it is rejected for remote-attach (`-r`), since there's no
1271
- program for `tdb` to spawn a terminal around.
1464
+ lldb-dap` instead. Go and PowerShell don't support `--terminal` either
1465
+ (`dlv dap` has no terminal-routing mode; PSES likewise). `--terminal` also
1466
+ only applies when `tdb` launches the program itself; it is rejected for
1467
+ remote-attach (`-r`), since there's no program for `tdb` to spawn a
1468
+ terminal around.
1272
1469
 
1273
1470
  The debuggee runs in a separate window of the specified terminal, including
1274
- all of its stdin/stdout/stderr -- keyboard input, program output, and
1471
+ all of its stdin/stdout/stderr. Keyboard input, program output, and
1275
1472
  anything the program itself draws to the terminal all happen in that
1276
1473
  window, not in `tdb`'s Console View. Supported choices:
1277
1474
  `xterm`, `konsole`, `gnome-terminal`, `ghostty`, `kitty`, `iterm2`, `warp`,
@@ -1309,10 +1506,58 @@ tdb --run my_program.py; echo $? # my_program.py's own exit code
1309
1506
  **Interrupting:** press Ctrl-C in the terminal (any platform), or, on Unix, send
1310
1507
  `SIGUSR1` to `tdb`'s pid from another terminal: `kill -USR1 <tdb pid>`. Either one pauses
1311
1508
  the debuggee at its currently executing line and opens the TUI with full debugging
1312
- available -- breakpoints, stepping, variable inspection, the evaluate console. The
1509
+ available (breakpoints, stepping, variable inspection, the evaluate console). The
1313
1510
  debuggee itself never receives the signal (`tdb`'s adapter runs in its own process
1314
1511
  group), so its own `SIGINT` handling is undisturbed.
1315
1512
 
1513
+ **Examining without opening the TUI:** press `Ctrl-\` in the terminal (`Ctrl-Break` on
1514
+ Windows), or, on Unix, send `SIGUSR2` to `tdb`'s pid: `kill -USR2 <tdb pid>`.
1515
+
1516
+ **Windows caveat:** the Ctrl-Break trigger is untested. Unlike Ctrl-C, a console
1517
+ Ctrl-Break is delivered to every process attached to the console, including the debug
1518
+ adapter and the program itself, so it may terminate the program instead of snapshotting
1519
+ it. There is no signal-based alternative on Windows.
1520
+
1521
+ `tdb` pauses the program, writes one JSON line describing the call stack of every thread
1522
+ (plus asyncio tasks and multiprocessing children for Python, goroutines for Go, and
1523
+ the concurrency snapshot for Rust), and resumes the program. Ctrl-C behavior is
1524
+ unchanged. By default the line goes to stdout; `--examine-log DEST` sends it to a file
1525
+ instead (`-` means stdout; repeat the flag to write to several places):
1526
+
1527
+ ```bash
1528
+ tdb --run --examine-log hang.jsonl my_program.py # file only, appended
1529
+ tdb --run --examine-log - --examine-log hang.jsonl my_program.py # both
1530
+ ```
1531
+
1532
+ Each record looks like (abridged):
1533
+
1534
+ ```json
1535
+ {"schema":1,"seq":1,"trigger":"SIGQUIT","status":"ok",
1536
+ "requested_at":"2026-09-07T14:02:11.482-07:00","landed_at":"2026-09-07T14:02:11.511-07:00",
1537
+ "elapsed_s":87.3,"language":"python","program":"/home/al/work/app.py",
1538
+ "processes":[{"pid":41213,"role":"parent","name":null,
1539
+ "threads":[{"id":1,"name":"MainThread","frames":[
1540
+ {"function":"wait","file":"/usr/lib/python3.13/threading.py","line":359},
1541
+ {"function":"main","file":"/home/al/work/app.py","line":42}]}],
1542
+ "tasks":[{"name":"worker-2","state":"PENDING","awaiting":"Lock.acquire","frames":["run (/home/al/work/app.py:18)"]}]}],
1543
+ "errors":[]}
1544
+ ```
1545
+
1546
+ Frames are innermost first. `tasks` appears only for Python programs with live asyncio
1547
+ tasks; `goroutines` only for Go; `rust_concurrency` only for Rust. `status` is
1548
+ `"pending"` when the pause could not land within a few seconds (the program is blocked
1549
+ inside a single call) -- a second record with the same `seq` and `status: "ok"` follows
1550
+ when it does land. If you press Ctrl-C or the program exits before it lands, no
1551
+ completing record is written. `errors` lists any sub-collector that failed, so a partial
1552
+ record is explicit about what is missing. A stderr line confirms each capture and where
1553
+ it went.
1554
+
1555
+ Narrow a long capture with `jq`, for example only threads with a frame in your own code:
1556
+
1557
+ ```bash
1558
+ jq -c '.processes[].threads[] | select(any(.frames[]; .file != null and (.file|test("/work/"))))' hang.jsonl
1559
+ ```
1560
+
1316
1561
  **Quitting an adopted session** adds a third choice to the usual quit dialog:
1317
1562
  - `d` (or `q`) -- detach and resume: the program keeps running headlessly; interrupt it
1318
1563
  again later the same way.
@@ -1327,18 +1572,61 @@ nonzero `sys.exit()`, which `debugpy` treats as an uncaught `SystemExit` -- `tdb
1327
1572
  the TUI at the point of failure instead of exiting silently, so you can inspect the
1328
1573
  crash. Exit-code passthrough applies only to a clean exit during the headless phase.
1329
1574
 
1330
- Supported languages: Python, Perl, Bash, Tcsh, Ruby, C/C++ (both `gdb` and
1331
- `lldb-dap`), and native OCaml (both `gdb` and `lldb-dap`; bytecode/
1332
- `ocamlearlybird` sessions don't support pause, so `--run` is unavailable
1333
- for those). `--run` is rejected up front for any other language.
1575
+ Supported languages: Python, Perl, Bash, Tcsh, Ruby, PowerShell 7, C/C++
1576
+ (both `gdb` and `lldb-dap`), and native OCaml (both `gdb` and `lldb-dap`;
1577
+ bytecode/`ocamlearlybird` sessions don't support pause, so `--run` is
1578
+ unavailable for those). `--run` is rejected up front for any other
1579
+ language.
1334
1580
 
1335
1581
  **Limitation:** pausing is cooperative. If the debuggee is blocked inside a single
1336
1582
  blocking external call or syscall, the pause can't land until that call returns --
1337
1583
  `tdb` prints a notice and opens the TUI once the stop actually arrives.
1338
1584
 
1339
- **Incompatible flags:** `-r`, `-k`/`-t`, `--record`, `--replay`, `--server`,
1585
+ **Incompatible flags:** `-r`, `-k`/`-t`, `--record`, `--replay`, `--replay-tui`, `--server`,
1340
1586
  `--headless`, `--mcp`, `--terminal`.
1341
1587
 
1588
+ ### Eval Mode (`-e`/`--eval`)
1589
+
1590
+ Eval mode automates a common debugging loop--set a breakpoint, run to it,
1591
+ evaluate an expression in the Evaluate console, continue--without
1592
+ opening the TUI. `-e` takes two arguments, a location and an expression:
1593
+
1594
+ ```bash
1595
+ tdb --eval examples/digits_of_pi.py:25 "print(f'{nr=}')" examples/digits_of_pi.py
1596
+ ```
1597
+
1598
+ sets a breakpoint at line 25, runs the program, and each time that line is
1599
+ reached evaluates `print(f'{nr=}')` in the stopped frame, then continues.
1600
+ Program output and evaluation output both stream to the terminal; when the
1601
+ program exits, `tdb` exits with the same code (and with `1` if the session
1602
+ ends without one--as with a crash or killed adapter--so a scripted `tdb -e ...
1603
+ && next` can trust the exit status). `Ctrl-C` stops the program and exits
1604
+ `130`. Under `multiprocessing`, an eval point placed in child code is
1605
+ evaluated in each child process that reaches it.
1606
+
1607
+ - The location is `FILE:LINE`, or a bare `LINE` of the program being debugged,
1608
+ and is snapped to the start of a logical statement just like `-k`.
1609
+ - `-e` may be repeated to plant several eval points in one run.
1610
+ - The expression is evaluated through the debug adapter (DAP `evaluate`,
1611
+ context `repl`), just like the Evaluate console: side effects are real
1612
+ and persist in the running program, for example
1613
+ `-e my_program.py:25 "limit = 10"` changes `limit` from line 25 onward.
1614
+ - A bare expression's result is printed to stdout; statement-style
1615
+ expressions (`print(...)`, assignments) print nothing beyond their own
1616
+ output. An expression error is reported on stderr and the run continues --
1617
+ a typo isn't fatal, matching the Evaluate console.
1618
+ - Saved breakpoints are ignored, and any other stop (e.g. an
1619
+ uncaught-exception stop) is continued untouched, so the program runs to
1620
+ completion unattended.
1621
+ - For non-Python debuggees the expression must be in the debug adapter's
1622
+ own syntax, for example gdb's `print x` / `set var x=3` for C/C++, and not
1623
+ necessarily the debuggee's language. The adapter may bind the breakpoint
1624
+ to the next executable line (gdb moves one off a declaration or brace);
1625
+ the eval point matches that bound line, so the expression still runs.
1626
+
1627
+ **Incompatible flags:** `--run`, `-r`, `-a`, `-k`/`-t`, `--record`,
1628
+ `--replay`, `--replay-tui`, `--server`, `--headless`, `--mcp`, `--terminal`.
1629
+
1342
1630
  ### Keybinding Schemes
1343
1631
 
1344
1632
  ```bash
@@ -1459,14 +1747,29 @@ written to `session.jsonl` as
1459
1747
  JSON-RPC commands. Works with launch mode (any language) and `-r`
1460
1748
  remote attach.
1461
1749
 
1462
- Replay it two ways:
1463
-
1750
+ Replay it three ways:
1751
+
1752
+ - `tdb --replay-tui session.jsonl` opens the normal TUI on the recorded
1753
+ program and performs every recorded action in front of you, at the
1754
+ recorded pace: breakpoints appear in the Code View, steps and continues
1755
+ move the cursor, Evaluate entries echo into the console with their
1756
+ results, stack navigation and variable expansion play out in their
1757
+ panels. The title bar shows progress (`tdb ▶ session.jsonl 3/11`) and a
1758
+ toast announces each action (`--replay-quiet` turns those toasts off).
1759
+ A failed action raises an error toast and replay continues. When the recording does not end with `quit`, the
1760
+ debugger stays open at the final stop for you to carry on by hand.
1761
+ `--replay-interval S` waits a fixed S seconds before each action
1762
+ instead of reproducing the recorded gaps. `--replay-timeout S` bounds
1763
+ each stop-wait (default 30 s). Exit code 0
1764
+ iff every action succeeded. Don't type into the TUI while it is
1765
+ replaying: stray keystrokes interleave with the recorded actions.
1464
1766
  - `tdb --replay session.jsonl` launches the recorded
1465
1767
  program headless, feeds every recorded command through the same RPC
1466
1768
  dispatch `tdb --server` uses, and prints a transcript (recorded time,
1467
1769
  command, verbatim result, interleaved program output). Exit code 0 iff
1468
1770
  every command succeeded. Add `--timing` to reproduce the original
1469
- pacing, `--replay-timeout S` to bound each stop-wait (default 30 s).
1771
+ pacing or `--replay-interval S` for a fixed delay before each command,
1772
+ `--replay-timeout S` to bound each stop-wait (default 30 s).
1470
1773
  - Against a live server: start `tdb --server prog.py`, then feed line 2
1471
1774
  onward of the file to `POST /rpc` . Each line is a valid request body:
1472
1775
 
@@ -1483,9 +1786,10 @@ lists), breakpoint enable/disable toggles, variable expansions when the
1483
1786
  adapter reports no `evaluateName` (currently the Perl adapter), and
1484
1787
  File > Open program switches.
1485
1788
 
1486
- ## MCP Integration
1789
+ ## MCP v2 Integration
1487
1790
 
1488
- tdb ships a Model Context Protocol (MCP) server (`tdb-mcp`) that exposes
1791
+ tdb ships a Model Context Protocol (MCP), version 2,
1792
+ server (`tdb-mcp`) that exposes
1489
1793
  the debugger as a curated set of tools an AI agent can call. The MCP
1490
1794
  server is a third in-process consumer of the same dispatch handlers the
1491
1795
  TUI and the HTTP server use so an agent gets the same lock semantics,
@@ -1583,7 +1887,7 @@ usage: tdb [-h] [-v] [-r [HOST:]PORT] [--cwd CWD] [--no-stop-on-entry]
1583
1887
  [--keybindings {default,vim,emacs}]
1584
1888
  [--terminal {xterm,konsole,gnome-terminal,ghostty,kitty,iterm2,warp,wezterm,terminator}]
1585
1889
  [--local-root PATH] [--remote-root PATH]
1586
- [--server] [--headless] [-k FILE:LINE|LINE] [--server-port SERVER_PORT] [-d] [--doc-text]
1890
+ [--server] [--headless] [-k FILE:LINE|LINE] [--server-port SERVER_PORT] [-d] [--doc-text] [--info]
1587
1891
  [program] [args ...]
1588
1892
  ```
1589
1893
 
@@ -1610,6 +1914,14 @@ usage: tdb [-h] [-v] [-r [HOST:]PORT] [--cwd CWD] [--no-stop-on-entry]
1610
1914
  | `--server` | Enable JSON-RPC server alongside TUI |
1611
1915
  | `--headless` | JSON-RPC server only, no TUI |
1612
1916
  | `--server-port PORT` | Server port (default: 8150) |
1917
+ | `--record FILE` | Record this TUI session's debugging actions to FILE (JSON-RPC lines) |
1918
+ | `--replay FILE` | Replay a recording headless, printing a transcript (no program argument) |
1919
+ | `--replay-tui FILE` | Replay a recording inside the TUI at the recorded pace, so you can watch it |
1920
+ | `--replay-quiet` | With `--replay-tui`: no per-action toasts (errors and the final summary still show) |
1921
+ | `--timing` | With `--replay`: reproduce the recorded pacing (`--replay-tui` always does) |
1922
+ | `--replay-interval S` | With `--replay`/`--replay-tui`: fixed S-second delay before each action instead of the recorded gaps |
1923
+ | `--replay-timeout S` | With `--replay`/`--replay-tui`: per-action stop-wait timeout (default 30) |
1924
+ | `--examine-log DEST` | With --run: write each Ctrl-\ / SIGUSR2 stack snapshot to DEST (- = stdout; repeatable) |
1613
1925
 
1614
1926
  ## Configuration
1615
1927
 
@@ -1634,6 +1946,10 @@ found; it's Python code, not an external executable), so
1634
1946
  `{"adapters": {"perl": "/path/to/perl"}}`. Use this when the `perl` on
1635
1947
  `PATH` is too old (< 5.18) or you need a specific `perlbrew`/`plenv` version.
1636
1948
 
1949
+ **PowerShell** follows the same pattern with two keys: `adapters.pwsh` names
1950
+ the interpreter and `adapters.pses` names the PowerShell Editor Services
1951
+ module directory (see [PowerShell](#powershell)).
1952
+
1637
1953
  Breakpoints are saved on exit and restored when debugging a program in the same
1638
1954
  directory. Each project's breakpoints are independent. Breakpoints set with
1639
1955
  `-t`/`--to-line` are the exception: they behave like `-k` breakpoints during
@@ -1666,6 +1982,28 @@ fires as expected.
1666
1982
 
1667
1983
  MIT
1668
1984
 
1985
+ ## Acknowledgments
1986
+
1987
+ Thank you:
1988
+
1989
+ - Will McGugan for the amazing `textual` module.
1990
+ `tdb` would be a pale shadow of itself had I used any other TUI framework.
1991
+ Fantastic work, Will.
1992
+
1993
+ - Microsoft for the Debug Adapter Protocol (DAP) and releasing
1994
+ its implementation in `debugpy` and the Python Debugger extension for Visual Studio Code
1995
+ as open source.
1996
+
1997
+ - Anthropic, for providing access to Claude Code through the
1998
+ [Claude for Open Source](https://claude.com/contact-sales/claude-for-oss) program.
1999
+ `tdb` was made almost entirely with Claude Code.
2000
+
2001
+ - OpenAI, for providing access to Codex through the
2002
+ [Codex for Open Source](https://developers.openai.com/community/codex-for-oss) program.
2003
+
2004
+ This project was inspired by Andreas Klöckner's excellent [pudb](https://pypi.org/project/pudb/)
2005
+ Python debugger.
2006
+
1669
2007
 
1670
2008
  ## Known Problems
1671
2009