tui-td 0.2.9 → 0.2.11

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.
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # rubocop:disable Metrics/ClassLength, Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Layout/LineLength
4
+
3
5
  require "json"
4
6
 
5
7
  module TUITD
@@ -35,7 +37,7 @@ module TUITD
35
37
  $stderr.sync = true
36
38
 
37
39
  # Signal readiness
38
- $stderr.puts "[tui-td MCP] Server started, awaiting JSON-RPC on stdin..."
40
+ warn "[tui-td MCP] Server started, awaiting JSON-RPC on stdin..."
39
41
 
40
42
  while @running && (line = $stdin.gets)
41
43
  line = line.strip
@@ -48,10 +50,10 @@ module TUITD
48
50
  puts JSON.generate(response) if response
49
51
  $stdout.flush
50
52
  rescue JSON::ParserError => e
51
- error_response(nil, -32700, "Parse error: #{e.message}")
53
+ error_response(nil, -32_700, "Parse error: #{e.message}")
52
54
  rescue StandardError => e
53
- $stderr.puts "[tui-td MCP] Error: #{e.class}: #{e.message}"
54
- $stderr.puts e.backtrace.first(5).join("\n ") if $DEBUG
55
+ warn "[tui-td MCP] Error: #{e.class}: #{e.message}"
56
+ warn e.backtrace.first(5).join("\n ") if $DEBUG
55
57
  end
56
58
  end
57
59
 
@@ -69,35 +71,35 @@ module TUITD
69
71
  when "initialize"
70
72
  handle_initialize(params, id)
71
73
  when "notifications/initialized"
72
- nil # No response needed
74
+ nil # No response needed
73
75
  when "tools/list"
74
76
  handle_tools_list(id)
75
77
  when "tools/call"
76
78
  handle_tools_call(params, id)
77
79
  else
78
80
  if method&.start_with?("notifications/")
79
- nil # Ignore unknown notifications
81
+ nil # Ignore unknown notifications
80
82
  else
81
- error_response(id, -32601, "Method not found: #{method}")
83
+ error_response(id, -32_601, "Method not found: #{method}")
82
84
  end
83
85
  end
84
86
  end
85
87
 
86
88
  # Initialize handshake
87
- def handle_initialize(params, id)
89
+ def handle_initialize(_params, id)
88
90
  {
89
91
  jsonrpc: "2.0",
90
92
  id: id,
91
93
  result: {
92
94
  protocolVersion: PROTOCOL_VERSION,
93
95
  capabilities: {
94
- tools: {}
96
+ tools: {},
95
97
  },
96
98
  serverInfo: {
97
99
  name: SERVER_NAME,
98
- version: SERVER_VERSION
99
- }
100
- }
100
+ version: SERVER_VERSION,
101
+ },
102
+ },
101
103
  }
102
104
  end
103
105
 
@@ -116,26 +118,26 @@ module TUITD
116
118
  properties: {
117
119
  command: {
118
120
  type: "string",
119
- description: "The command to run (e.g., 'htop', 'vim file.txt')"
121
+ description: "The command to run (e.g., 'htop', 'vim file.txt')",
120
122
  },
121
123
  rows: {
122
124
  type: "integer",
123
125
  description: "Terminal height in rows (default: 40)",
124
- default: 40
126
+ default: 40,
125
127
  },
126
128
  cols: {
127
129
  type: "integer",
128
130
  description: "Terminal width in columns (default: 120)",
129
- default: 120
131
+ default: 120,
130
132
  },
131
133
  timeout: {
132
134
  type: "integer",
133
135
  description: "Timeout in seconds for waits (default: 30)",
134
- default: 30
135
- }
136
+ default: 30,
137
+ },
136
138
  },
137
- required: ["command"]
138
- }
139
+ required: ["command"],
140
+ },
139
141
  },
140
142
  {
141
143
  name: "tui_send",
@@ -145,11 +147,11 @@ module TUITD
145
147
  properties: {
146
148
  text: {
147
149
  type: "string",
148
- description: "Text to send to the TUI. Use \\n for enter/newline."
149
- }
150
+ description: "Text to send to the TUI. Use \\n for enter/newline.",
151
+ },
150
152
  },
151
- required: ["text"]
152
- }
153
+ required: ["text"],
154
+ },
153
155
  },
154
156
  {
155
157
  name: "tui_send_key",
@@ -159,14 +161,14 @@ module TUITD
159
161
  properties: {
160
162
  key: {
161
163
  type: "string",
162
- enum: ["enter", "tab", "escape", "up", "down", "left", "right",
163
- "backspace", "ctrl_c", "ctrl_d", "ctrl_z", "page_up", "page_down",
164
- "home", "end", "delete"],
165
- description: "Key to press"
166
- }
164
+ enum: %w[enter tab escape up down left right
165
+ backspace ctrl_c ctrl_d ctrl_z page_up page_down
166
+ home end delete],
167
+ description: "Key to press",
168
+ },
167
169
  },
168
- required: ["key"]
169
- }
170
+ required: ["key"],
171
+ },
170
172
  },
171
173
  {
172
174
  name: "tui_wait_for_text",
@@ -176,16 +178,16 @@ module TUITD
176
178
  properties: {
177
179
  text: {
178
180
  type: "string",
179
- description: "Text to wait for in the terminal output"
181
+ description: "Text to wait for in the terminal output",
180
182
  },
181
183
  timeout: {
182
184
  type: "integer",
183
185
  description: "Custom timeout in seconds (overrides default)",
184
- default: 30
185
- }
186
+ default: 30,
187
+ },
186
188
  },
187
- required: ["text"]
188
- }
189
+ required: ["text"],
190
+ },
189
191
  },
190
192
  {
191
193
  name: "tui_wait_for_stable",
@@ -196,10 +198,10 @@ module TUITD
196
198
  timeout: {
197
199
  type: "integer",
198
200
  description: "Custom timeout in seconds",
199
- default: 30
200
- }
201
- }
202
- }
201
+ default: 30,
202
+ },
203
+ },
204
+ },
203
205
  },
204
206
  {
205
207
  name: "tui_state",
@@ -209,20 +211,20 @@ module TUITD
209
211
  properties: {
210
212
  format: {
211
213
  type: "string",
212
- enum: ["ai", "full", "text"],
214
+ enum: %w[ai full text],
213
215
  description: "Output format: 'ai' (compact, text+highlights, default), 'full' (complete cell grid with ANSI colors), 'text' (plain text only)",
214
- default: "ai"
215
- }
216
- }
217
- }
216
+ default: "ai",
217
+ },
218
+ },
219
+ },
218
220
  },
219
221
  {
220
222
  name: "tui_plain_text",
221
223
  description: "Get the current terminal content as plain text (all ANSI stripped).",
222
224
  inputSchema: {
223
225
  type: "object",
224
- properties: {}
225
- }
226
+ properties: {},
227
+ },
226
228
  },
227
229
  {
228
230
  name: "tui_screenshot",
@@ -232,10 +234,10 @@ module TUITD
232
234
  properties: {
233
235
  path: {
234
236
  type: "string",
235
- description: "Output file path (optional, auto-generated if omitted)"
236
- }
237
- }
238
- }
237
+ description: "Output file path (optional, auto-generated if omitted)",
238
+ },
239
+ },
240
+ },
239
241
  },
240
242
  {
241
243
  name: "tui_html_render",
@@ -245,21 +247,51 @@ module TUITD
245
247
  properties: {
246
248
  path: {
247
249
  type: "string",
248
- description: "Optional file path to save the HTML. If omitted, the HTML content is returned inline so you can view it directly."
249
- }
250
- }
251
- }
250
+ description: "Optional file path to save the HTML. If omitted, the HTML content is returned inline so you can view it directly.",
251
+ },
252
+ },
253
+ },
254
+ },
255
+ {
256
+ name: "tui_wait_for_exit",
257
+ description: "Wait until the TUI process exits. Returns the exit status code (0 = success, non-zero = error).",
258
+ inputSchema: {
259
+ type: "object",
260
+ properties: {},
261
+ },
262
+ },
263
+ {
264
+ name: "tui_exit_status",
265
+ description: "Get the exit status of the TUI process. Returns nil if still running, otherwise the exit code.",
266
+ inputSchema: {
267
+ type: "object",
268
+ properties: {},
269
+ },
270
+ },
271
+ {
272
+ name: "tui_find_text",
273
+ description: "Search for text or regex pattern in the current terminal state. Returns positions of all matches with surrounding context.",
274
+ inputSchema: {
275
+ type: "object",
276
+ properties: {
277
+ pattern: {
278
+ type: "string",
279
+ description: "Text or regex pattern to search for (e.g., 'error', 'ERROR|FAIL')",
280
+ },
281
+ },
282
+ required: ["pattern"],
283
+ },
252
284
  },
253
285
  {
254
286
  name: "tui_close",
255
287
  description: "Close the TUI application and clean up the PTY session. Call this when finished.",
256
288
  inputSchema: {
257
289
  type: "object",
258
- properties: {}
259
- }
260
- }
261
- ]
262
- }
290
+ properties: {},
291
+ },
292
+ },
293
+ ],
294
+ },
263
295
  }
264
296
  end
265
297
 
@@ -274,13 +306,16 @@ module TUITD
274
306
  when "tui_send_key" then call_tui_send_key(args)
275
307
  when "tui_wait_for_text" then call_tui_wait_for_text(args)
276
308
  when "tui_wait_for_stable" then call_tui_wait_for_stable(args)
277
- when "tui_state" then call_tui_state(args)
309
+ when "tui_state" then call_tui_state(args)
278
310
  when "tui_plain_text" then call_tui_plain_text
279
311
  when "tui_screenshot" then call_tui_screenshot(args)
280
312
  when "tui_html_render" then call_tui_html_render(args)
313
+ when "tui_wait_for_exit" then call_tui_wait_for_exit
314
+ when "tui_exit_status" then call_tui_exit_status
315
+ when "tui_find_text" then call_tui_find_text(args)
281
316
  when "tui_close" then call_tui_close
282
317
  else
283
- return error_response(id, -32602, "Unknown tool: #{tool_name}")
318
+ return error_response(id, -32_602, "Unknown tool: #{tool_name}")
284
319
  end
285
320
 
286
321
  {
@@ -290,10 +325,10 @@ module TUITD
290
325
  content: [
291
326
  {
292
327
  type: "text",
293
- text: result
294
- }
295
- ]
296
- }
328
+ text: result,
329
+ },
330
+ ],
331
+ },
297
332
  }
298
333
  rescue TUITD::TimeoutError => e
299
334
  {
@@ -303,11 +338,11 @@ module TUITD
303
338
  content: [
304
339
  {
305
340
  type: "text",
306
- text: "TIMEOUT: #{e.message}"
307
- }
341
+ text: "TIMEOUT: #{e.message}",
342
+ },
308
343
  ],
309
- isError: false
310
- }
344
+ isError: false,
345
+ },
311
346
  }
312
347
  rescue StandardError => e
313
348
  {
@@ -317,11 +352,11 @@ module TUITD
317
352
  content: [
318
353
  {
319
354
  type: "text",
320
- text: "ERROR: #{e.class}: #{e.message}"
321
- }
355
+ text: "ERROR: #{e.class}: #{e.message}",
356
+ },
322
357
  ],
323
- isError: true
324
- }
358
+ isError: true,
359
+ },
325
360
  }
326
361
  end
327
362
 
@@ -367,7 +402,7 @@ module TUITD
367
402
  state_to_ai_text(state_data)
368
403
  end
369
404
 
370
- def call_tui_wait_for_stable(args)
405
+ def call_tui_wait_for_stable(_args)
371
406
  ensure_driver!
372
407
  @driver.wait_for_stable
373
408
 
@@ -416,6 +451,40 @@ module TUITD
416
451
  end
417
452
  end
418
453
 
454
+ def call_tui_wait_for_exit
455
+ ensure_driver!
456
+ @driver.wait_for_exit
457
+ status = @driver.exitstatus
458
+ "OK: Process exited with status #{status}"
459
+ end
460
+
461
+ def call_tui_exit_status
462
+ ensure_driver!
463
+ status = @driver.exitstatus
464
+ if status.nil?
465
+ "Process is still running"
466
+ else
467
+ "Exit status: #{status}"
468
+ end
469
+ end
470
+
471
+ def call_tui_find_text(args)
472
+ ensure_driver!
473
+ pattern = args["pattern"] or return "ERROR: 'pattern' argument is required"
474
+ state = TUITD::State.new(@driver.state_data)
475
+ matches = state.find_text(pattern)
476
+
477
+ if matches.empty?
478
+ "No matches found for: #{pattern}"
479
+ else
480
+ lines = ["Found #{matches.size} match(es) for: #{pattern}"]
481
+ matches.each do |m|
482
+ lines << " row #{m[:row]}, col #{m[:col]}: #{m[:full_line].strip}"
483
+ end
484
+ lines.join("\n")
485
+ end
486
+ end
487
+
419
488
  def call_tui_close
420
489
  @driver&.close
421
490
  @driver = nil
@@ -454,10 +523,11 @@ module TUITD
454
523
  id: id,
455
524
  error: {
456
525
  code: code,
457
- message: message
458
- }
526
+ message: message,
527
+ },
459
528
  }
460
529
  end
461
530
  end
462
531
  end
463
532
  end
533
+ # rubocop:enable Metrics/ClassLength, Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Layout/LineLength