tui-td 0.2.8 → 0.2.10
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +19 -0
- data/README.md +16 -1
- data/lib/tui_td/cli.rb +12 -2
- data/lib/tui_td/driver.rb +28 -4
- data/lib/tui_td/mcp/server.rb +67 -0
- data/lib/tui_td/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3eb2dc4db4af2ddbcbe377f8a3d6bddf8e1a45f3c78c85a25d8480d1e740e9f7
|
|
4
|
+
data.tar.gz: 31a65006ceb299ba4487446932bf74305ecb92d4241288f82d7d11a0283e6bf6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 706d849d8c35380fabf149bcf6cee5441d92a71b228d7ac4bc0bbba3852be4443496d9d6ae79afd15daa5217b9ede798d6ce2d15dee2cbeb5f6a0901b5269722
|
|
7
|
+
data.tar.gz: 9595ce52cd78959dfb49f314d3272d6cf2c065dd215467e204c862b48da2a88525ab8a9dad0ab98ccdd7b7fc142e700eab4ca65ea6a8c2b90d53aefbe8eca922
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
## 0.2.10
|
|
4
|
+
|
|
5
|
+
- Three new MCP tools: `tui_wait_for_exit` (wait for process to end), `tui_exit_status` (get exit code), `tui_find_text` (search terminal state for text/regex matches)
|
|
6
|
+
- Document `tui_html_render` MCP tool in README (was already implemented but missing from docs)
|
|
7
|
+
- Smoke test expanded to 63 assertions covering all 13 MCP tools including new ones
|
|
8
|
+
|
|
9
|
+
## 0.2.9
|
|
10
|
+
|
|
11
|
+
- Fix: `wait_for_stable` uses parsed terminal grid comparison instead of raw byte arrival, preventing interactive TUIs that repaint cell-by-cell (e.g., glow) from timing out
|
|
12
|
+
- Fix: `cmd_capture` catches timeout for interactive TUIs and proceeds with whatever was rendered
|
|
13
|
+
- New tests for `driver.rb` (39), `cli.rb` (19), and `mcp/server.rb` (27) — 85 new tests total
|
|
14
|
+
|
|
15
|
+
## 0.2.8
|
|
16
|
+
|
|
17
|
+
- Unicode bitmap font in screenshot renderer: 2766 glyphs from GNU Unifont 17.0.04 covering Latin, Greek, Cyrillic, Arabic, Turkish, Math, Arrows, Box Drawing, Symbols, and Dingbats
|
|
18
|
+
- Cairo renderer as optional fallback for characters not in Unifont (e.g. CJK), with 3x supersampling and box-filter downsampling for sharp edges
|
|
19
|
+
- Rendering priority: Spleen (ASCII 33–126) → Unifont (127+, 2766 glyphs) → Cairo (fallback)
|
|
20
|
+
- Full test coverage for Unifont glyphs and Cairo renderer
|
|
21
|
+
|
|
3
22
|
## 0.2.7
|
|
4
23
|
|
|
5
24
|
- Screenshot rendering for 23 special characters: blocks (▀ ▄ █), triangles (▲ ▼), arrows (↑ ↓ → ←), half blocks (▌ ▐)
|
data/README.md
CHANGED
|
@@ -369,6 +369,10 @@ tui-td serve
|
|
|
369
369
|
| `tui_state` | Get terminal state: AI-friendly compact mode (default), `full` grid, or `text` only. |
|
|
370
370
|
| `tui_plain_text` | Get plain text content, ANSI stripped. |
|
|
371
371
|
| `tui_screenshot` | Capture a PNG screenshot of the current terminal. |
|
|
372
|
+
| `tui_html_render` | Render terminal state as a self-contained HTML document. Returns HTML inline or saves to file. |
|
|
373
|
+
| `tui_wait_for_exit` | Wait until the TUI process exits. Returns exit status. |
|
|
374
|
+
| `tui_exit_status` | Get the exit status code (nil if still running). |
|
|
375
|
+
| `tui_find_text` | Search for text or regex in terminal state. Returns positions of all matches. |
|
|
372
376
|
| `tui_close` | Close the TUI and clean up. |
|
|
373
377
|
|
|
374
378
|
### MCP configuration
|
|
@@ -407,7 +411,18 @@ Add to your MCP client configuration:
|
|
|
407
411
|
// 6. Take screenshot if needed
|
|
408
412
|
{"method": "tools/call", "params": {"name": "tui_screenshot", "arguments": {"path": "/tmp/proof.png"}}}
|
|
409
413
|
|
|
410
|
-
// 7.
|
|
414
|
+
// 7. Render as HTML (save to file or get inline)
|
|
415
|
+
{"method": "tools/call", "params": {"name": "tui_html_render", "arguments": {"path": "/tmp/proof.html"}}}
|
|
416
|
+
// Or without path to get HTML inline:
|
|
417
|
+
// {"method": "tools/call", "params": {"name": "tui_html_render", "arguments": {}}}
|
|
418
|
+
|
|
419
|
+
// 8. Search for text in the terminal
|
|
420
|
+
{"method": "tools/call", "params": {"name": "tui_find_text", "arguments": {"pattern": "error|fail"}}}
|
|
421
|
+
|
|
422
|
+
// 9. Check exit status (or wait for exit)
|
|
423
|
+
{"method": "tools/call", "params": {"name": "tui_exit_status", "arguments": {}}}
|
|
424
|
+
|
|
425
|
+
// 10. Clean up
|
|
411
426
|
{"method": "tools/call", "params": {"name": "tui_close", "arguments": {}}}
|
|
412
427
|
```
|
|
413
428
|
|
data/lib/tui_td/cli.rb
CHANGED
|
@@ -211,8 +211,18 @@ module TUITD
|
|
|
211
211
|
cmd = args.join(" ")
|
|
212
212
|
|
|
213
213
|
driver = Driver.new(cmd, **globals.slice(:rows, :cols, :timeout, :chdir))
|
|
214
|
-
|
|
215
|
-
|
|
214
|
+
begin
|
|
215
|
+
driver.start
|
|
216
|
+
rescue TimeoutError
|
|
217
|
+
# Interactive TUI that never stabilizes (e.g., glow without -p).
|
|
218
|
+
# Proceed with whatever was rendered before the timeout.
|
|
219
|
+
driver.refresh
|
|
220
|
+
end
|
|
221
|
+
begin
|
|
222
|
+
driver.wait_for_stable
|
|
223
|
+
rescue TimeoutError
|
|
224
|
+
# Ignored — already have rendered state from start
|
|
225
|
+
end
|
|
216
226
|
|
|
217
227
|
case globals[:format]
|
|
218
228
|
when :json
|
data/lib/tui_td/driver.rb
CHANGED
|
@@ -96,17 +96,28 @@ module TUITD
|
|
|
96
96
|
refresh_state!
|
|
97
97
|
end
|
|
98
98
|
|
|
99
|
-
# Wait for output to stabilize (
|
|
99
|
+
# Wait for output to stabilize (grid content unchanged for N milliseconds)
|
|
100
100
|
def wait_for_stable(stable_ms: 300)
|
|
101
101
|
deadline = monotonic + @timeout
|
|
102
102
|
last_change = monotonic
|
|
103
|
+
last_grid = nil
|
|
103
104
|
|
|
104
105
|
loop do
|
|
105
106
|
raise TimeoutError, "Timeout waiting for stable output" if monotonic > deadline
|
|
106
107
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
108
|
+
had_data = read_available!
|
|
109
|
+
process_alive = process_alive?
|
|
110
|
+
|
|
111
|
+
if had_data
|
|
112
|
+
current_grid = parse_grid_snapshot
|
|
113
|
+
if current_grid != last_grid
|
|
114
|
+
last_grid = current_grid
|
|
115
|
+
last_change = monotonic
|
|
116
|
+
end
|
|
117
|
+
elsif !process_alive
|
|
118
|
+
# Process exited and no more data — final state reached
|
|
119
|
+
break
|
|
120
|
+
elsif last_grid && (monotonic - last_change) * 1000 >= stable_ms
|
|
110
121
|
break
|
|
111
122
|
end
|
|
112
123
|
|
|
@@ -246,6 +257,19 @@ module TUITD
|
|
|
246
257
|
end
|
|
247
258
|
end
|
|
248
259
|
|
|
260
|
+
def parse_grid_snapshot
|
|
261
|
+
@output_mutex.synchronize do
|
|
262
|
+
ANSIParser.parse(@output_buffer, @rows, @cols)[:rows]
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def process_alive?
|
|
267
|
+
return false unless @pid
|
|
268
|
+
Process.waitpid(@pid, Process::WNOHANG).nil?
|
|
269
|
+
rescue Errno::ECHILD
|
|
270
|
+
false
|
|
271
|
+
end
|
|
272
|
+
|
|
249
273
|
def monotonic
|
|
250
274
|
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
251
275
|
end
|
data/lib/tui_td/mcp/server.rb
CHANGED
|
@@ -250,6 +250,36 @@ module TUITD
|
|
|
250
250
|
}
|
|
251
251
|
}
|
|
252
252
|
},
|
|
253
|
+
{
|
|
254
|
+
name: "tui_wait_for_exit",
|
|
255
|
+
description: "Wait until the TUI process exits. Returns the exit status code (0 = success, non-zero = error).",
|
|
256
|
+
inputSchema: {
|
|
257
|
+
type: "object",
|
|
258
|
+
properties: {}
|
|
259
|
+
}
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
name: "tui_exit_status",
|
|
263
|
+
description: "Get the exit status of the TUI process. Returns nil if still running, otherwise the exit code.",
|
|
264
|
+
inputSchema: {
|
|
265
|
+
type: "object",
|
|
266
|
+
properties: {}
|
|
267
|
+
}
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
name: "tui_find_text",
|
|
271
|
+
description: "Search for text or regex pattern in the current terminal state. Returns positions of all matches with surrounding context.",
|
|
272
|
+
inputSchema: {
|
|
273
|
+
type: "object",
|
|
274
|
+
properties: {
|
|
275
|
+
pattern: {
|
|
276
|
+
type: "string",
|
|
277
|
+
description: "Text or regex pattern to search for (e.g., 'error', 'ERROR|FAIL')"
|
|
278
|
+
}
|
|
279
|
+
},
|
|
280
|
+
required: ["pattern"]
|
|
281
|
+
}
|
|
282
|
+
},
|
|
253
283
|
{
|
|
254
284
|
name: "tui_close",
|
|
255
285
|
description: "Close the TUI application and clean up the PTY session. Call this when finished.",
|
|
@@ -278,6 +308,9 @@ module TUITD
|
|
|
278
308
|
when "tui_plain_text" then call_tui_plain_text
|
|
279
309
|
when "tui_screenshot" then call_tui_screenshot(args)
|
|
280
310
|
when "tui_html_render" then call_tui_html_render(args)
|
|
311
|
+
when "tui_wait_for_exit" then call_tui_wait_for_exit
|
|
312
|
+
when "tui_exit_status" then call_tui_exit_status
|
|
313
|
+
when "tui_find_text" then call_tui_find_text(args)
|
|
281
314
|
when "tui_close" then call_tui_close
|
|
282
315
|
else
|
|
283
316
|
return error_response(id, -32602, "Unknown tool: #{tool_name}")
|
|
@@ -416,6 +449,40 @@ module TUITD
|
|
|
416
449
|
end
|
|
417
450
|
end
|
|
418
451
|
|
|
452
|
+
def call_tui_wait_for_exit
|
|
453
|
+
ensure_driver!
|
|
454
|
+
@driver.wait_for_exit
|
|
455
|
+
status = @driver.exitstatus
|
|
456
|
+
"OK: Process exited with status #{status}"
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
def call_tui_exit_status
|
|
460
|
+
ensure_driver!
|
|
461
|
+
status = @driver.exitstatus
|
|
462
|
+
if status.nil?
|
|
463
|
+
"Process is still running"
|
|
464
|
+
else
|
|
465
|
+
"Exit status: #{status}"
|
|
466
|
+
end
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
def call_tui_find_text(args)
|
|
470
|
+
ensure_driver!
|
|
471
|
+
pattern = args["pattern"] or return "ERROR: 'pattern' argument is required"
|
|
472
|
+
state = TUITD::State.new(@driver.state_data)
|
|
473
|
+
matches = state.find_text(pattern)
|
|
474
|
+
|
|
475
|
+
if matches.empty?
|
|
476
|
+
"No matches found for: #{pattern}"
|
|
477
|
+
else
|
|
478
|
+
lines = ["Found #{matches.size} match(es) for: #{pattern}"]
|
|
479
|
+
matches.each do |m|
|
|
480
|
+
lines << " row #{m[:row]}, col #{m[:col]}: #{m[:full_line].strip}"
|
|
481
|
+
end
|
|
482
|
+
lines.join("\n")
|
|
483
|
+
end
|
|
484
|
+
end
|
|
485
|
+
|
|
419
486
|
def call_tui_close
|
|
420
487
|
@driver&.close
|
|
421
488
|
@driver = nil
|
data/lib/tui_td/version.rb
CHANGED