tau-coding-agent 0.1.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 (283) hide show
  1. tau/__init__.py +0 -0
  2. tau/agent/__init__.py +11 -0
  3. tau/agent/prompt/__init__.py +10 -0
  4. tau/agent/prompt/builder.py +302 -0
  5. tau/agent/prompt/types.py +33 -0
  6. tau/agent/service.py +369 -0
  7. tau/agent/types.py +61 -0
  8. tau/auth/manager.py +247 -0
  9. tau/auth/storage.py +82 -0
  10. tau/auth/types.py +41 -0
  11. tau/builtins/__init__.py +4 -0
  12. tau/builtins/__pycache__/__init__.cpython-313.pyc +0 -0
  13. tau/builtins/__pycache__/__init__.cpython-314.pyc +0 -0
  14. tau/builtins/commands/__init__.py +41 -0
  15. tau/builtins/commands/__pycache__/__init__.cpython-313.pyc +0 -0
  16. tau/builtins/commands/__pycache__/__init__.cpython-314.pyc +0 -0
  17. tau/builtins/commands/__pycache__/clear.cpython-313.pyc +0 -0
  18. tau/builtins/commands/__pycache__/clear.cpython-314.pyc +0 -0
  19. tau/builtins/commands/__pycache__/compact.cpython-313.pyc +0 -0
  20. tau/builtins/commands/__pycache__/compact.cpython-314.pyc +0 -0
  21. tau/builtins/commands/__pycache__/reload.cpython-313.pyc +0 -0
  22. tau/builtins/commands/__pycache__/reload.cpython-314.pyc +0 -0
  23. tau/builtins/commands/__pycache__/session.cpython-313.pyc +0 -0
  24. tau/builtins/commands/__pycache__/session.cpython-314.pyc +0 -0
  25. tau/builtins/commands/clear.py +16 -0
  26. tau/builtins/commands/compact.py +28 -0
  27. tau/builtins/commands/reload.py +27 -0
  28. tau/builtins/commands/session.py +19 -0
  29. tau/builtins/extensions/footer/__init__.py +76 -0
  30. tau/builtins/extensions/footer/__pycache__/__init__.cpython-313.pyc +0 -0
  31. tau/builtins/extensions/footer/__pycache__/git.cpython-313.pyc +0 -0
  32. tau/builtins/extensions/footer/__pycache__/model.cpython-313.pyc +0 -0
  33. tau/builtins/extensions/footer/__pycache__/utils.cpython-313.pyc +0 -0
  34. tau/builtins/extensions/footer/git.py +26 -0
  35. tau/builtins/extensions/footer/model.py +69 -0
  36. tau/builtins/extensions/footer/utils.py +44 -0
  37. tau/builtins/extensions/header/__init__.py +18 -0
  38. tau/builtins/extensions/header/__pycache__/__init__.cpython-313.pyc +0 -0
  39. tau/builtins/models/__init__.py +0 -0
  40. tau/builtins/models/__pycache__/__init__.cpython-313.pyc +0 -0
  41. tau/builtins/models/__pycache__/text.cpython-313.pyc +0 -0
  42. tau/builtins/models/audio.py +43 -0
  43. tau/builtins/models/image.py +43 -0
  44. tau/builtins/models/text.py +482 -0
  45. tau/builtins/models/video.py +40 -0
  46. tau/builtins/prompts/commit.md +7 -0
  47. tau/builtins/prompts/docs.md +7 -0
  48. tau/builtins/prompts/explain.md +7 -0
  49. tau/builtins/prompts/fix.md +7 -0
  50. tau/builtins/prompts/refactor.md +7 -0
  51. tau/builtins/prompts/review.md +7 -0
  52. tau/builtins/prompts/test.md +7 -0
  53. tau/builtins/providers/__init__.py +0 -0
  54. tau/builtins/providers/__pycache__/__init__.cpython-313.pyc +0 -0
  55. tau/builtins/providers/__pycache__/text.cpython-313.pyc +0 -0
  56. tau/builtins/providers/audio.py +10 -0
  57. tau/builtins/providers/image.py +9 -0
  58. tau/builtins/providers/text.py +33 -0
  59. tau/builtins/providers/video.py +6 -0
  60. tau/builtins/skills/code-review/SKILL.md +4 -0
  61. tau/builtins/skills/debug/SKILL.md +4 -0
  62. tau/builtins/skills/git-commit/SKILL.md +4 -0
  63. tau/builtins/themes/dark.yaml +1 -0
  64. tau/builtins/themes/light.yaml +46 -0
  65. tau/builtins/tools/__init__.py +73 -0
  66. tau/builtins/tools/__pycache__/__init__.cpython-313.pyc +0 -0
  67. tau/builtins/tools/__pycache__/__init__.cpython-314.pyc +0 -0
  68. tau/builtins/tools/__pycache__/bash.cpython-313.pyc +0 -0
  69. tau/builtins/tools/__pycache__/bash.cpython-314.pyc +0 -0
  70. tau/builtins/tools/__pycache__/edit.cpython-313.pyc +0 -0
  71. tau/builtins/tools/__pycache__/edit.cpython-314.pyc +0 -0
  72. tau/builtins/tools/__pycache__/glob.cpython-313.pyc +0 -0
  73. tau/builtins/tools/__pycache__/glob.cpython-314.pyc +0 -0
  74. tau/builtins/tools/__pycache__/grep.cpython-313.pyc +0 -0
  75. tau/builtins/tools/__pycache__/grep.cpython-314.pyc +0 -0
  76. tau/builtins/tools/__pycache__/ls.cpython-313.pyc +0 -0
  77. tau/builtins/tools/__pycache__/ls.cpython-314.pyc +0 -0
  78. tau/builtins/tools/__pycache__/read.cpython-313.pyc +0 -0
  79. tau/builtins/tools/__pycache__/read.cpython-314.pyc +0 -0
  80. tau/builtins/tools/__pycache__/terminal.cpython-313.pyc +0 -0
  81. tau/builtins/tools/__pycache__/terminal.cpython-314.pyc +0 -0
  82. tau/builtins/tools/__pycache__/write.cpython-313.pyc +0 -0
  83. tau/builtins/tools/__pycache__/write.cpython-314.pyc +0 -0
  84. tau/builtins/tools/edit.py +215 -0
  85. tau/builtins/tools/glob.py +112 -0
  86. tau/builtins/tools/grep.py +146 -0
  87. tau/builtins/tools/ls.py +135 -0
  88. tau/builtins/tools/read.py +122 -0
  89. tau/builtins/tools/terminal.py +150 -0
  90. tau/builtins/tools/write.py +105 -0
  91. tau/commands/__init__.py +10 -0
  92. tau/commands/registry.py +71 -0
  93. tau/commands/types.py +33 -0
  94. tau/console/__init__.py +0 -0
  95. tau/console/cli.py +266 -0
  96. tau/console/commands/__init__.py +0 -0
  97. tau/console/commands/auth.py +193 -0
  98. tau/console/commands/packages.py +104 -0
  99. tau/console/commands/update.py +76 -0
  100. tau/core/__init__.py +0 -0
  101. tau/core/registry.py +102 -0
  102. tau/engine/__init__.py +47 -0
  103. tau/engine/service.py +768 -0
  104. tau/engine/types.py +163 -0
  105. tau/extensions/__init__.py +28 -0
  106. tau/extensions/api.py +928 -0
  107. tau/extensions/context.py +462 -0
  108. tau/extensions/events.py +70 -0
  109. tau/extensions/loader.py +386 -0
  110. tau/extensions/runtime.py +184 -0
  111. tau/extensions/settings.py +137 -0
  112. tau/hooks/__init__.py +112 -0
  113. tau/hooks/engine.py +237 -0
  114. tau/hooks/inference.py +21 -0
  115. tau/hooks/runtime.py +126 -0
  116. tau/hooks/service.py +121 -0
  117. tau/hooks/session.py +117 -0
  118. tau/hooks/tui.py +61 -0
  119. tau/hooks/types.py +72 -0
  120. tau/inference/__init__.py +180 -0
  121. tau/inference/api/__init__.py +0 -0
  122. tau/inference/api/audio/__init__.py +0 -0
  123. tau/inference/api/audio/base.py +29 -0
  124. tau/inference/api/audio/builtins.py +15 -0
  125. tau/inference/api/audio/elevenlabs_audio.py +183 -0
  126. tau/inference/api/audio/gemini_audio.py +95 -0
  127. tau/inference/api/audio/openai_audio.py +159 -0
  128. tau/inference/api/audio/registry.py +15 -0
  129. tau/inference/api/audio/sarvam_audio.py +163 -0
  130. tau/inference/api/audio/service.py +103 -0
  131. tau/inference/api/audio/utils.py +47 -0
  132. tau/inference/api/image/__init__.py +0 -0
  133. tau/inference/api/image/base.py +17 -0
  134. tau/inference/api/image/builtins.py +8 -0
  135. tau/inference/api/image/gemini_image.py +77 -0
  136. tau/inference/api/image/openai_image.py +103 -0
  137. tau/inference/api/image/openrouter.py +144 -0
  138. tau/inference/api/image/registry.py +15 -0
  139. tau/inference/api/image/service.py +71 -0
  140. tau/inference/api/registry.py +82 -0
  141. tau/inference/api/text/__init__.py +0 -0
  142. tau/inference/api/text/anthropic_claude_code.py +222 -0
  143. tau/inference/api/text/anthropic_messages.py +196 -0
  144. tau/inference/api/text/base.py +40 -0
  145. tau/inference/api/text/builtins.py +19 -0
  146. tau/inference/api/text/gemini_generate.py +234 -0
  147. tau/inference/api/text/github_copilot_chat.py +172 -0
  148. tau/inference/api/text/google_antigravity.py +522 -0
  149. tau/inference/api/text/mistral_chat.py +284 -0
  150. tau/inference/api/text/ollama_chat.py +200 -0
  151. tau/inference/api/text/openai_codex_responses.py +497 -0
  152. tau/inference/api/text/openai_completions.py +227 -0
  153. tau/inference/api/text/openai_responses.py +235 -0
  154. tau/inference/api/text/registry.py +50 -0
  155. tau/inference/api/text/service.py +297 -0
  156. tau/inference/api/text/types.py +7 -0
  157. tau/inference/api/text/utils.py +228 -0
  158. tau/inference/api/video/__init__.py +0 -0
  159. tau/inference/api/video/base.py +26 -0
  160. tau/inference/api/video/builtins.py +7 -0
  161. tau/inference/api/video/fal_video.py +119 -0
  162. tau/inference/api/video/openrouter_video.py +142 -0
  163. tau/inference/api/video/registry.py +15 -0
  164. tau/inference/api/video/service.py +72 -0
  165. tau/inference/model/__init__.py +0 -0
  166. tau/inference/model/registry.py +102 -0
  167. tau/inference/model/types.py +65 -0
  168. tau/inference/provider/__init__.py +0 -0
  169. tau/inference/provider/oauth/__init__.py +35 -0
  170. tau/inference/provider/oauth/anthropic_claude_code.py +286 -0
  171. tau/inference/provider/oauth/github_copilot.py +333 -0
  172. tau/inference/provider/oauth/google_antigravity.py +258 -0
  173. tau/inference/provider/oauth/openai_codex.py +309 -0
  174. tau/inference/provider/oauth/pkce.py +14 -0
  175. tau/inference/provider/oauth/types.py +46 -0
  176. tau/inference/provider/oauth/utils.py +154 -0
  177. tau/inference/provider/registry.py +141 -0
  178. tau/inference/provider/types.py +114 -0
  179. tau/inference/types.py +549 -0
  180. tau/inference/utils.py +219 -0
  181. tau/message/__init__.py +0 -0
  182. tau/message/types.py +482 -0
  183. tau/message/utils.py +178 -0
  184. tau/packages/__init__.py +11 -0
  185. tau/packages/manager.py +190 -0
  186. tau/packages/types.py +20 -0
  187. tau/packages/utils.py +67 -0
  188. tau/prompts/expand.py +58 -0
  189. tau/prompts/loader.py +69 -0
  190. tau/prompts/registry.py +45 -0
  191. tau/prompts/types.py +24 -0
  192. tau/rpc/__init__.py +8 -0
  193. tau/rpc/mode.py +783 -0
  194. tau/rpc/types.py +252 -0
  195. tau/runtime/service.py +759 -0
  196. tau/runtime/types.py +303 -0
  197. tau/session/branch_summarization.py +312 -0
  198. tau/session/compaction.py +646 -0
  199. tau/session/manager.py +652 -0
  200. tau/session/types.py +188 -0
  201. tau/session/utils.py +233 -0
  202. tau/settings/manager.py +1077 -0
  203. tau/settings/paths.py +150 -0
  204. tau/settings/storage.py +63 -0
  205. tau/settings/types.py +173 -0
  206. tau/settings/utils.py +25 -0
  207. tau/skills/loader.py +91 -0
  208. tau/skills/registry.py +70 -0
  209. tau/skills/types.py +25 -0
  210. tau/themes/loader.py +238 -0
  211. tau/themes/registry.py +108 -0
  212. tau/themes/types.py +19 -0
  213. tau/tool/__init__.py +3 -0
  214. tau/tool/registry.py +117 -0
  215. tau/tool/render.py +21 -0
  216. tau/tool/types.py +244 -0
  217. tau/trust/__init__.py +13 -0
  218. tau/trust/manager.py +80 -0
  219. tau/trust/types.py +14 -0
  220. tau/trust/utils.py +72 -0
  221. tau/tui/__init__.py +54 -0
  222. tau/tui/agent_hooks.py +346 -0
  223. tau/tui/ansi.py +330 -0
  224. tau/tui/app.py +540 -0
  225. tau/tui/autocomplete.py +33 -0
  226. tau/tui/capabilities.py +119 -0
  227. tau/tui/commands/__init__.py +3 -0
  228. tau/tui/commands/appearance.py +498 -0
  229. tau/tui/commands/auth.py +232 -0
  230. tau/tui/commands/context.py +38 -0
  231. tau/tui/commands/misc.py +82 -0
  232. tau/tui/commands/model.py +118 -0
  233. tau/tui/commands/session.py +464 -0
  234. tau/tui/component.py +268 -0
  235. tau/tui/components/__init__.py +0 -0
  236. tau/tui/components/autocomplete_manager.py +267 -0
  237. tau/tui/components/autocomplete_picker.py +143 -0
  238. tau/tui/components/box.py +90 -0
  239. tau/tui/components/command_palette.py +144 -0
  240. tau/tui/components/dynamic_border.py +19 -0
  241. tau/tui/components/file_picker.py +233 -0
  242. tau/tui/components/image.py +181 -0
  243. tau/tui/components/inline_selector.py +71 -0
  244. tau/tui/components/layout.py +1194 -0
  245. tau/tui/components/message_list.py +692 -0
  246. tau/tui/components/modal.py +97 -0
  247. tau/tui/components/model_palette.py +204 -0
  248. tau/tui/components/picker_overlay.py +174 -0
  249. tau/tui/components/prompt_overlay.py +236 -0
  250. tau/tui/components/resume_modal.py +372 -0
  251. tau/tui/components/select_list.py +222 -0
  252. tau/tui/components/settings_modal.py +274 -0
  253. tau/tui/components/settings_schema.py +203 -0
  254. tau/tui/components/spinner.py +119 -0
  255. tau/tui/components/text_input.py +396 -0
  256. tau/tui/components/text_prompt.py +82 -0
  257. tau/tui/components/tree_select_list.py +580 -0
  258. tau/tui/components/trust_screen.py +97 -0
  259. tau/tui/diff.py +114 -0
  260. tau/tui/fuzzy.py +99 -0
  261. tau/tui/input.py +496 -0
  262. tau/tui/input_handler.py +716 -0
  263. tau/tui/keybindings.py +87 -0
  264. tau/tui/markdown.py +286 -0
  265. tau/tui/message_renderers.py +31 -0
  266. tau/tui/overlay.py +326 -0
  267. tau/tui/renderer.py +378 -0
  268. tau/tui/terminal.py +499 -0
  269. tau/tui/theme.py +148 -0
  270. tau/tui/tui.py +544 -0
  271. tau/tui/ui_context.py +768 -0
  272. tau/tui/utils.py +20 -0
  273. tau/utils/__init__.py +0 -0
  274. tau/utils/http_proxy.py +221 -0
  275. tau/utils/image_processing.py +172 -0
  276. tau/utils/secrets.py +59 -0
  277. tau/utils/version_check.py +60 -0
  278. tau_coding_agent-0.1.0.dist-info/METADATA +177 -0
  279. tau_coding_agent-0.1.0.dist-info/RECORD +283 -0
  280. tau_coding_agent-0.1.0.dist-info/WHEEL +5 -0
  281. tau_coding_agent-0.1.0.dist-info/entry_points.txt +2 -0
  282. tau_coding_agent-0.1.0.dist-info/licenses/LICENSE +21 -0
  283. tau_coding_agent-0.1.0.dist-info/top_level.txt +1 -0
tau/tui/overlay.py ADDED
@@ -0,0 +1,326 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import Callable, Literal, TYPE_CHECKING
5
+
6
+ if TYPE_CHECKING:
7
+ from tau.tui.component import Component
8
+ from tau.tui.input import InputEvent
9
+
10
+ # ---------------------------------------------------------------------------
11
+ # Types
12
+ # ---------------------------------------------------------------------------
13
+
14
+ # A size value: absolute column/row count or a percentage string like "60%"
15
+ SizeValue = int | str
16
+
17
+ # All nine anchor positions
18
+ OverlayAnchor = Literal[
19
+ "center",
20
+ "top-left", "top-center", "top-right",
21
+ "left-center", "right-center",
22
+ "bottom-left", "bottom-center", "bottom-right",
23
+ ]
24
+
25
+
26
+ def _parse_size(value: SizeValue, reference: int) -> int:
27
+ """Resolve a SizeValue against a reference dimension."""
28
+ if isinstance(value, str) and value.endswith("%"):
29
+ return int(reference * float(value[:-1]) / 100.0)
30
+ return int(value)
31
+
32
+
33
+ # ---------------------------------------------------------------------------
34
+ # OverlayOptions
35
+ # ---------------------------------------------------------------------------
36
+
37
+ @dataclass
38
+ class OverlayOptions:
39
+ """
40
+ Positioning and sizing options for a floating overlay window.
41
+
42
+ Positioning and sizing options for a floating overlay window with full anchor support, percentage sizes,
43
+ min/max constraints, explicit row/col positioning, responsive visibility,
44
+ and per-side margin control.
45
+
46
+ Examples::
47
+
48
+ # Centred dialog, 60% wide, max 80% tall
49
+ OverlayOptions(width="60%", max_height="80%", anchor="center")
50
+
51
+ # Right-side panel pinned to the bottom-right
52
+ OverlayOptions(width=40, anchor="bottom-right", margin=1)
53
+
54
+ # Responsive: hide when terminal is narrower than 80 cols
55
+ OverlayOptions(visible=lambda w, h: w >= 80)
56
+
57
+ # Explicit absolute position
58
+ OverlayOptions(row=5, col=10, width=30)
59
+ """
60
+
61
+ # ── Size ─────────────────────────────────────────────────────────────────
62
+ # Width of the overlay (columns). Defaults to "60%".
63
+ width: SizeValue = "60%"
64
+ # Explicit height (rows). When None, the overlay's natural render height is used.
65
+ height: SizeValue | None = None
66
+ # Lower bound on width after percentage resolution.
67
+ min_width: int | None = None
68
+ # Upper bound on width.
69
+ max_width: SizeValue | None = None
70
+ # Lower bound on height.
71
+ min_height: int | None = None
72
+ # Upper bound on height. Defaults to "80%" so very tall components stay on screen.
73
+ max_height: SizeValue | None = "80%"
74
+
75
+ # ── Position ─────────────────────────────────────────────────────────────
76
+ # Named anchor point for automatic positioning. Overridden by row/col.
77
+ anchor: OverlayAnchor = "center"
78
+ # Fine-tune position after anchor calculation (signed, in rows/cols).
79
+ offset_x: int = 0
80
+ offset_y: int = 0
81
+ # Explicit row (0-indexed from top). Overrides anchor row calculation.
82
+ row: SizeValue | None = None
83
+ # Explicit col (0-indexed from left). Overrides anchor col calculation.
84
+ col: SizeValue | None = None
85
+
86
+ # ── Margin ───────────────────────────────────────────────────────────────
87
+ # Minimum gap from each terminal edge. Either a uniform int or a dict
88
+ # with optional keys "top", "right", "bottom", "left".
89
+ margin: int | dict[str, int] = 1
90
+
91
+ # ── Behaviour ────────────────────────────────────────────────────────────
92
+ # Called each render cycle with (term_width, term_height).
93
+ # Return False to hide the overlay on small terminals.
94
+ visible: Callable[[int, int], bool] | None = None
95
+ # If True the overlay is painted but does NOT capture keyboard focus.
96
+ non_capturing: bool = False
97
+
98
+ # ── Margins helper ───────────────────────────────────────────────────────
99
+ def _margins(self) -> tuple[int, int, int, int]:
100
+ """Return (top, right, bottom, left) margin values."""
101
+ m = self.margin
102
+ if isinstance(m, int):
103
+ return m, m, m, m
104
+ return (
105
+ m.get("top", 1),
106
+ m.get("right", 1),
107
+ m.get("bottom", 1),
108
+ m.get("left", 1),
109
+ )
110
+
111
+
112
+ # ---------------------------------------------------------------------------
113
+ # OverlayHandle
114
+ # ---------------------------------------------------------------------------
115
+
116
+ class OverlayHandle:
117
+ """
118
+ Returned by TUI.show_overlay() — controls a live overlay.
119
+
120
+ Overlay handle API::
121
+
122
+ handle = tui.show_overlay(MyDialog(), opts)
123
+ handle.set_hidden(True) # temporarily hide
124
+ handle.show() # make visible again
125
+ handle.focus() # steal keyboard focus
126
+ handle.unfocus() # release focus back
127
+ handle.close() # permanently remove
128
+ """
129
+
130
+ def __init__(
131
+ self,
132
+ close_fn: Callable[[], None],
133
+ set_hidden_fn: Callable[[bool], None],
134
+ focus_fn: Callable[[], None],
135
+ unfocus_fn: Callable[["Component | None"], None],
136
+ is_focused_fn: Callable[[], bool],
137
+ is_hidden_fn: Callable[[], bool],
138
+ ) -> None:
139
+ self._close_fn = close_fn
140
+ self._set_hidden_fn = set_hidden_fn
141
+ self._focus_fn = focus_fn
142
+ self._unfocus_fn = unfocus_fn
143
+ self._is_focused_fn = is_focused_fn
144
+ self._is_hidden_fn = is_hidden_fn
145
+ self._closed = False
146
+
147
+ # ── Lifecycle ─────────────────────────────────────────────────────────────
148
+
149
+ def close(self) -> None:
150
+ """Permanently remove this overlay from the screen."""
151
+ if not self._closed:
152
+ self._closed = True
153
+ self._close_fn()
154
+
155
+ # Alias — calls it hide() when it means close
156
+ hide = close
157
+
158
+ # ── Visibility ────────────────────────────────────────────────────────────
159
+
160
+ def set_hidden(self, hidden: bool) -> None:
161
+ """Temporarily hide (True) or show (False) without closing."""
162
+ if not self._closed:
163
+ self._set_hidden_fn(hidden)
164
+
165
+ def show(self) -> None:
166
+ """Make the overlay visible (undo a set_hidden(True))."""
167
+ self.set_hidden(False)
168
+
169
+ @property
170
+ def hidden(self) -> bool:
171
+ """True while the overlay is temporarily hidden."""
172
+ return self._is_hidden_fn()
173
+
174
+ # ── Focus ─────────────────────────────────────────────────────────────────
175
+
176
+ def focus(self) -> None:
177
+ """Give keyboard focus to this overlay's component."""
178
+ if not self._closed:
179
+ self._focus_fn()
180
+
181
+ def unfocus(self, target: "Component | None" = None) -> None:
182
+ """
183
+ Release focus from this overlay.
184
+
185
+ ``target`` optionally specifies which component should receive
186
+ focus next; if None, TUI restores the previous focus target.
187
+ """
188
+ if not self._closed:
189
+ self._unfocus_fn(target)
190
+
191
+ def is_focused(self) -> bool:
192
+ """True when this overlay's component currently holds keyboard focus."""
193
+ return self._is_focused_fn()
194
+
195
+
196
+ # ---------------------------------------------------------------------------
197
+ # OverlayEntry (internal)
198
+ # ---------------------------------------------------------------------------
199
+
200
+ @dataclass
201
+ class OverlayEntry:
202
+ """Internal: one entry on the TUI overlay stack."""
203
+
204
+ component: "Component"
205
+ options: OverlayOptions = field(default_factory=OverlayOptions)
206
+ hidden: bool = False
207
+
208
+ def is_visible(self, term_w: int, term_h: int) -> bool:
209
+ """Return False if the responsive visible() callback hides this overlay."""
210
+ if self.hidden:
211
+ return False
212
+ fn = self.options.visible
213
+ return fn(term_w, term_h) if fn is not None else True
214
+
215
+ def resolve_width(self, term_w: int) -> int:
216
+ """Compute overlay width from options, applying min/max constraints."""
217
+ opt = self.options
218
+ mt, mr, mb, ml = opt._margins()
219
+ h_margin = ml + mr
220
+
221
+ w = _parse_size(opt.width, term_w)
222
+
223
+ if opt.min_width is not None:
224
+ w = max(w, opt.min_width)
225
+ if opt.max_width is not None:
226
+ w = min(w, _parse_size(opt.max_width, term_w))
227
+
228
+ return max(10, min(w, term_w - h_margin))
229
+
230
+ def resolve(
231
+ self,
232
+ term_w: int,
233
+ term_h: int,
234
+ natural_h: int,
235
+ ) -> tuple[int, int, int, int]:
236
+ """
237
+ Return (width, height, row, col) — all 0-indexed.
238
+
239
+ ``natural_h`` is the component's unconstrained render line count.
240
+ """
241
+ opt = self.options
242
+ mt, mr, mb, ml = opt._margins()
243
+
244
+ # ── Width ─────────────────────────────────────────────────────────
245
+ width = self.resolve_width(term_w)
246
+
247
+ # ── Height ────────────────────────────────────────────────────────
248
+ if opt.height is not None:
249
+ height = _parse_size(opt.height, term_h)
250
+ else:
251
+ height = natural_h
252
+
253
+ if opt.min_height is not None:
254
+ height = max(height, opt.min_height)
255
+ if opt.max_height is not None:
256
+ height = min(height, _parse_size(opt.max_height, term_h))
257
+
258
+ # Clamp to what the terminal can fit accounting for margins
259
+ max_h = max(3, term_h - mt - mb)
260
+ height = min(height, max_h)
261
+
262
+ # ── Position via anchor ───────────────────────────────────────────
263
+ anchor = opt.anchor
264
+ if anchor == "top-left":
265
+ row = mt
266
+ col = ml
267
+ elif anchor == "top-center":
268
+ row = mt
269
+ col = max(ml, (term_w - width) // 2)
270
+ elif anchor == "top-right":
271
+ row = mt
272
+ col = max(ml, term_w - width - mr)
273
+ elif anchor == "left-center":
274
+ row = max(mt, (term_h - height) // 2)
275
+ col = ml
276
+ elif anchor == "right-center":
277
+ row = max(mt, (term_h - height) // 2)
278
+ col = max(ml, term_w - width - mr)
279
+ elif anchor == "bottom-left":
280
+ row = max(mt, term_h - height - mb)
281
+ col = ml
282
+ elif anchor == "bottom-center":
283
+ row = max(mt, term_h - height - mb)
284
+ col = max(ml, (term_w - width) // 2)
285
+ elif anchor == "bottom-right":
286
+ row = max(mt, term_h - height - mb)
287
+ col = max(ml, term_w - width - mr)
288
+ else: # "center" — default
289
+ row = max(mt, (term_h - height) // 2)
290
+ col = max(ml, (term_w - width) // 2)
291
+
292
+ # ── Explicit row/col overrides anchor ─────────────────────────────
293
+ if opt.row is not None:
294
+ row = _parse_size(opt.row, term_h)
295
+ if opt.col is not None:
296
+ col = _parse_size(opt.col, term_w)
297
+
298
+ # ── Fine-tune with offset ─────────────────────────────────────────
299
+ row = max(0, min(row + opt.offset_y, term_h - height))
300
+ col = max(0, min(col + opt.offset_x, term_w - width))
301
+
302
+ return width, height, row, col
303
+
304
+
305
+ # ---------------------------------------------------------------------------
306
+ # CustomOptions (for Layout.custom())
307
+ # ---------------------------------------------------------------------------
308
+
309
+ @dataclass
310
+ class CustomOptions:
311
+ """
312
+ Options for ``Layout.custom()`` — controls how the factory component
313
+ is displayed.
314
+
315
+ overlay=False (default) swaps the TUI root to the custom component
316
+ for a full-screen takeover; when the done() callback fires the
317
+ Layout is restored.
318
+
319
+ overlay=True renders the component as a floating overlay on top of
320
+ the existing layout, using overlay_options for positioning.
321
+ """
322
+
323
+ overlay: bool = False
324
+ overlay_options: OverlayOptions = field(default_factory=OverlayOptions)
325
+ # Called with the OverlayHandle immediately after the overlay is shown
326
+ on_handle: Callable[["OverlayHandle"], None] | None = None