voidx-cli 3.5.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. voidx_cli-3.5.2/PKG-INFO +9 -0
  2. voidx_cli-3.5.2/pyproject.toml +21 -0
  3. voidx_cli-3.5.2/setup.cfg +4 -0
  4. voidx_cli-3.5.2/tests/conftest.py +4 -0
  5. voidx_cli-3.5.2/tests/test_frame_advanced.py +293 -0
  6. voidx_cli-3.5.2/tests/test_frame_rendering.py +429 -0
  7. voidx_cli-3.5.2/tests/test_headless.py +71 -0
  8. voidx_cli-3.5.2/tests/test_input_advanced.py +446 -0
  9. voidx_cli-3.5.2/tests/test_input_handling.py +459 -0
  10. voidx_cli-3.5.2/tests/test_output_paste.py +242 -0
  11. voidx_cli-3.5.2/tests/test_output_stream.py +130 -0
  12. voidx_cli-3.5.2/tests/test_output_tools.py +271 -0
  13. voidx_cli-3.5.2/tests/test_output_tree.py +378 -0
  14. voidx_cli-3.5.2/tests/test_paste_handling.py +364 -0
  15. voidx_cli-3.5.2/tests/test_startup.py +60 -0
  16. voidx_cli-3.5.2/tests/test_status_activity.py +640 -0
  17. voidx_cli-3.5.2/tests/test_status_layout.py +145 -0
  18. voidx_cli-3.5.2/tests/test_terminal_input.py +437 -0
  19. voidx_cli-3.5.2/tests/test_terminal_panels.py +439 -0
  20. voidx_cli-3.5.2/tests/test_win32_paste_drain_integration.py +154 -0
  21. voidx_cli-3.5.2/tests/test_win32_paste_drain_regression.py +495 -0
  22. voidx_cli-3.5.2/tests/test_win32_paste_drain_unit.py +140 -0
  23. voidx_cli-3.5.2/tests/tui_helpers.py +70 -0
  24. voidx_cli-3.5.2/voidx_cli/__init__.py +6 -0
  25. voidx_cli-3.5.2/voidx_cli/activity.py +64 -0
  26. voidx_cli-3.5.2/voidx_cli/app.py +650 -0
  27. voidx_cli-3.5.2/voidx_cli/choice_mixin.py +59 -0
  28. voidx_cli-3.5.2/voidx_cli/clipboard_mixin.py +83 -0
  29. voidx_cli-3.5.2/voidx_cli/helpers.py +144 -0
  30. voidx_cli-3.5.2/voidx_cli/input.py +370 -0
  31. voidx_cli-3.5.2/voidx_cli/overlays.py +157 -0
  32. voidx_cli-3.5.2/voidx_cli/panels.py +347 -0
  33. voidx_cli-3.5.2/voidx_cli/parser.py +531 -0
  34. voidx_cli-3.5.2/voidx_cli/render_activity.py +217 -0
  35. voidx_cli-3.5.2/voidx_cli/render_frame.py +615 -0
  36. voidx_cli-3.5.2/voidx_cli/render_input.py +154 -0
  37. voidx_cli-3.5.2/voidx_cli/render_status.py +266 -0
  38. voidx_cli-3.5.2/voidx_cli/render_todo.py +79 -0
  39. voidx_cli-3.5.2/voidx_cli/renderer.py +24 -0
  40. voidx_cli-3.5.2/voidx_cli/state.py +254 -0
  41. voidx_cli-3.5.2/voidx_cli/terminal_mixin.py +95 -0
  42. voidx_cli-3.5.2/voidx_cli/text_prompt_mixin.py +49 -0
  43. voidx_cli-3.5.2/voidx_cli.egg-info/PKG-INFO +9 -0
  44. voidx_cli-3.5.2/voidx_cli.egg-info/SOURCES.txt +45 -0
  45. voidx_cli-3.5.2/voidx_cli.egg-info/dependency_links.txt +1 -0
  46. voidx_cli-3.5.2/voidx_cli.egg-info/requires.txt +2 -0
  47. voidx_cli-3.5.2/voidx_cli.egg-info/top_level.txt +4 -0
@@ -0,0 +1,9 @@
1
+ Metadata-Version: 2.4
2
+ Name: voidx-cli
3
+ Version: 3.5.2
4
+ Summary: CLI / terminal UI frontend for voidx.
5
+ Author: chikhamx
6
+ License: MIT
7
+ Requires-Python: >=3.11
8
+ Requires-Dist: voidx==3.5.2
9
+ Requires-Dist: rich>=13.9.0
@@ -0,0 +1,21 @@
1
+ [project]
2
+ name = "voidx-cli"
3
+ dynamic = ["version"]
4
+ description = "CLI / terminal UI frontend for voidx."
5
+ license = {text = "MIT"}
6
+ authors = [{name = "chikhamx"}]
7
+ requires-python = ">=3.11"
8
+ dependencies = [
9
+ "voidx==3.5.2",
10
+ "rich>=13.9.0",
11
+ ]
12
+
13
+ [build-system]
14
+ requires = ["setuptools>=68", "wheel"]
15
+ build-backend = "setuptools.build_meta"
16
+
17
+ [tool.setuptools.dynamic]
18
+ version = {attr = "voidx_cli.__version__"}
19
+
20
+ [tool.setuptools.packages.find]
21
+ where = ["."]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,4 @@
1
+ import sys
2
+ from pathlib import Path
3
+
4
+ sys.path.insert(0, str(Path(__file__).parent))
@@ -0,0 +1,293 @@
1
+ from tui_helpers import * # noqa: F403
2
+
3
+ import os
4
+ import re
5
+ import shutil
6
+ import sys
7
+
8
+ from rich.console import Console
9
+
10
+ from voidx.ui.output.dock import dock
11
+
12
+
13
+ def test_render_frame_pins_to_bottom_after_history_fills_terminal(tmp_path, monkeypatch):
14
+ class FakeStdout:
15
+ def __init__(self) -> None:
16
+ self.text = ""
17
+
18
+ def write(self, value: str) -> int:
19
+ self.text += value
20
+ return len(value)
21
+
22
+ def flush(self) -> None:
23
+ pass
24
+
25
+ fake_stdout = FakeStdout()
26
+ monkeypatch.setattr(sys, "stdout", fake_stdout)
27
+ monkeypatch.setattr(
28
+ shutil,
29
+ "get_terminal_size",
30
+ lambda fallback=None: os.terminal_size((80, 12)),
31
+ )
32
+
33
+ tui = _tui(tmp_path)
34
+ tui._tty = True
35
+ tui._console = Console(file=None, force_terminal=True, width=80, height=12, _environ={})
36
+ for index in range(20):
37
+ dock.tree.new_node(
38
+ parent=dock.tree.root,
39
+ node_type="message",
40
+ header=f"committed line {index}",
41
+ collapsed=False,
42
+ )
43
+ tui._committed_line_count = 20
44
+ tui._visible_committed_rows = 12
45
+
46
+ tui._render_frame()
47
+
48
+ assert tui._last_frame_start_row == max(12 - tui._last_frame_rows + 1, 1)
49
+ assert tui._last_frame_start_row < tui._committed_line_count + 1
50
+
51
+
52
+ def test_render_frame_clips_long_transcript_to_terminal_height(tmp_path, monkeypatch):
53
+ class FakeStdout:
54
+ def __init__(self) -> None:
55
+ self.text = ""
56
+
57
+ def write(self, value: str) -> int:
58
+ self.text += value
59
+ return len(value)
60
+
61
+ def flush(self) -> None:
62
+ pass
63
+
64
+ fake_stdout = FakeStdout()
65
+ monkeypatch.setattr(sys, "stdout", fake_stdout)
66
+ monkeypatch.setattr(
67
+ shutil, "get_terminal_size",
68
+ lambda fallback=None: os.terminal_size((80, 12)),
69
+ )
70
+
71
+ tui = _tui(tmp_path)
72
+ tui._tty = True
73
+ tui._console = Console(file=None, force_terminal=True, width=80, height=12, _environ={})
74
+ for index in range(50):
75
+ dock.tree.new_node(
76
+ parent=dock.tree.root,
77
+ node_type="message",
78
+ header=f"frame line {index:02d}",
79
+ collapsed=False,
80
+ )
81
+
82
+ tui._render_frame()
83
+
84
+ assert tui._last_frame_rows <= 12
85
+ if sys.platform != "win32":
86
+ assert "frame line 41" in fake_stdout.text
87
+ assert "frame line 40" not in fake_stdout.text
88
+
89
+
90
+ def test_render_frame_clips_single_wrapped_transcript_line_to_terminal_height(
91
+ tmp_path, monkeypatch
92
+ ):
93
+ class FakeStdout:
94
+ def __init__(self) -> None:
95
+ self.text = ""
96
+
97
+ def write(self, value: str) -> int:
98
+ self.text += value
99
+ return len(value)
100
+
101
+ def flush(self) -> None:
102
+ pass
103
+
104
+ fake_stdout = FakeStdout()
105
+ monkeypatch.setattr(sys, "stdout", fake_stdout)
106
+ monkeypatch.setattr(
107
+ shutil,
108
+ "get_terminal_size",
109
+ lambda fallback=None: os.terminal_size((80, 12)),
110
+ )
111
+
112
+ tui = _tui(tmp_path)
113
+ tui._tty = True
114
+ tui._console = Console(file=None, force_terminal=True, width=80, height=12, _environ={})
115
+ dock.tree.new_node(
116
+ parent=dock.tree.root,
117
+ node_type="message",
118
+ header="long " + ("x" * 2000),
119
+ collapsed=False,
120
+ )
121
+
122
+ tui._render_frame()
123
+
124
+ assert tui._last_frame_rows <= 12
125
+ assert "❯" in fake_stdout.text
126
+
127
+
128
+ def test_render_frame_clips_long_choice_panel_to_terminal_height(tmp_path, monkeypatch):
129
+ class FakeStdout:
130
+ def __init__(self) -> None:
131
+ self.text = ""
132
+
133
+ def write(self, value: str) -> int:
134
+ self.text += value
135
+ return len(value)
136
+
137
+ def flush(self) -> None:
138
+ pass
139
+
140
+ fake_stdout = FakeStdout()
141
+ monkeypatch.setattr(sys, "stdout", fake_stdout)
142
+ monkeypatch.setattr(
143
+ shutil,
144
+ "get_terminal_size",
145
+ lambda fallback=None: os.terminal_size((80, 12)),
146
+ )
147
+
148
+ tui = _tui(tmp_path)
149
+ tui._tty = True
150
+ tui._busy = True
151
+ tui._busy_started_at = 0.0
152
+ tui._busy_activity_verb = "Ruminating"
153
+ tui._console = Console(file=None, force_terminal=True, width=80, height=12, _environ={})
154
+ tui._active_choice = [("approved", "approved", ""), ("modified", "modified", ""), ("rejected", "rejected", "")]
155
+ tui._choice_prompt = (
156
+ "Plan: 新增 `document` 工具,让 LLM 在 design 节点激活时能按需读取文档模板。\n\n"
157
+ "Steps:\n"
158
+ "1. 新增 src/voidx/tools/load_doc_template.py,用 importlib.resources 读取 "
159
+ "voidx.data/templates/{doc_type}.md,返回模板内容。\n"
160
+ "2. 在 orchestrator 的 AgentDef.tools 列表中加入 document。\n"
161
+ "3. 更新 design 节点 step 4 的描述。\n"
162
+ "4. 添加测试。\n"
163
+ "5. 跑测试确认无回归。\n\n"
164
+ "Affected files: src/voidx/tools/load_doc_template.py, "
165
+ "src/voidx/agent/agents.py, tests/test_tools/test_basic.py"
166
+ )
167
+ dock.begin_capture()
168
+ dock.ensure_agent()
169
+ for index in range(4):
170
+ dock.tree.new_node(
171
+ parent=dock.tree.root,
172
+ node_type="message",
173
+ header=f"active line {index}",
174
+ collapsed=False,
175
+ )
176
+
177
+ tui._render_frame()
178
+
179
+ assert tui._last_frame_rows <= (13 if sys.platform == "win32" else 12)
180
+ assert "approved" in fake_stdout.text
181
+ assert "rejected" in fake_stdout.text
182
+
183
+
184
+ def test_input_display_rows_uses_frame_width_boundary(tmp_path):
185
+ tui = _tui(tmp_path)
186
+ tui._console = Console(file=None, force_terminal=True, width=80, height=24, _environ={})
187
+
188
+ width = tui._frame_width()
189
+ max_first_line_cells = width - tui._input_line_prefix_width(0) - 1
190
+
191
+ tui._input_lines = ["x" * max_first_line_cells]
192
+ tui._cursor_col = max_first_line_cells
193
+ assert tui._input_display_rows(width) == [1]
194
+
195
+ tui._input_lines = ["x" * (max_first_line_cells + 1)]
196
+ tui._cursor_col = max_first_line_cells + 1
197
+ assert tui._input_display_rows(width) == [2]
198
+
199
+
200
+ def test_wrapped_input_keeps_prompt_on_first_content_row(tmp_path):
201
+ tui = _tui(tmp_path)
202
+ tui._console = Console(file=None, force_terminal=True, width=80, height=24, _environ={})
203
+ tui._input_lines = ["x" * 80]
204
+ tui._cursor_col = 80
205
+
206
+ ansi = tui._capture_renderable(tui._render_bottom_impl(), tui._frame_width())
207
+ plain_lines = [
208
+ re.sub(r"\x1b\[[0-9;]*[a-zA-Z]", "", line).rstrip()
209
+ for line in ansi.splitlines()
210
+ ]
211
+
212
+ prompt_line = next((l for l in plain_lines if l.startswith("❯")), "")
213
+ if sys.platform != "win32":
214
+ assert prompt_line.startswith("❯ x")
215
+
216
+
217
+ def test_non_tty_flush_prints_transcript_without_live_frame_chrome(tmp_path, monkeypatch):
218
+ class FakeStdout:
219
+ def __init__(self) -> None:
220
+ self.text = ""
221
+
222
+ def write(self, value: str) -> int:
223
+ self.text += value
224
+ return len(value)
225
+
226
+ def flush(self) -> None:
227
+ pass
228
+
229
+ fake_stdout = FakeStdout()
230
+ monkeypatch.setattr(sys, "stdout", fake_stdout)
231
+
232
+ tui = _tui(tmp_path)
233
+ tui._tty = False
234
+ tui._console = Console(file=None, force_terminal=False, width=80, height=24, _environ={})
235
+ dock.tree.new_node(
236
+ parent=dock.tree.root,
237
+ node_type="startup",
238
+ header="[bold]Welcome[/bold]",
239
+ body_lines=["plain line"],
240
+ collapsed=False,
241
+ )
242
+
243
+ tui._flush_committed(force=True)
244
+
245
+ assert "Welcome" in fake_stdout.text
246
+ assert "plain line" in fake_stdout.text
247
+ assert "─" not in fake_stdout.text
248
+ assert "❯ " not in fake_stdout.text
249
+
250
+ fake_stdout.text = ""
251
+ tui._render_frame()
252
+
253
+ assert fake_stdout.text == ""
254
+
255
+
256
+ def test_typing_redraws_input_region_without_rewriting_transcript(tmp_path, monkeypatch):
257
+ class FakeStdout:
258
+ def __init__(self) -> None:
259
+ self.text = ""
260
+
261
+ def write(self, value: str) -> int:
262
+ self.text += value
263
+ return len(value)
264
+
265
+ def flush(self) -> None:
266
+ pass
267
+
268
+ fake_stdout = FakeStdout()
269
+ monkeypatch.setattr(sys, "stdout", fake_stdout)
270
+ monkeypatch.setattr(
271
+ shutil, "get_terminal_size",
272
+ lambda fallback=None: os.terminal_size((80, 12)),
273
+ )
274
+
275
+ tui = _tui(tmp_path)
276
+ tui._tty = True
277
+ tui._console = Console(file=None, force_terminal=True, width=80, height=12, _environ={})
278
+ dock.tree.new_node(
279
+ parent=dock.tree.root,
280
+ node_type="message",
281
+ header="startup banner",
282
+ collapsed=False,
283
+ )
284
+
285
+ tui._render_frame()
286
+ assert "startup banner" in fake_stdout.text
287
+
288
+ fake_stdout.text = ""
289
+ assert tui._process_input(b"x") is True
290
+ tui._render_after_input()
291
+
292
+ assert "startup banner" not in fake_stdout.text
293
+ assert "x" in fake_stdout.text