iterm2_ruby 0.3.1 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7ea8b3aab793d0294a0cb9f726e8cc01123b0da6a22b02034a7f64c3e4744959
4
- data.tar.gz: 3773d06e8c6969221bb52049ff3a11131a786f8280d36e3f5a889174c3a56d17
3
+ metadata.gz: '005262752689643c56c4a97896c3b923f422c4ff49dd5c310664dcefbe7f4c2a'
4
+ data.tar.gz: 332e89ff558b000f3f69d35102587a051b48db11818bafeba03c34621feab9f3
5
5
  SHA512:
6
- metadata.gz: ecff09efcb6229d0079eff7f8cc324fb0da17287c762f39fb1e99ada8c2476283373ed6ef48c097c3c36f6fe87346485c3b311133f0ca45be22e5f60940da729
7
- data.tar.gz: 143e8af32d29624720bb35719eb399e620364c0e632bc5613ea710190acd0c8a5b8c3112a150db5794ff8da1968a6c39d38d1288626366ee9231ddf9810c63f5
6
+ metadata.gz: 2f9294eb24b0e60b62954296a1ee675a9107b9caea7e6cf75fb1c36429740c0219cae2c0dfa1710d303007f024b713ab5fb5b7f0a8ada62915b6a5fcac4668a8
7
+ data.tar.gz: fdb38625258e956ae0fef7be027a099739fda95c256a7b6bd2be5e7034bc13e16c885fbed94b26a230a5efded8334a6e4e907be58ed978d0bf0c2f7bfd4a804b
data/CHANGELOG.md CHANGED
@@ -3,6 +3,17 @@
3
3
  All notable changes to this project are documented here. Format follows
4
4
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
5
 
6
+ ## [0.4.0] - 2026-10-23
7
+
8
+ ### Added
9
+ - `client.split_browser_pane(session_id, url, vertical: true)` -- split a pane that shows a URL as a web view.
10
+ Uses the `"Custom Command" => "Browser"` profile override, so no "Web Browser" profile is needed.
11
+ - `ITerm2::Browser` -- connection-free probes (`plugin_installed?`, `profiles_enabled?`, `available?`) and
12
+ `pane_properties(url)`. The plugin check looks in the usual install folders first and asks Spotlight only
13
+ as a fallback, so a plugin in those folders never depends on a Spotlight lookup. Probe commands run without a
14
+ shell and are terminated on timeout.
15
+ - `ITerm2.current_session_id` -- the calling pane's session ID from `ITERM_SESSION_ID`, `nil` outside iTerm2.
16
+
6
17
  ## [0.3.1] - 2026-09-04
7
18
 
8
19
  ### Fixed
data/README.md CHANGED
@@ -35,12 +35,14 @@ This feeds a real-time "mission control" view over a whole fleet of sessions.
35
35
  **Split a pane and pop a live browser preview next to your code:**
36
36
 
37
37
  ```ruby
38
- ITerm2.connect do |c|
39
- c.split_pane(guid, vertical: true, profile_name: "Web Browser",
40
- profile_customizations: { "Initial URL" => url })
38
+ here = ITerm2.current_session_id # nil outside iTerm2
39
+ if here && ITerm2::Browser.available? # Browser Plugin installed, browser-style profiles on
40
+ ITerm2.connect { |c| c.split_browser_pane(here, url, vertical: true) }
41
41
  end
42
42
  ```
43
43
 
44
+ No "Web Browser" profile needs to exist; the pane's profile is overridden to show the URL.
45
+
44
46
  **Spawn a background task in its own window, no permission dialogs:**
45
47
 
46
48
  ```bash
@@ -214,6 +216,7 @@ Most commands accept `--session ID`, `--tab ID`, or `--window ID` to target a sp
214
216
  | `raise_by_cwd(pattern)` | Raise first session matching cwd |
215
217
  | `create_tab(window_id:, profile_name:)` | Create a new tab |
216
218
  | `split_pane(session_id, vertical:)` | Split a pane |
219
+ | `split_browser_pane(session_id, url, vertical:)` | Split a pane that shows a URL as a web view (check `ITerm2::Browser.available?`) |
217
220
  | `close_session(session_id)` | Close a session |
218
221
  | `close_tab(tab_id)` | Close a tab |
219
222
  | `reorder_tabs(window_id => [tab_ids])` | Move/reorder tabs across windows |
data/docs/api.md CHANGED
@@ -28,6 +28,12 @@ Creates a persistent client with an open connection. Equivalent to `ITerm2.conne
28
28
 
29
29
  Closes the connection. Unsubscribes all notification listeners and stops the dispatch thread if running.
30
30
 
31
+ ### `ITerm2.current_session_id(env = ENV)` -> String | nil
32
+
33
+ The session ID of the pane the calling process runs in: the GUID after the colon of `ITERM_SESSION_ID`
34
+ (`"w0t0p0:<GUID>"`). `nil` outside iTerm2 or when the value is not that shape. This is the ID every
35
+ session-targeted call takes.
36
+
31
37
  ---
32
38
 
33
39
  ## Topology
@@ -188,6 +194,33 @@ Splits a pane. Returns the new session ID.
188
194
 
189
195
  **Raises:** `RPCError` on failure.
190
196
 
197
+ ### `client.split_browser_pane(session_id, url, vertical: true)` -> String
198
+
199
+ Splits a pane that shows `url` as a web view (a browser pane), beside `session_id`. Returns the new session ID.
200
+ It is `split_pane` with the profile overrides `"Custom Command" => "Browser"` and `"Initial URL" => url`, so no
201
+ "Web Browser" profile has to be installed. iTerm2 still needs its separately installed Browser Plugin and the
202
+ "Enable browser-style profiles" Advanced setting (on by default); check with `ITerm2::Browser.available?`.
203
+
204
+ - **session_id** (String) -- session to split
205
+ - **url** (String) -- page the pane loads
206
+ - **vertical** (Boolean) -- `true` for a vertical split, `false` for horizontal
207
+
208
+ **Raises:** `RPCError` on failure.
209
+
210
+ Pass a real session ID: with `nil`, `split_pane` sends no session and iTerm2 chooses which pane to split.
211
+ When calling with `ITerm2.current_session_id`, guard against `nil` (outside iTerm2).
212
+
213
+ ### `ITerm2::Browser`
214
+
215
+ Connection-free checks for browser panes. Off macOS, `plugin_installed?`, `profiles_enabled?` and `available?` return `false`.
216
+ When a check cannot tell (a failed or timed-out probe), the two differ on purpose: `plugin_installed?` returns `false`
217
+ (a pane that cannot work should not be attempted) and `profiles_enabled?` returns `true` (the setting ships enabled).
218
+
219
+ - `ITerm2::Browser.plugin_installed?` -- the Browser Plugin is in `/Applications` or `~/Applications`, or Spotlight finds it by bundle identifier
220
+ - `ITerm2::Browser.profiles_enabled?` -- the "Enable browser-style profiles" setting is not turned off (unset counts as enabled)
221
+ - `ITerm2::Browser.available?` -- both of the above
222
+ - `ITerm2::Browser.pane_properties(url)` -- the profile overrides `split_browser_pane` applies; returns a Hash on every platform
223
+
191
224
  ### `client.close_session(session_id, force: false)` -> true | false
192
225
 
193
226
  Closes a session. With `force: true`, skips the confirmation prompt.
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rbconfig"
4
+ require "timeout"
5
+
6
+ module ITerm2
7
+ # iTerm2 draws a pane as a web view instead of a shell when its profile's
8
+ # "Custom Command" is the literal string "Browser" -- exactly how iTerm2's own
9
+ # built-in "Web Browser" profile is defined. Overriding that property (plus
10
+ # "Initial URL") on a split produces a browser pane from ANY base profile, so
11
+ # no "Web Browser" profile needs to be installed.
12
+ #
13
+ # iTerm2's own browser feature still needs two things this override cannot
14
+ # bypass: the separately downloaded Browser Plugin, and the "Enable
15
+ # browser-style profiles" Advanced setting (on by default). The probes below
16
+ # check both without opening an API connection.
17
+ module Browser
18
+ PROFILE_PROPERTIES = { "Custom Command" => "Browser" }.freeze
19
+
20
+ # The plugin's own CFBundleIdentifier (iterm2.com/browser-plugin.html).
21
+ PLUGIN_BUNDLE_ID = "com.googlecode.iterm2.iTermBrowserPlugin"
22
+
23
+ # Persisted key of Settings > Advanced > "Enable browser-style profiles".
24
+ # Unset on a machine that never touched it; iTerm2 treats that as enabled.
25
+ PROFILES_PREF_KEY = "browserProfiles"
26
+
27
+ # Where the plugin is normally unzipped. Checked first: a directory test is
28
+ # instant and deterministic, where a cold Spotlight (mdfind) lookup can take
29
+ # seconds and would read as "not installed" if it hit the timeout.
30
+ PLUGIN_PATHS = [
31
+ "/Applications/iTermBrowserPlugin.app",
32
+ File.join(Dir.home, "Applications", "iTermBrowserPlugin.app")
33
+ ].freeze
34
+
35
+ PROBE_TIMEOUT = 5
36
+ SPOTLIGHT_TIMEOUT = 15
37
+
38
+ # The profile properties that turn a split into a browser pane showing `url`.
39
+ def self.pane_properties(url)
40
+ PROFILE_PROPERTIES.merge("Initial URL" => url)
41
+ end
42
+
43
+ # True when the Browser Plugin is installed: in a usual location, or found
44
+ # by bundle identifier wherever it was unzipped. False on any doubt: not
45
+ # macOS, mdfind missing or erroring, timeout.
46
+ def self.plugin_installed?
47
+ return false unless darwin?
48
+ return true if PLUGIN_PATHS.any? { |path| File.directory?(path) }
49
+
50
+ found = capture("mdfind", "kMDItemCFBundleIdentifier == '#{PLUGIN_BUNDLE_ID}'", timeout: SPOTLIGHT_TIMEOUT)
51
+ !found.strip.empty?
52
+ rescue StandardError
53
+ false
54
+ end
55
+
56
+ # True unless the user explicitly turned off browser-style profiles. An
57
+ # unset preference reads as enabled. Not macOS: false. Any probe failure:
58
+ # true (the setting ships enabled, so doubt should not block).
59
+ def self.profiles_enabled?
60
+ return false unless darwin?
61
+
62
+ capture("defaults", "read", "com.googlecode.iterm2", PROFILES_PREF_KEY, timeout: PROBE_TIMEOUT).strip != "0"
63
+ rescue StandardError
64
+ true
65
+ end
66
+
67
+ # Whether a browser pane can be expected to work on this machine.
68
+ def self.available?
69
+ plugin_installed? && profiles_enabled?
70
+ end
71
+
72
+ def self.darwin?
73
+ RbConfig::CONFIG["host_os"].match?(/darwin/)
74
+ end
75
+
76
+ # Runs `command` without a shell and returns its stdout. On timeout the
77
+ # child is terminated, not left running, and Timeout::Error is raised.
78
+ def self.capture(*command, timeout:)
79
+ IO.popen(command, err: File::NULL) do |io|
80
+ Timeout.timeout(timeout) { io.read }
81
+ rescue Timeout::Error
82
+ terminate(io.pid)
83
+ raise
84
+ end
85
+ end
86
+
87
+ def self.terminate(pid)
88
+ Process.kill("TERM", pid)
89
+ rescue Errno::ESRCH
90
+ nil # already gone
91
+ end
92
+
93
+ private_class_method :darwin?, :capture, :terminate
94
+ end
95
+ end
data/lib/iterm2/client.rb CHANGED
@@ -159,6 +159,14 @@ module ITerm2
159
159
  resp.session_id.first
160
160
  end
161
161
 
162
+ # --- Browser panes ---
163
+
164
+ # Splits `session_id` with a pane that shows `url` as a web view. Returns the
165
+ # new session ID. See ITerm2::Browser for what iTerm2 needs for this to work.
166
+ def split_browser_pane(session_id, url, vertical: true)
167
+ split_pane(session_id, vertical: vertical, profile_customizations: Browser.pane_properties(url))
168
+ end
169
+
162
170
  # --- ReorderTabs ---
163
171
 
164
172
  def reorder_tabs(assignments)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ITerm2
4
- VERSION = "0.3.1"
4
+ VERSION = "0.4.0"
5
5
  end
data/lib/iterm2.rb CHANGED
@@ -3,6 +3,7 @@
3
3
  require_relative "iterm2/version"
4
4
  require_relative "iterm2/proto/api_pb"
5
5
  require_relative "iterm2/connection"
6
+ require_relative "iterm2/browser"
6
7
  require_relative "iterm2/client"
7
8
  require_relative "iterm2/window"
8
9
  require_relative "iterm2/tab"
@@ -19,6 +20,18 @@ module ITerm2
19
20
  class NotFoundError < RPCError; end
20
21
  class SubscriptionError < Error; end
21
22
 
23
+ # The session ID of the pane the calling process runs in, or nil outside
24
+ # iTerm2. ITERM_SESSION_ID looks like "w0t0p0:<GUID>"; the GUID after the
25
+ # colon is the ID every API call takes. A value with no colon is not that
26
+ # shape, so it returns nil rather than a meaningless string.
27
+ def self.current_session_id(env = ENV)
28
+ raw = env["ITERM_SESSION_ID"]
29
+ return nil unless raw&.include?(":")
30
+
31
+ id = raw.split(":", 2).last
32
+ id.empty? ? nil : id
33
+ end
34
+
22
35
  # Convenience: open connection, yield client, close
23
36
  def self.connect(app_name: "iterm2_ruby")
24
37
  client = Client.new(app_name: app_name)
data/llms.txt CHANGED
@@ -64,11 +64,22 @@ States: `:editing`, `:running`, `:at_prompt`, `:unavailable`.
64
64
  ## Other Methods
65
65
 
66
66
  `topology_enriched`, `list_sessions`, `activate_tab`, `activate_window`,
67
- `raise_by_cwd`, `create_tab`, `split_pane`, `close_session`, `close_tab`,
67
+ `raise_by_cwd`, `create_tab`, `split_pane`, `split_browser_pane`, `close_session`, `close_tab`,
68
68
  `set_profile_property`, `get_profile_property`, `list_profiles`, `get_property`,
69
69
  `set_property`, `window_frame`, `set_window_frame`, `set_session_grid_size`,
70
70
  `get_variables`, `set_variables`, `inject`, `reorder_tabs`, `topology_for_aggregator`.
71
71
 
72
+ ## Browser panes (split a web view beside a session)
73
+
74
+ ```ruby
75
+ here = ITerm2.current_session_id # the calling pane's id from ENV["ITERM_SESSION_ID"]; nil outside iTerm2
76
+ if here && ITerm2::Browser.available? # plugin installed AND browser-style profiles enabled (macOS only; no API connection)
77
+ ITerm2.connect { |c| c.split_browser_pane(here, "http://localhost:4579/ambient", vertical: true) } # => new session id
78
+ end
79
+ ```
80
+
81
+ No "Web Browser" profile is needed: the method overrides the new pane's profile ("Custom Command" => "Browser", "Initial URL" => url). iTerm2's separately installed Browser Plugin must be present; `ITerm2::Browser.available?` checks that. Always pass a real session id (with nil, iTerm2 picks which pane to split). Callers keep their own platform guard and fallback (e.g. system browser off iTerm2). Never use the Python `iterm2` package.
82
+
72
83
  ## Notifications
73
84
 
74
85
  ```ruby
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: iterm2_ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Forrest Chang
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-09-04 00:00:00.000000000 Z
11
+ date: 2026-10-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: google-protobuf
@@ -43,6 +43,7 @@ files:
43
43
  - docs/cli.md
44
44
  - iterm2_ruby.gemspec
45
45
  - lib/iterm2.rb
46
+ - lib/iterm2/browser.rb
46
47
  - lib/iterm2/client.rb
47
48
  - lib/iterm2/connection.rb
48
49
  - lib/iterm2/proto/api_pb.rb