smartcli-toolkit 0.1.5__tar.gz → 0.1.6__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 (27) hide show
  1. {smartcli_toolkit-0.1.5 → smartcli_toolkit-0.1.6}/PKG-INFO +7 -7
  2. {smartcli_toolkit-0.1.5 → smartcli_toolkit-0.1.6}/README.md +6 -6
  3. {smartcli_toolkit-0.1.5 → smartcli_toolkit-0.1.6}/pyproject.toml +1 -1
  4. {smartcli_toolkit-0.1.5 → smartcli_toolkit-0.1.6}/smartcli_core/__init__.py +1 -1
  5. {smartcli_toolkit-0.1.5 → smartcli_toolkit-0.1.6}/smartcli_core/session.py +36 -0
  6. {smartcli_toolkit-0.1.5 → smartcli_toolkit-0.1.6}/smartcli_toolkit.egg-info/PKG-INFO +7 -7
  7. {smartcli_toolkit-0.1.5 → smartcli_toolkit-0.1.6}/smartcli_toolkit.egg-info/SOURCES.txt +2 -1
  8. smartcli_toolkit-0.1.6/tests/test_wait_change.py +115 -0
  9. {smartcli_toolkit-0.1.5 → smartcli_toolkit-0.1.6}/LICENSE +0 -0
  10. {smartcli_toolkit-0.1.5 → smartcli_toolkit-0.1.6}/setup.cfg +0 -0
  11. {smartcli_toolkit-0.1.5 → smartcli_toolkit-0.1.6}/smartcli_core/__main__.py +0 -0
  12. {smartcli_toolkit-0.1.5 → smartcli_toolkit-0.1.6}/smartcli_core/pty_backend.py +0 -0
  13. {smartcli_toolkit-0.1.5 → smartcli_toolkit-0.1.6}/smartcli_core/py.typed +0 -0
  14. {smartcli_toolkit-0.1.5 → smartcli_toolkit-0.1.6}/smartcli_core/readiness.py +0 -0
  15. {smartcli_toolkit-0.1.5 → smartcli_toolkit-0.1.6}/smartcli_core/screen_model.py +0 -0
  16. {smartcli_toolkit-0.1.5 → smartcli_toolkit-0.1.6}/smartcli_core/snapshot.py +0 -0
  17. {smartcli_toolkit-0.1.5 → smartcli_toolkit-0.1.6}/smartcli_toolkit.egg-info/dependency_links.txt +0 -0
  18. {smartcli_toolkit-0.1.5 → smartcli_toolkit-0.1.6}/smartcli_toolkit.egg-info/requires.txt +0 -0
  19. {smartcli_toolkit-0.1.5 → smartcli_toolkit-0.1.6}/smartcli_toolkit.egg-info/top_level.txt +0 -0
  20. {smartcli_toolkit-0.1.5 → smartcli_toolkit-0.1.6}/tests/test_char_width.py +0 -0
  21. {smartcli_toolkit-0.1.5 → smartcli_toolkit-0.1.6}/tests/test_cpr_reply.py +0 -0
  22. {smartcli_toolkit-0.1.5 → smartcli_toolkit-0.1.6}/tests/test_degenerate_inputs.py +0 -0
  23. {smartcli_toolkit-0.1.5 → smartcli_toolkit-0.1.6}/tests/test_doc_counts.py +0 -0
  24. {smartcli_toolkit-0.1.5 → smartcli_toolkit-0.1.6}/tests/test_fx_contract.py +0 -0
  25. {smartcli_toolkit-0.1.5 → smartcli_toolkit-0.1.6}/tests/test_golden_frames.py +0 -0
  26. {smartcli_toolkit-0.1.5 → smartcli_toolkit-0.1.6}/tests/test_readiness.py +0 -0
  27. {smartcli_toolkit-0.1.5 → smartcli_toolkit-0.1.6}/tests/test_vendor_sync.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: smartcli-toolkit
3
- Version: 0.1.5
3
+ Version: 0.1.6
4
4
  Summary: Pluggable-PTY + pyte screen model + semantic snapshot / readiness core for driving interactive terminal programs (SmartCLI shared core).
5
5
  Author: dwgx
6
6
  License-Expression: MIT
@@ -195,7 +195,7 @@ backend, and dependency versions — run it and paste the output when filing a b
195
195
 
196
196
  ```bash
197
197
  cd skills/cmd-art
198
- python -m fx list # list all 22 effects
198
+ python -m fx list # list all 28 effects
199
199
  python -m fx play donut --seconds 5 # play one effect (bounded)
200
200
  python -m fx gallery # one frame of each effect
201
201
  python -m fx show --seq "donut:fire:3,plasma::3"
@@ -205,7 +205,7 @@ python -m fx show --seq "donut:fire:3,plasma::3"
205
205
 
206
206
  ```bash
207
207
  cd skills/tui-ui
208
- python -m ui widgets # list all 15 widgets
208
+ python -m ui widgets # list all 17 widgets
209
209
  python -m ui gallery --width 100 --height 30
210
210
  python -m ui demo table --width 80 --height 12 --theme dashboard
211
211
  ```
@@ -251,9 +251,9 @@ suite, see **[`README-USAGE.md`](README-USAGE.md)**.
251
251
  ## Features
252
252
 
253
253
  **`cmd-art`** (`skills/cmd-art`) — a "living-template" effect engine: an `Effect` ABC +
254
- `@register` decorator + auto-discovery. **22 effects** (donut, solarsystem, fire, plasma,
254
+ `@register` decorator + auto-discovery. **28 effects** (donut, solarsystem, fire, plasma,
255
255
  rain, starfield, tunnel, text3d, cube, sphere, boids, life, fireworks, sparkle, decrypt,
256
- gradient_text, banner_scroll, image2ascii, typewriter, julia, mandelbrot, perlin) across **8 themes** (mono, fire,
256
+ gradient_text, banner_scroll, image2ascii, typewriter, julia, mandelbrot, perlin, flames, water, nebula, text_flyin, text_converge, text_decrypt) across **8 themes** (mono, fire,
257
257
  ocean, synthwave, viridis, pastel, matrix-green, rainbow). Effects are pure frame
258
258
  producers; `play` is bounded by default and always restores the terminal.
259
259
 
@@ -285,9 +285,9 @@ cross-links. See [`knowledge/INDEX.md`](knowledge/INDEX.md).
285
285
  ```text
286
286
  SmartCLI/
287
287
  smartcli_core/ shared PTY + pyte engine (importable package)
288
- skills/cmd-art/ fx effect package and CLI (22 effects, 8 themes)
288
+ skills/cmd-art/ fx effect package and CLI (28 effects, 8 themes)
289
289
  skills/drive-tui/ TUI pattern library and PTY driver CLI (8 recipes)
290
- skills/tui-ui/ terminal UI layout engine and widgets (15 widgets)
290
+ skills/tui-ui/ terminal UI layout engine and widgets (17 widgets)
291
291
  tools/screenshot/ pyte -> PNG smoke-test harness
292
292
  tools/agentcli/ agent-CLI control validation harness
293
293
  knowledge/ 122-note knowledge graph (see knowledge/INDEX.md)
@@ -149,7 +149,7 @@ backend, and dependency versions — run it and paste the output when filing a b
149
149
 
150
150
  ```bash
151
151
  cd skills/cmd-art
152
- python -m fx list # list all 22 effects
152
+ python -m fx list # list all 28 effects
153
153
  python -m fx play donut --seconds 5 # play one effect (bounded)
154
154
  python -m fx gallery # one frame of each effect
155
155
  python -m fx show --seq "donut:fire:3,plasma::3"
@@ -159,7 +159,7 @@ python -m fx show --seq "donut:fire:3,plasma::3"
159
159
 
160
160
  ```bash
161
161
  cd skills/tui-ui
162
- python -m ui widgets # list all 15 widgets
162
+ python -m ui widgets # list all 17 widgets
163
163
  python -m ui gallery --width 100 --height 30
164
164
  python -m ui demo table --width 80 --height 12 --theme dashboard
165
165
  ```
@@ -205,9 +205,9 @@ suite, see **[`README-USAGE.md`](README-USAGE.md)**.
205
205
  ## Features
206
206
 
207
207
  **`cmd-art`** (`skills/cmd-art`) — a "living-template" effect engine: an `Effect` ABC +
208
- `@register` decorator + auto-discovery. **22 effects** (donut, solarsystem, fire, plasma,
208
+ `@register` decorator + auto-discovery. **28 effects** (donut, solarsystem, fire, plasma,
209
209
  rain, starfield, tunnel, text3d, cube, sphere, boids, life, fireworks, sparkle, decrypt,
210
- gradient_text, banner_scroll, image2ascii, typewriter, julia, mandelbrot, perlin) across **8 themes** (mono, fire,
210
+ gradient_text, banner_scroll, image2ascii, typewriter, julia, mandelbrot, perlin, flames, water, nebula, text_flyin, text_converge, text_decrypt) across **8 themes** (mono, fire,
211
211
  ocean, synthwave, viridis, pastel, matrix-green, rainbow). Effects are pure frame
212
212
  producers; `play` is bounded by default and always restores the terminal.
213
213
 
@@ -239,9 +239,9 @@ cross-links. See [`knowledge/INDEX.md`](knowledge/INDEX.md).
239
239
  ```text
240
240
  SmartCLI/
241
241
  smartcli_core/ shared PTY + pyte engine (importable package)
242
- skills/cmd-art/ fx effect package and CLI (22 effects, 8 themes)
242
+ skills/cmd-art/ fx effect package and CLI (28 effects, 8 themes)
243
243
  skills/drive-tui/ TUI pattern library and PTY driver CLI (8 recipes)
244
- skills/tui-ui/ terminal UI layout engine and widgets (15 widgets)
244
+ skills/tui-ui/ terminal UI layout engine and widgets (17 widgets)
245
245
  tools/screenshot/ pyte -> PNG smoke-test harness
246
246
  tools/agentcli/ agent-CLI control validation harness
247
247
  knowledge/ 122-note knowledge graph (see knowledge/INDEX.md)
@@ -30,7 +30,7 @@ build-backend = "setuptools.build_meta"
30
30
 
31
31
  [project]
32
32
  name = "smartcli-toolkit"
33
- version = "0.1.5"
33
+ version = "0.1.6"
34
34
  description = "Pluggable-PTY + pyte screen model + semantic snapshot / readiness core for driving interactive terminal programs (SmartCLI shared core)."
35
35
  readme = "README.md"
36
36
  license = "MIT"
@@ -55,4 +55,4 @@ __all__ = [
55
55
  "KEY_MAP",
56
56
  ]
57
57
 
58
- __version__ = "0.1.5"
58
+ __version__ = "0.1.6"
@@ -20,6 +20,7 @@ Key tokens (``send_keys``) are mapped to escape bytes via :data:`KEY_MAP`.
20
20
 
21
21
  from __future__ import annotations
22
22
 
23
+ import time
23
24
  from typing import List, Optional, Tuple, Union, Sequence
24
25
 
25
26
  from .pty_backend import PtyBackend, get_default_backend
@@ -295,3 +296,38 @@ class PtySession:
295
296
  flags=flags,
296
297
  )
297
298
  return matched, snap # type: ignore[return-value]
299
+
300
+ def wait_change(
301
+ self,
302
+ baseline_hash: Optional[str] = None,
303
+ timeout_ms: int = 10000,
304
+ poll_ms: int = 30,
305
+ ) -> Tuple[bool, Snapshot]:
306
+ """Wait until the screen content changes away from ``baseline_hash``.
307
+
308
+ The precise "did my action land?" primitive: after sending input, block
309
+ until the screen's content hash differs from the baseline (by default,
310
+ the hash at the moment of the call). Returns (changed, snapshot) —
311
+ ``changed`` is False on timeout, and the snapshot is always the latest
312
+ screen so the caller can inspect it either way. Complements wait_stable
313
+ (settle) / wait_for (a specific marker): this catches ANY change, which
314
+ is what you want right after acting, and it can't false-positive on a
315
+ screen that was already showing the target text.
316
+
317
+ This is a thin session-level poll over the existing pump + content_hash;
318
+ it adds no new core state.
319
+ """
320
+ if baseline_hash is None:
321
+ # Baseline = the screen as it stands NOW, WITHOUT draining pending
322
+ # bytes first — otherwise the very output we're waiting for could be
323
+ # folded into the baseline and never register as a change.
324
+ baseline_hash = self.model.content_hash()
325
+ deadline = time.monotonic() + timeout_ms / 1000.0
326
+ poll_s = max(0.0, poll_ms / 1000.0)
327
+ while True:
328
+ self.pump()
329
+ if self.model.content_hash() != baseline_hash:
330
+ return True, self.snapshot()
331
+ if time.monotonic() >= deadline:
332
+ return False, self.snapshot()
333
+ time.sleep(poll_s)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: smartcli-toolkit
3
- Version: 0.1.5
3
+ Version: 0.1.6
4
4
  Summary: Pluggable-PTY + pyte screen model + semantic snapshot / readiness core for driving interactive terminal programs (SmartCLI shared core).
5
5
  Author: dwgx
6
6
  License-Expression: MIT
@@ -195,7 +195,7 @@ backend, and dependency versions — run it and paste the output when filing a b
195
195
 
196
196
  ```bash
197
197
  cd skills/cmd-art
198
- python -m fx list # list all 22 effects
198
+ python -m fx list # list all 28 effects
199
199
  python -m fx play donut --seconds 5 # play one effect (bounded)
200
200
  python -m fx gallery # one frame of each effect
201
201
  python -m fx show --seq "donut:fire:3,plasma::3"
@@ -205,7 +205,7 @@ python -m fx show --seq "donut:fire:3,plasma::3"
205
205
 
206
206
  ```bash
207
207
  cd skills/tui-ui
208
- python -m ui widgets # list all 15 widgets
208
+ python -m ui widgets # list all 17 widgets
209
209
  python -m ui gallery --width 100 --height 30
210
210
  python -m ui demo table --width 80 --height 12 --theme dashboard
211
211
  ```
@@ -251,9 +251,9 @@ suite, see **[`README-USAGE.md`](README-USAGE.md)**.
251
251
  ## Features
252
252
 
253
253
  **`cmd-art`** (`skills/cmd-art`) — a "living-template" effect engine: an `Effect` ABC +
254
- `@register` decorator + auto-discovery. **22 effects** (donut, solarsystem, fire, plasma,
254
+ `@register` decorator + auto-discovery. **28 effects** (donut, solarsystem, fire, plasma,
255
255
  rain, starfield, tunnel, text3d, cube, sphere, boids, life, fireworks, sparkle, decrypt,
256
- gradient_text, banner_scroll, image2ascii, typewriter, julia, mandelbrot, perlin) across **8 themes** (mono, fire,
256
+ gradient_text, banner_scroll, image2ascii, typewriter, julia, mandelbrot, perlin, flames, water, nebula, text_flyin, text_converge, text_decrypt) across **8 themes** (mono, fire,
257
257
  ocean, synthwave, viridis, pastel, matrix-green, rainbow). Effects are pure frame
258
258
  producers; `play` is bounded by default and always restores the terminal.
259
259
 
@@ -285,9 +285,9 @@ cross-links. See [`knowledge/INDEX.md`](knowledge/INDEX.md).
285
285
  ```text
286
286
  SmartCLI/
287
287
  smartcli_core/ shared PTY + pyte engine (importable package)
288
- skills/cmd-art/ fx effect package and CLI (22 effects, 8 themes)
288
+ skills/cmd-art/ fx effect package and CLI (28 effects, 8 themes)
289
289
  skills/drive-tui/ TUI pattern library and PTY driver CLI (8 recipes)
290
- skills/tui-ui/ terminal UI layout engine and widgets (15 widgets)
290
+ skills/tui-ui/ terminal UI layout engine and widgets (17 widgets)
291
291
  tools/screenshot/ pyte -> PNG smoke-test harness
292
292
  tools/agentcli/ agent-CLI control validation harness
293
293
  knowledge/ 122-note knowledge graph (see knowledge/INDEX.md)
@@ -21,4 +21,5 @@ tests/test_doc_counts.py
21
21
  tests/test_fx_contract.py
22
22
  tests/test_golden_frames.py
23
23
  tests/test_readiness.py
24
- tests/test_vendor_sync.py
24
+ tests/test_vendor_sync.py
25
+ tests/test_wait_change.py
@@ -0,0 +1,115 @@
1
+ #!/usr/bin/env python3
2
+ """test_wait_change.py — PtySession.wait_change regression (deterministic).
3
+
4
+ wait_change is the precise "did my action land?" primitive: block until the
5
+ screen content hash changes away from a baseline. This locks:
6
+ * baseline is the screen NOW, without draining pending bytes first (so the
7
+ awaited output can't be folded into the baseline and missed),
8
+ * a real content change returns (True, snapshot),
9
+ * no change within the window returns (False, latest snapshot),
10
+ * an explicit baseline_hash lets a caller wait for change from a known state.
11
+
12
+ Pure/in-memory: a fake backend feeds bytes on demand. No PTY, no process.
13
+ """
14
+ from __future__ import annotations
15
+
16
+ import sys
17
+ from pathlib import Path
18
+
19
+ ROOT = Path(__file__).resolve().parents[1]
20
+ sys.path.insert(0, str(ROOT))
21
+
22
+ from smartcli_core import PtySession # noqa: E402
23
+ from smartcli_core.pty_backend import PtyBackend # noqa: E402
24
+
25
+ failures = 0
26
+
27
+
28
+ def check(cond, label, detail=""):
29
+ global failures
30
+ if not cond:
31
+ failures += 1
32
+ print(f"{'PASS' if cond else 'FAIL'} {label}" + (f" -- {detail}" if detail else ""))
33
+
34
+
35
+ class FakeBackend(PtyBackend):
36
+ def __init__(self):
37
+ self._q = []
38
+ self._alive = True
39
+
40
+ def queue(self, data):
41
+ self._q.append(data)
42
+
43
+ def spawn(self, cmd, cols, rows):
44
+ pass
45
+
46
+ def read_nonblocking(self, timeout=0.0):
47
+ return self._q.pop(0) if self._q else b""
48
+
49
+ def write(self, data):
50
+ pass
51
+
52
+ def resize(self, cols, rows):
53
+ pass
54
+
55
+ def is_alive(self):
56
+ return self._alive
57
+
58
+ def terminate(self):
59
+ self._alive = False
60
+
61
+
62
+ def test_change_detected():
63
+ be = FakeBackend()
64
+ sess = PtySession(cols=40, rows=10, backend=be)
65
+ be.queue(b"hello world")
66
+ changed, snap = sess.wait_change(timeout_ms=2000)
67
+ check(changed is True, "content change is detected")
68
+ check("hello" in snap.to_text(), "returned snapshot shows the new content")
69
+
70
+
71
+ def test_no_change_times_out():
72
+ be = FakeBackend()
73
+ sess = PtySession(cols=40, rows=10, backend=be)
74
+ changed, snap = sess.wait_change(timeout_ms=250)
75
+ check(changed is False, "no change within the window -> False (timeout)")
76
+ check(snap is not None, "timeout still returns the latest snapshot")
77
+
78
+
79
+ def test_baseline_not_polluted():
80
+ # The bug this guards: if wait_change drained pending bytes to form the
81
+ # baseline, the awaited output would be in the baseline and never seen.
82
+ be = FakeBackend()
83
+ sess = PtySession(cols=40, rows=10, backend=be)
84
+ be.queue(b"the output I am waiting for") # already queued before the call
85
+ changed, _ = sess.wait_change(timeout_ms=2000)
86
+ check(changed is True, "pending bytes at call time still count as a change")
87
+
88
+
89
+ def test_explicit_baseline():
90
+ be = FakeBackend()
91
+ sess = PtySession(cols=40, rows=10, backend=be)
92
+ be.queue(b"first")
93
+ sess.wait_change(timeout_ms=2000) # consume 'first'
94
+ h = sess.model.content_hash()
95
+ be.queue(b"\x1b[2J\x1b[Hsecond") # clear + new
96
+ changed, snap = sess.wait_change(baseline_hash=h, timeout_ms=2000)
97
+ check(changed is True, "explicit baseline_hash detects change from a known state")
98
+ check("second" in snap.to_text(), "snapshot reflects the change")
99
+
100
+
101
+ def main():
102
+ test_change_detected()
103
+ test_no_change_times_out()
104
+ test_baseline_not_polluted()
105
+ test_explicit_baseline()
106
+ print()
107
+ if failures:
108
+ print(f"{failures} FAILURE(S)")
109
+ return 1
110
+ print("ALL PASS")
111
+ return 0
112
+
113
+
114
+ if __name__ == "__main__":
115
+ sys.exit(main())