pentool 1.0.0__py3-none-any.whl

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 (169) hide show
  1. pentool/__init__.py +4 -0
  2. pentool/__main__.py +17 -0
  3. pentool/api/__init__.py +45 -0
  4. pentool/api/base_api.py +52 -0
  5. pentool/api/comparer_api.py +23 -0
  6. pentool/api/decoder_api.py +28 -0
  7. pentool/api/intruder_api.py +209 -0
  8. pentool/api/proxy_api.py +319 -0
  9. pentool/api/repeater_api.py +118 -0
  10. pentool/api/scanner_api.py +336 -0
  11. pentool/api/sequencer_api.py +21 -0
  12. pentool/api/spider_api.py +126 -0
  13. pentool/api/target_api.py +121 -0
  14. pentool/cli/__init__.py +1 -0
  15. pentool/cli/main.py +100 -0
  16. pentool/cli/project.py +90 -0
  17. pentool/cli/proxy.py +185 -0
  18. pentool/cli/scan.py +118 -0
  19. pentool/core/__init__.py +1 -0
  20. pentool/core/config.py +184 -0
  21. pentool/core/crash_reporter.py +75 -0
  22. pentool/core/database.py +139 -0
  23. pentool/core/event_bus.py +232 -0
  24. pentool/core/events.py +164 -0
  25. pentool/core/features.py +410 -0
  26. pentool/core/license.py +294 -0
  27. pentool/core/logging.py +59 -0
  28. pentool/core/plugin_manager.py +332 -0
  29. pentool/core/project.py +37 -0
  30. pentool/core/storage_interface.py +311 -0
  31. pentool/core/updater.py +96 -0
  32. pentool/core/utils.py +61 -0
  33. pentool/modules/__init__.py +1 -0
  34. pentool/modules/comparer.py +177 -0
  35. pentool/modules/decoder.py +281 -0
  36. pentool/modules/intruder.py +432 -0
  37. pentool/modules/intruder_turbo.py +190 -0
  38. pentool/modules/match_replace.py +146 -0
  39. pentool/modules/proxy.py +803 -0
  40. pentool/modules/repeater.py +237 -0
  41. pentool/modules/scanner/__init__.py +7 -0
  42. pentool/modules/scanner/ai_analyzer.py +230 -0
  43. pentool/modules/scanner/base.py +173 -0
  44. pentool/modules/scanner/baseline.py +101 -0
  45. pentool/modules/scanner/checks/__init__.py +47 -0
  46. pentool/modules/scanner/checks/broken_auth.py +231 -0
  47. pentool/modules/scanner/checks/cors.py +209 -0
  48. pentool/modules/scanner/checks/dom_xss.py +114 -0
  49. pentool/modules/scanner/checks/graphql.py +110 -0
  50. pentool/modules/scanner/checks/header_injection.py +262 -0
  51. pentool/modules/scanner/checks/headers.py +75 -0
  52. pentool/modules/scanner/checks/helpers.py +358 -0
  53. pentool/modules/scanner/checks/info_leak.py +68 -0
  54. pentool/modules/scanner/checks/jwt_none.py +238 -0
  55. pentool/modules/scanner/checks/lfi.py +186 -0
  56. pentool/modules/scanner/checks/nosql_injection.py +92 -0
  57. pentool/modules/scanner/checks/oauth.py +123 -0
  58. pentool/modules/scanner/checks/open_redirect.py +168 -0
  59. pentool/modules/scanner/checks/path_traversal.py +229 -0
  60. pentool/modules/scanner/checks/prototype_pollution.py +88 -0
  61. pentool/modules/scanner/checks/rce.py +338 -0
  62. pentool/modules/scanner/checks/sensitive_data.py +74 -0
  63. pentool/modules/scanner/checks/sqli.py +546 -0
  64. pentool/modules/scanner/checks/ssrf.py +273 -0
  65. pentool/modules/scanner/checks/ssti.py +353 -0
  66. pentool/modules/scanner/checks/xss.py +622 -0
  67. pentool/modules/scanner/checks/xxe.py +240 -0
  68. pentool/modules/scanner/engine.py +407 -0
  69. pentool/modules/scanner/fingerprint.py +169 -0
  70. pentool/modules/scanner/helpers.py +94 -0
  71. pentool/modules/scanner/mutator.py +378 -0
  72. pentool/modules/scanner/oob.py +104 -0
  73. pentool/modules/scanner/passive.py +114 -0
  74. pentool/modules/scanner/payloads.py +224 -0
  75. pentool/modules/scanner/report.py +117 -0
  76. pentool/modules/sequencer.py +500 -0
  77. pentool/modules/spider.py +702 -0
  78. pentool/modules/target.py +190 -0
  79. pentool/modules/websocket_handler.py +259 -0
  80. pentool/plugins/__init__.py +7 -0
  81. pentool/plugins/builtin/__init__.py +1 -0
  82. pentool/plugins/builtin/payloads_pro.py +404 -0
  83. pentool/plugins/builtin/reports_pro.py +450 -0
  84. pentool/plugins/builtin/scanner_pro.py +308 -0
  85. pentool/plugins/example_plugin.py +75 -0
  86. pentool/project_to_txt.py +67 -0
  87. pentool/services/__init__.py +1 -0
  88. pentool/services/base_service.py +64 -0
  89. pentool/services/intruder_service.py +108 -0
  90. pentool/services/proxy_service.py +232 -0
  91. pentool/services/repeater_service.py +111 -0
  92. pentool/services/scan_service.py +340 -0
  93. pentool/storage/__init__.py +0 -0
  94. pentool/storage/http_storage.py +522 -0
  95. pentool/storage/large_body_handler.py +54 -0
  96. pentool/storage/lru_cache.py +49 -0
  97. pentool/t.py +213 -0
  98. pentool/tui/__init__.py +1 -0
  99. pentool/tui/app.py +1438 -0
  100. pentool/tui/constants.py +79 -0
  101. pentool/tui/dialogs/__init__.py +0 -0
  102. pentool/tui/dialogs/cert_dialog.py +81 -0
  103. pentool/tui/dialogs/file_selector.py +227 -0
  104. pentool/tui/dialogs/load_from_proxy.py +125 -0
  105. pentool/tui/dialogs/match_replace_dialog.py +229 -0
  106. pentool/tui/dialogs/project_dialog.py +82 -0
  107. pentool/tui/dialogs/scope_dialog.py +225 -0
  108. pentool/tui/messages.py +121 -0
  109. pentool/tui/mixins/__init__.py +0 -0
  110. pentool/tui/mixins/app_mixin.py +61 -0
  111. pentool/tui/mixins/dialog_cancel.py +25 -0
  112. pentool/tui/mixins/request_context_menu.py +285 -0
  113. pentool/tui/mixins/tab_rename.py +101 -0
  114. pentool/tui/screens/__init__.py +31 -0
  115. pentool/tui/screens/comparer/__init__.py +3 -0
  116. pentool/tui/screens/comparer/screen.py +233 -0
  117. pentool/tui/screens/dashboard/__init__.py +2 -0
  118. pentool/tui/screens/dashboard/live_dashboard.py +741 -0
  119. pentool/tui/screens/dashboard/screen.py +820 -0
  120. pentool/tui/screens/decoder/__init__.py +3 -0
  121. pentool/tui/screens/decoder/screen.py +311 -0
  122. pentool/tui/screens/extensions/__init__.py +3 -0
  123. pentool/tui/screens/extensions/screen.py +24 -0
  124. pentool/tui/screens/intruder/__init__.py +3 -0
  125. pentool/tui/screens/intruder/screen.py +1362 -0
  126. pentool/tui/screens/proxy/__init__.py +3 -0
  127. pentool/tui/screens/proxy/screen.py +1664 -0
  128. pentool/tui/screens/repeater/__init__.py +3 -0
  129. pentool/tui/screens/repeater/screen.py +646 -0
  130. pentool/tui/screens/scanner/__init__.py +3 -0
  131. pentool/tui/screens/scanner/screen.py +1984 -0
  132. pentool/tui/screens/sequencer/__init__.py +3 -0
  133. pentool/tui/screens/sequencer/screen.py +520 -0
  134. pentool/tui/screens/settings/__init__.py +5 -0
  135. pentool/tui/screens/settings/hotkeys.py +134 -0
  136. pentool/tui/screens/settings/screen.py +543 -0
  137. pentool/tui/screens/spider/__init__.py +3 -0
  138. pentool/tui/screens/spider/screen.py +385 -0
  139. pentool/tui/screens/target/__init__.py +4 -0
  140. pentool/tui/screens/target/screen.py +361 -0
  141. pentool/tui/screens/terminal/__init__.py +5 -0
  142. pentool/tui/screens/terminal/screen.py +181 -0
  143. pentool/tui/widgets/__init__.py +1 -0
  144. pentool/tui/widgets/context_menu.py +148 -0
  145. pentool/tui/widgets/filter_bar.py +206 -0
  146. pentool/tui/widgets/inspector_panel.py +112 -0
  147. pentool/tui/widgets/menu.py +86 -0
  148. pentool/tui/widgets/menu_bar.py +238 -0
  149. pentool/tui/widgets/module_tabs.py +73 -0
  150. pentool/tui/widgets/payload_drop_zone.py +66 -0
  151. pentool/tui/widgets/request_editor.py +504 -0
  152. pentool/tui/widgets/resize_handle.py +116 -0
  153. pentool/tui/widgets/search_bar.py +113 -0
  154. pentool/tui/widgets/statusbar.py +99 -0
  155. pentool/tui/widgets/toolbar_button.py +76 -0
  156. pentool/utils/__init__.py +1 -0
  157. pentool/utils/cert.py +361 -0
  158. pentool/utils/coder.py +170 -0
  159. pentool/utils/copy_as.py +251 -0
  160. pentool/utils/diff.py +91 -0
  161. pentool/utils/http_client.py +175 -0
  162. pentool/utils/parser.py +227 -0
  163. pentool/utils/terminal_check.py +32 -0
  164. pentool-1.0.0.dist-info/METADATA +155 -0
  165. pentool-1.0.0.dist-info/RECORD +169 -0
  166. pentool-1.0.0.dist-info/WHEEL +5 -0
  167. pentool-1.0.0.dist-info/entry_points.txt +2 -0
  168. pentool-1.0.0.dist-info/licenses/LICENSE +34 -0
  169. pentool-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,3 @@
1
+ from pentool.tui.screens.decoder.screen import DecoderScreen
2
+
3
+ __all__ = ["DecoderScreen"]
@@ -0,0 +1,311 @@
1
+ """Экран Decoder/Encoder — кодирование, декодирование, хэширование."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ from textual import on
7
+ from textual.app import ComposeResult
8
+ from textual.binding import Binding
9
+ from textual.containers import Horizontal, Vertical
10
+ from textual.widget import Widget
11
+ from textual.widgets import Label, RichLog, Static, TextArea
12
+
13
+ from pentool.tui.widgets.toolbar_button import ToolbarButton
14
+ from pentool.tui.widgets.resize_handle import ResizeHandle
15
+ from pentool.core.logging import get_logger
16
+
17
+ logger = get_logger(__name__)
18
+
19
+ _CSS = (Path(__file__).parent / "screen.tcss").read_text(encoding="utf-8")
20
+
21
+
22
+ class DecoderScreen(Widget):
23
+ """Encoder / Decoder с цепочками операций."""
24
+
25
+ DEFAULT_CSS = _CSS
26
+
27
+ BINDINGS = [
28
+ Binding("ctrl+enter", "run_chain", "Run", show=True),
29
+ Binding("ctrl+l", "clear_all", "Clear", show=False),
30
+ Binding("ctrl+c", "copy_result","Copy", show=False),
31
+ ]
32
+
33
+ def __init__(self, **kwargs) -> None:
34
+ super().__init__(**kwargs)
35
+ from pentool.api.decoder_api import OP_LABELS
36
+ self._op_labels: list[str] = OP_LABELS
37
+ self._selected_op: str = OP_LABELS[0] # текущая выбранная операция
38
+ self._chain: list[str] = [] # список операций в цепочке
39
+
40
+ def compose(self) -> ComposeResult:
41
+ from pentool.api.decoder_api import OP_LABELS
42
+
43
+ # ── Тулбар ─────────────────────────────────────────────────────────────
44
+ with Horizontal(id="dec-toolbar"):
45
+ yield ToolbarButton("▶ Run", "btn-dec-run")
46
+ yield Static(" │ ", classes="toolbar-sep")
47
+ yield ToolbarButton("+ Add Step", "btn-dec-add")
48
+ yield Static(" │ ", classes="toolbar-sep")
49
+ yield ToolbarButton("✗ Clear Chain","btn-dec-clrchain")
50
+ yield Static(" │ ", classes="toolbar-sep")
51
+ yield ToolbarButton("⇅ Swap I/O", "btn-dec-swap")
52
+ yield Static(" │ ", classes="toolbar-sep")
53
+ yield ToolbarButton("📋 Copy", "btn-dec-copy")
54
+ yield Static(" │ ", classes="toolbar-sep")
55
+ yield ToolbarButton("🔍 Smart", "btn-dec-smart")
56
+
57
+ # ── Operation selector (добавление шагов) ──────────────────────────────
58
+ with Horizontal(id="dec-op-row"):
59
+ yield Label("Operation:", id="dec-op-label")
60
+ yield ToolbarButton(f"{self._selected_op} ▼", "btn-dec-op-select")
61
+ yield Label(" Chain:", id="dec-chain-label")
62
+ yield Static("(empty)", id="dec-chain-display")
63
+
64
+ # ── Рабочая область ─────────────────────────────────────────────────────
65
+ with Horizontal(id="dec-work-area"):
66
+ # Ввод
67
+ with Vertical(id="dec-input-col"):
68
+ yield Static("Input", id="dec-input-label", classes="dec-col-label")
69
+ yield TextArea(id="dec-input", language=None)
70
+
71
+ yield ResizeHandle("dec-input-col", "dec-output-col", id="dec-resize-h")
72
+
73
+ # Вывод
74
+ with Vertical(id="dec-output-col"):
75
+ yield Static("Output", id="dec-output-label", classes="dec-col-label")
76
+ yield TextArea(id="dec-output", language=None)
77
+
78
+ yield ResizeHandle("dec-work-area", "dec-steps-area", vertical=True,
79
+ id="dec-resize-v")
80
+
81
+ # ── Лог шагов цепочки ─────────────────────────────────────────────────
82
+ with Vertical(id="dec-steps-area"):
83
+ yield Static("Chain steps", id="dec-steps-label", classes="dec-col-label")
84
+ yield RichLog(id="dec-steps-log", highlight=True, markup=True,
85
+ wrap=True, max_lines=200)
86
+
87
+ yield Static(
88
+ "Ctrl+Enter: Run │ + Add Step: add operation to chain │ ⇅ Swap: swap Input/Output"
89
+ " │ 📋 Copy: copy result │ 🔍 Smart: auto-detect encoding",
90
+ id="status-bar",
91
+ )
92
+
93
+ # ── Toolbar actions ────────────────────────────────────────────────────────
94
+
95
+ @on(ToolbarButton.Pressed, "#btn-dec-run")
96
+ def on_btn_dec_run(self, _: ToolbarButton.Pressed) -> None:
97
+ self.action_run_chain()
98
+
99
+ @on(ToolbarButton.Pressed, "#btn-dec-add")
100
+ def on_btn_dec_add(self, _: ToolbarButton.Pressed) -> None:
101
+ self._add_step()
102
+
103
+ @on(ToolbarButton.Pressed, "#btn-dec-op-select")
104
+ def on_btn_dec_op_select(self, event: ToolbarButton.Pressed) -> None:
105
+ self._open_op_menu(event.button)
106
+
107
+ @on(ToolbarButton.Pressed, "#btn-dec-clrchain")
108
+ def on_btn_dec_clrchain(self, _: ToolbarButton.Pressed) -> None:
109
+ self._clear_chain()
110
+
111
+ @on(ToolbarButton.Pressed, "#btn-dec-swap")
112
+ def on_btn_dec_swap(self, _: ToolbarButton.Pressed) -> None:
113
+ self._swap_io()
114
+
115
+ @on(ToolbarButton.Pressed, "#btn-dec-copy")
116
+ def on_btn_dec_copy(self, _: ToolbarButton.Pressed) -> None:
117
+ self.action_copy_result()
118
+
119
+ @on(ToolbarButton.Pressed, "#btn-dec-smart")
120
+ def on_btn_dec_smart(self, _: ToolbarButton.Pressed) -> None:
121
+ self._smart_decode()
122
+
123
+ def _open_op_menu(self, btn: ToolbarButton) -> None:
124
+ """Открыть контекстное меню выбора операции."""
125
+ items = [
126
+ (op, ("✓ " if op == self._selected_op else " ") + op)
127
+ for op in self._op_labels
128
+ ]
129
+ r = btn.region
130
+ self.app.show_context_menu(items, r.x, r.y + 1, callback=self._on_op_selected)
131
+
132
+ def _on_op_selected(self, op: str) -> None:
133
+ self._selected_op = op
134
+ try:
135
+ btn = self.query_one("#btn-dec-op-select", ToolbarButton)
136
+ btn.label = f"{op} ▼"
137
+ except Exception:
138
+ pass
139
+
140
+ def _add_step(self) -> None:
141
+ """Добавить выбранную операцию в цепочку."""
142
+ try:
143
+ op = self._selected_op
144
+ if op:
145
+ self._chain.append(op)
146
+ self._update_chain_display()
147
+ except Exception as exc:
148
+ logger.debug("_add_step: %s", exc)
149
+
150
+ def _clear_chain(self) -> None:
151
+ self._chain.clear()
152
+ self._update_chain_display()
153
+ try:
154
+ self.query_one("#dec-steps-log", RichLog).clear()
155
+ except Exception:
156
+ pass
157
+
158
+ def _swap_io(self) -> None:
159
+ try:
160
+ inp = self.query_one("#dec-input", TextArea)
161
+ out = self.query_one("#dec-output", TextArea)
162
+ inp_text = inp.text
163
+ inp.load_text(out.text)
164
+ out.load_text(inp_text)
165
+ except Exception as exc:
166
+ logger.debug("_swap_io: %s", exc)
167
+
168
+ def _smart_decode(self) -> None:
169
+ """Автоопределение и цепочное декодирование ввода."""
170
+ try:
171
+ from pentool.api.decoder_api import decode_smart
172
+ from pentool.modules.decoder import _detect_encoding, encode_op
173
+ inp = self.query_one("#dec-input", TextArea)
174
+ text = inp.text.strip()
175
+ if not text:
176
+ self.app.notify("Input is empty", severity="warning")
177
+ return
178
+
179
+ # Run chain and collect steps for visualization
180
+ current = text
181
+ chain: list[str] = []
182
+ steps: list[str] = [current]
183
+ for _ in range(8):
184
+ op = _detect_encoding(current)
185
+ if op is None:
186
+ break
187
+ try:
188
+ nxt = encode_op(op, current)
189
+ if nxt == current:
190
+ break
191
+ chain.append(op)
192
+ current = nxt
193
+ steps.append(current)
194
+ except Exception:
195
+ break
196
+
197
+ result = current
198
+ self.query_one("#dec-output", TextArea).load_text(result)
199
+ log = self.query_one("#dec-steps-log", RichLog)
200
+ log.clear()
201
+ if chain:
202
+ log.write(f"[bold cyan]Smart decode chain: {' → '.join(chain)}[/bold cyan]")
203
+ log.write("")
204
+ for i, (op, val) in enumerate(zip(["INPUT"] + chain, steps)):
205
+ color = "dim" if i == 0 else "green"
206
+ preview = val[:100].replace("\n", "↵")
207
+ label = f"Step {i}" if i > 0 else "Input"
208
+ log.write(f"[bold]{label}[/bold] [{color}]{op}[/{color}]")
209
+ log.write(f" [dim]{preview}[/dim] [dim]({len(val)} chars)[/dim]")
210
+ else:
211
+ log.write("[yellow]Smart decode: no known encoding detected[/yellow]")
212
+ log.write(f"[dim]Input ({len(text)} chars): {text[:80]}[/dim]")
213
+ except Exception as exc:
214
+ self.app.notify(f"Smart decode error: {exc}", severity="error")
215
+
216
+ def _update_chain_display(self) -> None:
217
+ try:
218
+ lbl = self.query_one("#dec-chain-display", Static)
219
+ if self._chain:
220
+ lbl.update(" → ".join(self._chain))
221
+ else:
222
+ lbl.update("[dim](empty)[/dim]")
223
+ except Exception:
224
+ pass
225
+
226
+ # ── Run ───────────────────────────────────────────────────────────────────
227
+
228
+ def action_run_chain(self) -> None:
229
+ """Запустить цепочку операций."""
230
+ try:
231
+ from pentool.api.decoder_api import run_chain, encode_op
232
+ inp_text = self.query_one("#dec-input", TextArea).text
233
+ if not inp_text:
234
+ self.app.notify("Input is empty", severity="warning")
235
+ return
236
+
237
+ if not self._chain:
238
+ # Одиночная операция — по выбранной кнопкой
239
+ op = self._selected_op
240
+ try:
241
+ result = encode_op(op, inp_text)
242
+ steps = [inp_text, result]
243
+ chain_used = [op]
244
+ except Exception as exc:
245
+ self.app.notify(f"Error: {exc}", severity="error")
246
+ return
247
+ else:
248
+ result, steps = run_chain(self._chain, inp_text)
249
+ chain_used = self._chain
250
+
251
+ self.query_one("#dec-output", TextArea).load_text(result)
252
+ self._render_steps(chain_used, steps)
253
+
254
+ except Exception as exc:
255
+ self.app.notify(f"Run error: {exc}", severity="error")
256
+ logger.debug("action_run_chain: %s", exc)
257
+
258
+ def _render_steps(self, chain: list[str], steps: list[str]) -> None:
259
+ """Показать шаги цепочки в лог-панели."""
260
+ try:
261
+ log = self.query_one("#dec-steps-log", RichLog)
262
+ log.clear()
263
+ log.write(f"[bold cyan]Chain: {' → '.join(chain)}[/bold cyan]")
264
+ log.write("")
265
+ for i, (op, value) in enumerate(zip(["INPUT"] + chain, steps)):
266
+ step_label = f"Step {i}" if i > 0 else "Input"
267
+ color = "dim" if i == 0 else ("green" if not value.startswith("[error") else "red")
268
+ preview = value[:100].replace("\n", "↵") + ("…" if len(value) > 100 else "")
269
+ log.write(f"[bold]{step_label}[/bold] [{color}]{op}[/{color}]")
270
+ log.write(f" [dim]{preview}[/dim] [dim]({len(value)} chars)[/dim]")
271
+ except Exception as exc:
272
+ logger.debug("_render_steps: %s", exc)
273
+
274
+ # ── Copy / Clear ──────────────────────────────────────────────────────────
275
+
276
+ def action_copy_result(self) -> None:
277
+ try:
278
+ from pentool.utils.copy_as import copy_to_clipboard
279
+ text = self.query_one("#dec-output", TextArea).text
280
+ if text and copy_to_clipboard(text):
281
+ self.app.notify("Result copied", timeout=2)
282
+ else:
283
+ self.app.notify("Nothing to copy", severity="warning")
284
+ except Exception as exc:
285
+ self.app.notify(f"Copy failed: {exc}", severity="error")
286
+
287
+ def action_clear_all(self) -> None:
288
+ try:
289
+ self.query_one("#dec-input", TextArea).load_text("")
290
+ self.query_one("#dec-output", TextArea).load_text("")
291
+ self.query_one("#dec-steps-log", RichLog).clear()
292
+ self._chain.clear()
293
+ self._update_chain_display()
294
+ except Exception as exc:
295
+ logger.debug("action_clear_all: %s", exc)
296
+
297
+ # ── Public API (загрузка из других модулей) ───────────────────────────────
298
+
299
+ def load_text(self, text: str) -> None:
300
+ """Загрузить текст в input-панель (вызов из Proxy/Repeater), очищает output."""
301
+ try:
302
+ self.query_one("#dec-input", TextArea).load_text(text)
303
+ self.query_one("#dec-output", TextArea).load_text("")
304
+ self.query_one("#dec-steps-log", RichLog).clear()
305
+ except Exception as exc:
306
+ logger.debug("load_text: %s", exc)
307
+
308
+ # ── Keyboard shortcut ─────────────────────────────────────────────────────
309
+
310
+ def action_run_chain_binding(self) -> None:
311
+ self.action_run_chain()
@@ -0,0 +1,3 @@
1
+ from pentool.tui.screens.extensions.screen import ExtensionsScreen
2
+
3
+ __all__ = ["ExtensionsScreen"]
@@ -0,0 +1,24 @@
1
+ """Экран Extensions — управление плагинами."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from textual.app import ComposeResult
6
+ from textual.widget import Widget
7
+ from textual.widgets import Static
8
+ from pathlib import Path
9
+
10
+ _CSS = (Path(__file__).parent / "screen.tcss").read_text(encoding="utf-8")
11
+
12
+
13
+ class ExtensionsScreen(Widget):
14
+ """Placeholder: Plugin manager screen."""
15
+
16
+ DEFAULT_CSS = _CSS
17
+
18
+ def compose(self) -> ComposeResult:
19
+ with Static(id="box"):
20
+ yield Static("Extensions", classes="title")
21
+ yield Static(
22
+ "Load Python plugins that add\nnew screens and CLI commands.",
23
+ classes="desc",
24
+ )
@@ -0,0 +1,3 @@
1
+ from pentool.tui.screens.intruder.screen import IntruderScreen
2
+
3
+ __all__ = ["IntruderScreen"]