Class: AgentsControl::Terminals::ITerm2
- Defined in:
- lib/agents_control/terminals/iterm2.rb
Overview
iTerm2 via AppleScript.
Scripts are passed to osascript over stdin, and parameters via
on run argv. Values are never interpolated into the script text:
identifiers, and especially arbitrary user commands, would
otherwise turn into an AppleScript injection.
A full scan of sessions via AppleScript is expensive, so the list is built on demand rather than polled on a timer. Agent state comes from hooks, not from here.
Constant Summary collapse
- LIST =
The session identifier matches the ITERM_SESSION_ID variable that iTerm2 puts in a tab's environment. A hook uses it to tie an agent session to a specific tab.
<<~APPLESCRIPT on run argv set fs to (character id 31) set rs to (character id 30) set out to "" tell application "iTerm2" repeat with w in windows repeat with t in tabs of w repeat with s in sessions of t set out to out & (id of s) & fs & (tty of s) & fs ¬ & ((is processing of s) as text) & fs ¬ & ((is at shell prompt of s) as text) & fs ¬ & (variable s named "path") & fs ¬ & (name of s) & rs end repeat end repeat end repeat end tell return out end run APPLESCRIPT
- FIND_AND =
Shared wrapper: find a session by id and do something with it. %s is substituted right here in the code, never from user input.
<<~APPLESCRIPT on run argv set target_id to item 1 of argv tell application "iTerm2" repeat with w in windows repeat with t in tabs of w repeat with s in sessions of t if (id of s) is target_id then %s return "ok" end if end repeat end repeat end repeat end tell return "missing" end run APPLESCRIPT
- CREATE_TAB =
<<~APPLESCRIPT on run argv set target_dir to item 1 of argv set target_cmd to item 2 of argv tell application "iTerm2" if (count of windows) is 0 then set w to (create window with default profile) else set w to current window tell w to create tab with default profile end if set s to current session of current tab of w if target_dir is not "" then tell s to write text ("cd " & quoted form of target_dir) end if if target_cmd is not "" then tell s to write text target_cmd end if return (id of s) end tell end run APPLESCRIPT
- FIELDS =
%i[id tty processing at_shell_prompt cwd title].freeze
Instance Method Summary collapse
-
#available? ⇒ Boolean
Checked against the process tree, not via AppleScript: talking to the app over AppleScript launches it if it's closed, and an availability check has no business opening a terminal on the user.
- #capture(id, lines: 200) ⇒ Object
- #close(id) ⇒ Object
- #create_tab(cwd: nil, command: nil) ⇒ Object
- #focus(id) ⇒ Object
- #name ⇒ Object
- #send_text(id, text, newline: true) ⇒ Object
- #sessions ⇒ Object
Methods inherited from Base
Constructor Details
This class inherits a constructor from AgentsControl::Terminals::Base
Instance Method Details
#available? ⇒ Boolean
Checked against the process tree, not via AppleScript: talking to the app over AppleScript launches it if it's closed, and an availability check has no business opening a terminal on the user.
pgrep won't do here: on macOS -x matches against the full path,
and in a restricted environment it misses processes that ps sees fine.
95 |
# File 'lib/agents_control/terminals/iterm2.rb', line 95 def available? = probe.running?("iTerm2") |
#capture(id, lines: 200) ⇒ Object
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
# File 'lib/agents_control/terminals/iterm2.rb', line 125 def capture(id, lines: 200) script = format(FIND_AND, "return contents of s") result = osascript(script, id) return "" unless result.success? # AppleScript only hands back the visible area — there's no # scrollback here; for history, go to tmux or the agent's transcript. # # `contents of s` is the whole visible screen, including blank # filler lines below the text when it doesn't reach the bottom of # the pane. rstrip removes that tail before the .last(lines) cut — # otherwise, on a pane taller than its content, the cut would # grab only blank lines. text = result.stdout.rstrip text.empty? ? "" : text.lines.last(lines).join.rstrip end |
#close(id) ⇒ Object
146 147 148 |
# File 'lib/agents_control/terminals/iterm2.rb', line 146 def close(id) act(format(FIND_AND, "tell s to close"), id) end |
#create_tab(cwd: nil, command: nil) ⇒ Object
114 115 116 117 |
# File 'lib/agents_control/terminals/iterm2.rb', line 114 def create_tab(cwd: nil, command: nil) result = osascript(CREATE_TAB, cwd.to_s, command.to_s) result.success? ? result.stdout.strip : nil end |
#focus(id) ⇒ Object
142 143 144 |
# File 'lib/agents_control/terminals/iterm2.rb', line 142 def focus(id) act(format(FIND_AND, "tell t to select\n tell w to select"), id) end |
#name ⇒ Object
87 |
# File 'lib/agents_control/terminals/iterm2.rb', line 87 def name = :iterm2 |
#send_text(id, text, newline: true) ⇒ Object
119 120 121 122 123 |
# File 'lib/agents_control/terminals/iterm2.rb', line 119 def send_text(id, text, newline: true) # The text goes in as argv's second argument, not into the script body. script = format(FIND_AND, %(tell s to write text (item 2 of argv) newline #{newline})) act(script, id, text) end |
#sessions ⇒ Object
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
# File 'lib/agents_control/terminals/iterm2.rb', line 97 def sessions result = osascript(LIST) return [] unless result.success? parse_records(result.stdout, FIELDS).map do |record| Session.new( id: record[:id], backend: name, tty: record[:tty], title: record[:title].to_s.strip, cwd: record[:cwd].to_s.strip, processing: record[:processing] == "true", at_shell_prompt: record[:at_shell_prompt] == "true" ) end end |