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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ebeda13c1510dfd65ec0dd5e45a330ed9100c3dd5a0919dfcedb13340e01a280
4
- data.tar.gz: fe33d4368ae7b516f18b7cc30974124408214dbd116f6e300986b05a967e4835
3
+ metadata.gz: 3eb2dc4db4af2ddbcbe377f8a3d6bddf8e1a45f3c78c85a25d8480d1e740e9f7
4
+ data.tar.gz: 31a65006ceb299ba4487446932bf74305ecb92d4241288f82d7d11a0283e6bf6
5
5
  SHA512:
6
- metadata.gz: f1c7051f157319f46d96f371654d276ada570e2aea06d4871659cf989734f5c3d65c0da163a23d56ebb844d3c3cb3b5087b6fcc60f8dcebfdbec0d35ab317720
7
- data.tar.gz: 2f74615955a3434efe4d06427099ae18eca2c43bdefe93b6c8b2465ebb2eb28f84024ecbcfcbda39cedec4e5ac0373141d6f6ec0ac31aff6a64f7cf8b9ebb781
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. Clean up
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
- driver.start
215
- driver.wait_for_stable
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 (no new data for N milliseconds)
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
- if read_available!
108
- last_change = monotonic
109
- elsif (monotonic - last_change) * 1000 >= stable_ms
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
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TUITD
4
- VERSION = "0.2.8"
4
+ VERSION = "0.2.10"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tui-td
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.8
4
+ version: 0.2.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Haluk Durmus