iterm2_ruby 0.2.0
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 +7 -0
- data/CHANGELOG.md +23 -0
- data/Gemfile +10 -0
- data/LICENSE +21 -0
- data/README.md +265 -0
- data/Rakefile +7 -0
- data/bin/iterm2ctl +620 -0
- data/docs/api.md +523 -0
- data/docs/architecture.md +91 -0
- data/docs/cli.md +257 -0
- data/iterm2_ruby.gemspec +29 -0
- data/lib/iterm2/client.rb +690 -0
- data/lib/iterm2/connection.rb +267 -0
- data/lib/iterm2/proto/api_pb.rb +233 -0
- data/lib/iterm2/session.rb +44 -0
- data/lib/iterm2/tab.rb +39 -0
- data/lib/iterm2/version.rb +5 -0
- data/lib/iterm2/window.rb +33 -0
- data/lib/iterm2.rb +106 -0
- data/llms.txt +114 -0
- data/proto/api.proto +1642 -0
- metadata +82 -0
data/bin/iterm2ctl
ADDED
|
@@ -0,0 +1,620 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "json"
|
|
5
|
+
require "optparse"
|
|
6
|
+
|
|
7
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
|
|
8
|
+
require "iterm2"
|
|
9
|
+
|
|
10
|
+
module ITerm2CTL
|
|
11
|
+
module_function
|
|
12
|
+
|
|
13
|
+
def run(args = ARGV)
|
|
14
|
+
command = args.shift
|
|
15
|
+
case command
|
|
16
|
+
when "list" then cmd_list(args)
|
|
17
|
+
when "tabs" then cmd_tabs(args)
|
|
18
|
+
when "send" then cmd_send(args)
|
|
19
|
+
when "send-text" then cmd_send_text(args)
|
|
20
|
+
when "read" then cmd_read(args)
|
|
21
|
+
when "read-screen" then cmd_read_screen(args)
|
|
22
|
+
when "raise" then cmd_raise(args)
|
|
23
|
+
when "activate-session" then cmd_activate_session(args)
|
|
24
|
+
when "create" then cmd_create(args)
|
|
25
|
+
when "split" then cmd_split(args)
|
|
26
|
+
when "close" then cmd_close(args)
|
|
27
|
+
when "move" then cmd_move(args)
|
|
28
|
+
when "var" then cmd_var(args)
|
|
29
|
+
when "info" then cmd_info(args)
|
|
30
|
+
when "focus" then cmd_focus(args)
|
|
31
|
+
when "prompt" then cmd_prompt(args)
|
|
32
|
+
when "watch" then cmd_watch(args)
|
|
33
|
+
when "profile" then cmd_profile(args)
|
|
34
|
+
when "inject" then cmd_inject(args)
|
|
35
|
+
when "profiles" then cmd_profiles(args)
|
|
36
|
+
when "version" then puts "iterm2ctl #{ITerm2::VERSION}"
|
|
37
|
+
when "help", nil then usage
|
|
38
|
+
else
|
|
39
|
+
$stderr.puts "Unknown command: #{command}"
|
|
40
|
+
usage
|
|
41
|
+
exit 1
|
|
42
|
+
end
|
|
43
|
+
rescue ITerm2::ConnectionError => e
|
|
44
|
+
$stderr.puts "Connection error: #{e.message}"
|
|
45
|
+
exit 2
|
|
46
|
+
rescue ITerm2::AuthError => e
|
|
47
|
+
$stderr.puts "Auth error: #{e.message}"
|
|
48
|
+
exit 3
|
|
49
|
+
rescue ITerm2::NotFoundError => e
|
|
50
|
+
$stderr.puts "Not found: #{e.message}"
|
|
51
|
+
exit 4
|
|
52
|
+
rescue ITerm2::RPCError => e
|
|
53
|
+
$stderr.puts "RPC error: #{e.message}"
|
|
54
|
+
exit 5
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def usage
|
|
58
|
+
puts <<~HELP
|
|
59
|
+
Usage: iterm2ctl <command> [options]
|
|
60
|
+
|
|
61
|
+
Commands:
|
|
62
|
+
list List all windows/tabs/sessions
|
|
63
|
+
list --json JSON topology output
|
|
64
|
+
list --triage Compact triage (window/tab/session/cwd/job)
|
|
65
|
+
list --with-cwd Include working directories
|
|
66
|
+
list --with-pid Include PIDs
|
|
67
|
+
tabs List tabs grouped by window
|
|
68
|
+
send TEXT [--session ID] Send text to a session
|
|
69
|
+
send-text ID TEXT Send text to specific session
|
|
70
|
+
read [--session ID] Read visible screen contents
|
|
71
|
+
read-screen ID Read screen for a specific session
|
|
72
|
+
raise PATTERN Raise tab matching title pattern
|
|
73
|
+
activate-session ID Activate a session directly
|
|
74
|
+
raise --cwd PATH Raise tab by working directory
|
|
75
|
+
create [window|tab] Create a new window or tab
|
|
76
|
+
split [--session ID] Split the current pane
|
|
77
|
+
close [--session ID] Close a session
|
|
78
|
+
move --tab ID --to-window ID Move a tab to another window
|
|
79
|
+
var get NAME [--session ID] Get a variable
|
|
80
|
+
var set NAME VALUE Set a variable (user.* only)
|
|
81
|
+
var all [--session ID] Dump all variables
|
|
82
|
+
info [--session ID] Show session tty, pid, cwd, job
|
|
83
|
+
focus Show current focus state
|
|
84
|
+
prompt [--session ID] Show prompt state (editing/running/finished)
|
|
85
|
+
watch [TYPE] [--session ID] Watch for events (focus/sessions/prompt/screen/layout)
|
|
86
|
+
profile [KEYS] [--session ID] Get profile properties
|
|
87
|
+
profiles [--properties K] List all profiles
|
|
88
|
+
inject TEXT [--session ID] Inject data as if from process
|
|
89
|
+
version Show version
|
|
90
|
+
help Show this help
|
|
91
|
+
|
|
92
|
+
Options:
|
|
93
|
+
--json Output as JSON
|
|
94
|
+
--session ID Target session by ID
|
|
95
|
+
--tab ID Target tab by ID
|
|
96
|
+
--window ID Target window by ID
|
|
97
|
+
HELP
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def cmd_list(args)
|
|
101
|
+
json_mode = args.delete("--json")
|
|
102
|
+
triage_mode = args.delete("--triage")
|
|
103
|
+
with_cwd = args.delete("--with-cwd") || triage_mode
|
|
104
|
+
with_pid = args.delete("--with-pid") || triage_mode
|
|
105
|
+
enriched = with_cwd || with_pid
|
|
106
|
+
|
|
107
|
+
ITerm2.connect do |client|
|
|
108
|
+
sessions = enriched ? client.topology_enriched : client.topology
|
|
109
|
+
|
|
110
|
+
if json_mode
|
|
111
|
+
puts JSON.pretty_generate(sessions)
|
|
112
|
+
elsif triage_mode
|
|
113
|
+
puts format("%-10s %-10s %-38s %-30s %-10s %-18s", "Window", "Tab", "Session", "CWD", "PID", "Job")
|
|
114
|
+
puts "-" * 130
|
|
115
|
+
sessions.each do |s|
|
|
116
|
+
puts format("%-10s %-10s %-38s %-30s %-10s %-18s",
|
|
117
|
+
s[:window_id],
|
|
118
|
+
s[:tab_id],
|
|
119
|
+
s[:session_id],
|
|
120
|
+
(s[:cwd] || "")[0, 30],
|
|
121
|
+
s[:pid],
|
|
122
|
+
(s[:job] || "")[0, 18])
|
|
123
|
+
end
|
|
124
|
+
else
|
|
125
|
+
if sessions.empty?
|
|
126
|
+
puts "No sessions found"
|
|
127
|
+
return
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
if enriched
|
|
131
|
+
fmt = "%-38s %-30s"
|
|
132
|
+
fmt += " %-30s" if with_cwd
|
|
133
|
+
fmt += " %-8s" if with_pid
|
|
134
|
+
header_args = ["Session", "Title"]
|
|
135
|
+
header_args << "CWD" if with_cwd
|
|
136
|
+
header_args << "PID" if with_pid
|
|
137
|
+
puts format(fmt, *header_args)
|
|
138
|
+
puts "-" * (70 + (with_cwd ? 32 : 0) + (with_pid ? 10 : 0))
|
|
139
|
+
sessions.each do |s|
|
|
140
|
+
row = [s[:session_id], s[:title]&.slice(0, 29)]
|
|
141
|
+
row << s[:cwd]&.slice(0, 29) if with_cwd
|
|
142
|
+
row << s[:pid].to_s if with_pid
|
|
143
|
+
puts format(fmt, *row)
|
|
144
|
+
end
|
|
145
|
+
else
|
|
146
|
+
puts format("%-12s %-12s %-38s %s", "Window", "Tab", "Session", "Title")
|
|
147
|
+
puts "-" * 90
|
|
148
|
+
sessions.each do |s|
|
|
149
|
+
puts format("%-12s %-12s %-38s %s",
|
|
150
|
+
s[:window_id], s[:tab_id], s[:session_id], s[:title])
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def cmd_tabs(args)
|
|
158
|
+
json_mode = args.delete("--json")
|
|
159
|
+
|
|
160
|
+
ITerm2.connect do |client|
|
|
161
|
+
grouped = client.topology.group_by { |s| s[:window_id] }
|
|
162
|
+
if json_mode
|
|
163
|
+
output = grouped.transform_values do |rows|
|
|
164
|
+
rows.group_by { |s| s[:tab_id] }.transform_values do |tab_rows|
|
|
165
|
+
{
|
|
166
|
+
session_count: tab_rows.count,
|
|
167
|
+
sessions: tab_rows.map { |r| { session_id: r[:session_id], title: r[:title] } }
|
|
168
|
+
}
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
puts JSON.pretty_generate(output)
|
|
172
|
+
else
|
|
173
|
+
grouped.keys.sort.each do |window_id|
|
|
174
|
+
puts "Window #{window_id}"
|
|
175
|
+
grouped[window_id].group_by { |s| s[:tab_id] }.each do |tab_id, tab_rows|
|
|
176
|
+
first_title = tab_rows.first[:title]
|
|
177
|
+
puts " Tab #{tab_id} (#{tab_rows.count} session#{tab_rows.count == 1 ? '' : 's'}) - #{first_title}"
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def cmd_send_text(args)
|
|
185
|
+
session_id = args.shift
|
|
186
|
+
text = args.join(" ")
|
|
187
|
+
if session_id.nil? || text.empty?
|
|
188
|
+
$stderr.puts "Usage: iterm2ctl send-text SESSION_ID TEXT"
|
|
189
|
+
exit 1
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
ITerm2.connect do |client|
|
|
193
|
+
client.send_text(session_id, text.end_with?("\n") ? text : "#{text}\n")
|
|
194
|
+
puts "Sent to #{session_id}"
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def cmd_read_screen(args)
|
|
199
|
+
session_id = args.shift
|
|
200
|
+
if session_id.nil?
|
|
201
|
+
$stderr.puts "Usage: iterm2ctl read-screen SESSION_ID [--json] [--scrollback N]"
|
|
202
|
+
exit 1
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
json_mode = args.delete("--json")
|
|
206
|
+
trailing = nil
|
|
207
|
+
if (idx = args.index("--scrollback"))
|
|
208
|
+
args.delete_at(idx)
|
|
209
|
+
trailing = args.delete_at(idx)&.to_i || 100
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
ITerm2.connect do |client|
|
|
213
|
+
result = client.read_screen(session_id, trailing_lines: trailing)
|
|
214
|
+
json_mode ? (puts JSON.pretty_generate(result)) : result[:lines].each { |line| puts line }
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def cmd_activate_session(args)
|
|
219
|
+
session_id = args.shift
|
|
220
|
+
if session_id.nil?
|
|
221
|
+
$stderr.puts "Usage: iterm2ctl activate-session SESSION_ID"
|
|
222
|
+
exit 1
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
ITerm2.connect { |client| client.activate_session(session_id) }
|
|
226
|
+
puts "Activated #{session_id}"
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def cmd_send(args)
|
|
230
|
+
opts = parse_target_opts(args)
|
|
231
|
+
text = args.join(" ")
|
|
232
|
+
|
|
233
|
+
if text.empty?
|
|
234
|
+
$stderr.puts "Usage: iterm2ctl send TEXT [--session ID]"
|
|
235
|
+
exit 1
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# Append newline if not present (like typing a command)
|
|
239
|
+
text += "\n" unless text.end_with?("\n")
|
|
240
|
+
|
|
241
|
+
ITerm2.connect do |client|
|
|
242
|
+
session_id = resolve_session(client, opts)
|
|
243
|
+
client.send_text(session_id, text)
|
|
244
|
+
puts "Sent to #{session_id}"
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def cmd_read(args)
|
|
249
|
+
opts = parse_target_opts(args)
|
|
250
|
+
json_mode = args.delete("--json")
|
|
251
|
+
trailing = nil
|
|
252
|
+
|
|
253
|
+
if (idx = args.index("--scrollback"))
|
|
254
|
+
args.delete_at(idx)
|
|
255
|
+
trailing = args.delete_at(idx)&.to_i || 100
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
ITerm2.connect do |client|
|
|
259
|
+
session_id = resolve_session(client, opts)
|
|
260
|
+
result = client.read_screen(session_id, trailing_lines: trailing)
|
|
261
|
+
|
|
262
|
+
if json_mode
|
|
263
|
+
puts JSON.pretty_generate(result)
|
|
264
|
+
else
|
|
265
|
+
result[:lines].each { |line| puts line }
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
def cmd_raise(args)
|
|
271
|
+
opts = parse_target_opts(args)
|
|
272
|
+
cwd_mode = false
|
|
273
|
+
if (idx = args.index("--cwd"))
|
|
274
|
+
args.delete_at(idx)
|
|
275
|
+
cwd_mode = true
|
|
276
|
+
end
|
|
277
|
+
pattern = args.join(" ")
|
|
278
|
+
|
|
279
|
+
if opts[:session]
|
|
280
|
+
ITerm2.connect { |c| c.activate_session(opts[:session]) }
|
|
281
|
+
puts "Raised session #{opts[:session]}"
|
|
282
|
+
elsif pattern.empty?
|
|
283
|
+
$stderr.puts "Usage: iterm2ctl raise PATTERN [--cwd] [--session ID]"
|
|
284
|
+
exit 1
|
|
285
|
+
elsif cwd_mode
|
|
286
|
+
ITerm2.connect { |c| c.raise_by_cwd(pattern) }
|
|
287
|
+
puts "Raised tab with cwd matching #{pattern.inspect}"
|
|
288
|
+
else
|
|
289
|
+
ITerm2.connect { |c| c.raise_by_title(pattern) }
|
|
290
|
+
puts "Raised tab matching #{pattern.inspect}"
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
def cmd_create(args)
|
|
295
|
+
opts = parse_target_opts(args)
|
|
296
|
+
type = args.shift || "tab"
|
|
297
|
+
|
|
298
|
+
ITerm2.connect do |client|
|
|
299
|
+
case type
|
|
300
|
+
when "window"
|
|
301
|
+
result = client.create_tab(profile_name: opts[:profile])
|
|
302
|
+
puts "Created window #{result[:window_id]} with session #{result[:session_id]}"
|
|
303
|
+
when "tab"
|
|
304
|
+
result = client.create_tab(window_id: opts[:window], profile_name: opts[:profile])
|
|
305
|
+
puts "Created tab #{result[:tab_id]} with session #{result[:session_id]}"
|
|
306
|
+
else
|
|
307
|
+
$stderr.puts "Usage: iterm2ctl create [window|tab]"
|
|
308
|
+
exit 1
|
|
309
|
+
end
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
def cmd_split(args)
|
|
314
|
+
opts = parse_target_opts(args)
|
|
315
|
+
vertical = !args.delete("--horizontal")
|
|
316
|
+
|
|
317
|
+
ITerm2.connect do |client|
|
|
318
|
+
session_id = resolve_session(client, opts)
|
|
319
|
+
new_id = client.split_pane(session_id, vertical: vertical, profile_name: opts[:profile])
|
|
320
|
+
puts "Split #{session_id} -> #{new_id}"
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
def cmd_close(args)
|
|
325
|
+
opts = parse_target_opts(args)
|
|
326
|
+
force = args.delete("--force")
|
|
327
|
+
|
|
328
|
+
ITerm2.connect do |client|
|
|
329
|
+
if opts[:tab]
|
|
330
|
+
client.close_tab(opts[:tab], force: !!force)
|
|
331
|
+
puts "Closed tab #{opts[:tab]}"
|
|
332
|
+
else
|
|
333
|
+
session_id = resolve_session(client, opts)
|
|
334
|
+
client.close_session(session_id, force: !!force)
|
|
335
|
+
puts "Closed session #{session_id}"
|
|
336
|
+
end
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
def cmd_move(args)
|
|
341
|
+
opts = parse_target_opts(args)
|
|
342
|
+
to_window = nil
|
|
343
|
+
if (idx = args.index("--to-window"))
|
|
344
|
+
args.delete_at(idx)
|
|
345
|
+
to_window = args.delete_at(idx)
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
tab_id = opts[:tab]
|
|
349
|
+
if tab_id.nil? || to_window.nil?
|
|
350
|
+
$stderr.puts "Usage: iterm2ctl move --tab TAB_ID --to-window WINDOW_ID"
|
|
351
|
+
exit 1
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
ITerm2.connect do |client|
|
|
355
|
+
# Get current tab order for the target window, then append the moved tab
|
|
356
|
+
sessions = client.topology
|
|
357
|
+
existing_tabs = sessions.select { |s| s[:window_id] == to_window }.map { |s| s[:tab_id] }.uniq
|
|
358
|
+
new_order = existing_tabs + [tab_id]
|
|
359
|
+
|
|
360
|
+
client.reorder_tabs(to_window => new_order)
|
|
361
|
+
puts "Moved tab #{tab_id} to window #{to_window}"
|
|
362
|
+
end
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
def cmd_var(args)
|
|
366
|
+
opts = parse_target_opts(args)
|
|
367
|
+
subcmd = args.shift
|
|
368
|
+
|
|
369
|
+
case subcmd
|
|
370
|
+
when "get"
|
|
371
|
+
name = args.shift
|
|
372
|
+
if name.nil?
|
|
373
|
+
$stderr.puts "Usage: iterm2ctl var get NAME [--session ID]"
|
|
374
|
+
exit 1
|
|
375
|
+
end
|
|
376
|
+
ITerm2.connect do |client|
|
|
377
|
+
scope = resolve_var_scope(client, opts)
|
|
378
|
+
val = client.get_variable(name, **scope)
|
|
379
|
+
puts val.is_a?(String) ? val : JSON.pretty_generate(val)
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
when "set"
|
|
383
|
+
name = args.shift
|
|
384
|
+
value = args.shift
|
|
385
|
+
if name.nil? || value.nil?
|
|
386
|
+
$stderr.puts "Usage: iterm2ctl var set NAME VALUE [--session ID]"
|
|
387
|
+
exit 1
|
|
388
|
+
end
|
|
389
|
+
ITerm2.connect do |client|
|
|
390
|
+
scope = resolve_var_scope(client, opts)
|
|
391
|
+
parsed = begin; JSON.parse(value); rescue; value; end
|
|
392
|
+
client.set_variables({ name => parsed }, **scope)
|
|
393
|
+
puts "Set #{name}"
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
when "all"
|
|
397
|
+
json_mode = args.delete("--json")
|
|
398
|
+
ITerm2.connect do |client|
|
|
399
|
+
scope = resolve_var_scope(client, opts)
|
|
400
|
+
all = client.get_variables("*", **scope)
|
|
401
|
+
if json_mode
|
|
402
|
+
puts JSON.pretty_generate(all)
|
|
403
|
+
else
|
|
404
|
+
all.sort_by { |k, _| k }.each do |k, v|
|
|
405
|
+
puts format("%-45s %s", k, v.is_a?(String) ? v : JSON.dump(v))
|
|
406
|
+
end
|
|
407
|
+
end
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
else
|
|
411
|
+
$stderr.puts "Usage: iterm2ctl var [get|set|all] ..."
|
|
412
|
+
exit 1
|
|
413
|
+
end
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
def cmd_info(args)
|
|
417
|
+
opts = parse_target_opts(args)
|
|
418
|
+
json_mode = args.delete("--json")
|
|
419
|
+
|
|
420
|
+
ITerm2.connect do |client|
|
|
421
|
+
session_id = resolve_session(client, opts)
|
|
422
|
+
info = client.session_info(session_id)
|
|
423
|
+
|
|
424
|
+
if json_mode
|
|
425
|
+
puts JSON.pretty_generate(info)
|
|
426
|
+
else
|
|
427
|
+
info.each do |k, v|
|
|
428
|
+
puts format("%-8s %s", "#{k}:", v || "(nil)")
|
|
429
|
+
end
|
|
430
|
+
end
|
|
431
|
+
end
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
def cmd_focus(args)
|
|
435
|
+
json_mode = args.delete("--json")
|
|
436
|
+
|
|
437
|
+
ITerm2.connect do |client|
|
|
438
|
+
result = client.focus
|
|
439
|
+
|
|
440
|
+
if json_mode
|
|
441
|
+
puts JSON.pretty_generate(result)
|
|
442
|
+
else
|
|
443
|
+
result.each do |k, v|
|
|
444
|
+
puts format("%-18s %s", "#{k}:", v.nil? ? "(none)" : v.to_s)
|
|
445
|
+
end
|
|
446
|
+
end
|
|
447
|
+
end
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
def cmd_prompt(args)
|
|
451
|
+
opts = parse_target_opts(args)
|
|
452
|
+
json_mode = args.delete("--json")
|
|
453
|
+
|
|
454
|
+
ITerm2.connect do |client|
|
|
455
|
+
session_id = resolve_session(client, opts)
|
|
456
|
+
result = client.get_prompt(session_id)
|
|
457
|
+
|
|
458
|
+
if json_mode
|
|
459
|
+
puts JSON.pretty_generate(result)
|
|
460
|
+
else
|
|
461
|
+
result.each do |k, v|
|
|
462
|
+
puts format("%-20s %s", "#{k}:", v.nil? ? "(nil)" : v.to_s)
|
|
463
|
+
end
|
|
464
|
+
end
|
|
465
|
+
end
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
def cmd_watch(args)
|
|
469
|
+
opts = parse_target_opts(args)
|
|
470
|
+
type = args.shift
|
|
471
|
+
|
|
472
|
+
ITerm2.connect do |client|
|
|
473
|
+
subscriptions = []
|
|
474
|
+
|
|
475
|
+
case type
|
|
476
|
+
when "focus", nil
|
|
477
|
+
subscriptions << client.on_focus_change { |n| puts JSON.dump(n) }
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
case type
|
|
481
|
+
when "sessions", nil
|
|
482
|
+
subscriptions << client.on_new_session { |n| puts JSON.dump(n) }
|
|
483
|
+
subscriptions << client.on_session_terminated { |n| puts JSON.dump(n) }
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
case type
|
|
487
|
+
when "prompt"
|
|
488
|
+
session_id = resolve_session(client, opts)
|
|
489
|
+
subscriptions << client.on_prompt_change(session_id) { |n| puts JSON.dump(n) }
|
|
490
|
+
when nil
|
|
491
|
+
# Skip prompt in watch-all (requires session_id)
|
|
492
|
+
end
|
|
493
|
+
|
|
494
|
+
case type
|
|
495
|
+
when "screen"
|
|
496
|
+
session_id = resolve_session(client, opts)
|
|
497
|
+
subscriptions << client.on_screen_update(session_id) { |n| puts JSON.dump(n) }
|
|
498
|
+
end
|
|
499
|
+
|
|
500
|
+
case type
|
|
501
|
+
when "layout", nil
|
|
502
|
+
subscriptions << client.on_layout_change { |n| puts JSON.dump(n) }
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
if subscriptions.empty?
|
|
506
|
+
$stderr.puts "No subscriptions created. Valid types: focus, sessions, prompt, screen, layout"
|
|
507
|
+
exit 1
|
|
508
|
+
end
|
|
509
|
+
|
|
510
|
+
$stdout.sync = true
|
|
511
|
+
$stderr.puts "Watching for #{type || 'all'} events... (Ctrl+C to stop)"
|
|
512
|
+
sleep
|
|
513
|
+
rescue Interrupt
|
|
514
|
+
$stderr.puts "\nStopping..."
|
|
515
|
+
end
|
|
516
|
+
end
|
|
517
|
+
|
|
518
|
+
def cmd_profile(args)
|
|
519
|
+
opts = parse_target_opts(args)
|
|
520
|
+
json_mode = args.delete("--json")
|
|
521
|
+
keys = args
|
|
522
|
+
|
|
523
|
+
ITerm2.connect do |client|
|
|
524
|
+
session_id = resolve_session(client, opts)
|
|
525
|
+
result = client.get_profile_property(session_id, *keys)
|
|
526
|
+
|
|
527
|
+
if json_mode
|
|
528
|
+
puts JSON.pretty_generate(result)
|
|
529
|
+
else
|
|
530
|
+
result.sort_by { |k, _| k }.each do |k, v|
|
|
531
|
+
puts format("%-40s %s", k, v.is_a?(String) ? v : JSON.dump(v))
|
|
532
|
+
end
|
|
533
|
+
end
|
|
534
|
+
end
|
|
535
|
+
end
|
|
536
|
+
|
|
537
|
+
def cmd_profiles(args)
|
|
538
|
+
json_mode = args.delete("--json")
|
|
539
|
+
properties = nil
|
|
540
|
+
if (idx = args.index("--properties"))
|
|
541
|
+
args.delete_at(idx)
|
|
542
|
+
properties = args.delete_at(idx)&.split(",")
|
|
543
|
+
end
|
|
544
|
+
|
|
545
|
+
ITerm2.connect do |client|
|
|
546
|
+
result = client.list_profiles(properties: properties)
|
|
547
|
+
|
|
548
|
+
if json_mode
|
|
549
|
+
puts JSON.pretty_generate(result)
|
|
550
|
+
else
|
|
551
|
+
result.each_with_index do |profile, i|
|
|
552
|
+
name = profile["Name"] || "(unnamed)"
|
|
553
|
+
guid = profile["Guid"] || "(no guid)"
|
|
554
|
+
puts "#{i + 1}. #{name} (#{guid})"
|
|
555
|
+
end
|
|
556
|
+
end
|
|
557
|
+
end
|
|
558
|
+
end
|
|
559
|
+
|
|
560
|
+
def cmd_inject(args)
|
|
561
|
+
opts = parse_target_opts(args)
|
|
562
|
+
text = args.join(" ")
|
|
563
|
+
|
|
564
|
+
if text.empty?
|
|
565
|
+
$stderr.puts "Usage: iterm2ctl inject TEXT [--session ID]"
|
|
566
|
+
exit 1
|
|
567
|
+
end
|
|
568
|
+
|
|
569
|
+
ITerm2.connect do |client|
|
|
570
|
+
session_id = resolve_session(client, opts)
|
|
571
|
+
client.inject(session_id, text)
|
|
572
|
+
puts "Injected to #{session_id}"
|
|
573
|
+
end
|
|
574
|
+
end
|
|
575
|
+
|
|
576
|
+
# Resolve scope for variable commands
|
|
577
|
+
def resolve_var_scope(client, opts)
|
|
578
|
+
if opts[:session]
|
|
579
|
+
{ session_id: opts[:session] }
|
|
580
|
+
elsif opts[:tab]
|
|
581
|
+
{ tab_id: opts[:tab] }
|
|
582
|
+
elsif opts[:window]
|
|
583
|
+
{ window_id: opts[:window] }
|
|
584
|
+
else
|
|
585
|
+
# Default to first session
|
|
586
|
+
sessions = client.topology
|
|
587
|
+
raise ITerm2::NotFoundError, "No sessions found" if sessions.empty?
|
|
588
|
+
{ session_id: sessions.first[:session_id] }
|
|
589
|
+
end
|
|
590
|
+
end
|
|
591
|
+
|
|
592
|
+
# Parse --session, --tab, --window, --profile from args (mutating)
|
|
593
|
+
def parse_target_opts(args)
|
|
594
|
+
opts = {}
|
|
595
|
+
%w[--session --tab --window --profile].each do |flag|
|
|
596
|
+
if (idx = args.index(flag))
|
|
597
|
+
args.delete_at(idx)
|
|
598
|
+
opts[flag.delete_prefix("--").to_sym] = args.delete_at(idx)
|
|
599
|
+
end
|
|
600
|
+
end
|
|
601
|
+
opts
|
|
602
|
+
end
|
|
603
|
+
|
|
604
|
+
# Resolve a session ID from opts, falling back to first session
|
|
605
|
+
def resolve_session(client, opts)
|
|
606
|
+
return opts[:session] if opts[:session]
|
|
607
|
+
|
|
608
|
+
sessions = client.topology
|
|
609
|
+
if opts[:tab]
|
|
610
|
+
match = sessions.find { |s| s[:tab_id] == opts[:tab] }
|
|
611
|
+
return match[:session_id] if match
|
|
612
|
+
raise ITerm2::NotFoundError, "Tab #{opts[:tab]} not found"
|
|
613
|
+
end
|
|
614
|
+
|
|
615
|
+
raise ITerm2::NotFoundError, "No sessions found" if sessions.empty?
|
|
616
|
+
sessions.first[:session_id]
|
|
617
|
+
end
|
|
618
|
+
end
|
|
619
|
+
|
|
620
|
+
ITerm2CTL.run
|