claude-team-mcp 0.6.1__py3-none-any.whl → 0.7.0__py3-none-any.whl

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.
@@ -0,0 +1,251 @@
1
+ """
2
+ iTerm2 terminal backend adapter.
3
+
4
+ Wraps iTerm2 session objects in a backend-agnostic interface.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import TYPE_CHECKING, Any, Optional
10
+
11
+ from .base import TerminalBackend, TerminalSession
12
+ from .. import iterm_utils
13
+
14
+ if TYPE_CHECKING:
15
+ from iterm2.app import App as ItermApp
16
+ from iterm2.connection import Connection as ItermConnection
17
+ from iterm2.profile import LocalWriteOnlyProfile as ItermLocalWriteOnlyProfile
18
+ from iterm2.session import Session as ItermSession
19
+ from iterm2.tab import Tab as ItermTab
20
+ from iterm2.window import Window as ItermWindow
21
+
22
+ from ..cli_backends import AgentCLI
23
+
24
+ # Re-export iTerm-specific layout limit via the backend layer.
25
+ MAX_PANES_PER_TAB = iterm_utils.MAX_PANES_PER_TAB
26
+
27
+
28
+ class ItermBackend(TerminalBackend):
29
+ """Terminal backend adapter for iTerm2."""
30
+
31
+ backend_id = "iterm"
32
+
33
+ def __init__(self, connection: "ItermConnection", app: "ItermApp") -> None:
34
+ """Initialize the backend with an active iTerm2 connection and app."""
35
+ self._connection = connection
36
+ self._app = app
37
+
38
+ @property
39
+ def connection(self) -> "ItermConnection":
40
+ """Return the active iTerm2 connection."""
41
+ return self._connection
42
+
43
+ @property
44
+ def app(self) -> "ItermApp":
45
+ """Return the active iTerm2 app handle."""
46
+ return self._app
47
+
48
+ def wrap_session(self, handle: "ItermSession") -> TerminalSession:
49
+ """Wrap an iTerm2 session handle in a TerminalSession."""
50
+ return TerminalSession(
51
+ backend_id=self.backend_id,
52
+ native_id=handle.session_id,
53
+ handle=handle,
54
+ )
55
+
56
+ def unwrap_session(self, session: TerminalSession) -> "ItermSession":
57
+ """Extract the iTerm2 session handle from a TerminalSession."""
58
+ return session.handle
59
+
60
+ def handle_from_session(self, session: "ItermSession") -> TerminalSession:
61
+ """Wrap a native iTerm2 session in a TerminalSession (alias for wrap_session)."""
62
+ return self.wrap_session(session)
63
+
64
+ async def find_handle_by_native_id(self, native_id: str) -> Optional[TerminalSession]:
65
+ """
66
+ Find a TerminalSession for a native iTerm2 session ID.
67
+
68
+ Returns None if the session cannot be found.
69
+ """
70
+ for window in self._app.terminal_windows:
71
+ for tab in window.tabs:
72
+ for session in tab.sessions:
73
+ if session.session_id == native_id:
74
+ return self.wrap_session(session)
75
+ return None
76
+
77
+ def list_handles(self) -> list[TerminalSession]:
78
+ """Return TerminalSessions for all iTerm2 sessions in all windows."""
79
+ handles: list[TerminalSession] = []
80
+ for window in self._app.terminal_windows:
81
+ for tab in window.tabs:
82
+ for session in tab.sessions:
83
+ handles.append(self.wrap_session(session))
84
+ return handles
85
+
86
+ async def create_session(
87
+ self,
88
+ name: str | None = None,
89
+ *,
90
+ project_path: str | None = None,
91
+ issue_id: str | None = None,
92
+ coordinator_annotation: str | None = None,
93
+ profile: str | None = None,
94
+ profile_customizations: "ItermLocalWriteOnlyProfile" | None = None,
95
+ ) -> TerminalSession:
96
+ """Create a new iTerm2 window/session and return its initial pane."""
97
+ window = await iterm_utils.create_window(
98
+ self._connection,
99
+ profile=profile,
100
+ profile_customizations=profile_customizations,
101
+ )
102
+ tab = window.current_tab
103
+ if tab is None or tab.current_session is None:
104
+ raise RuntimeError("Failed to get initial iTerm2 session from window")
105
+ if name:
106
+ try:
107
+ await tab.async_set_title(name)
108
+ except Exception:
109
+ pass
110
+ return self.wrap_session(tab.current_session)
111
+
112
+ async def send_text(self, session: TerminalSession, text: str) -> None:
113
+ """Send raw text to an iTerm2 session."""
114
+ await iterm_utils.send_text(self.unwrap_session(session), text)
115
+
116
+ async def send_key(self, session: TerminalSession, key: str) -> None:
117
+ """Send a special key to an iTerm2 session."""
118
+ await iterm_utils.send_key(self.unwrap_session(session), key)
119
+
120
+ async def send_prompt(
121
+ self, session: TerminalSession, text: str, submit: bool = True
122
+ ) -> None:
123
+ """Send a prompt to a terminal session, optionally submitting it."""
124
+ await iterm_utils.send_prompt(self.unwrap_session(session), text, submit=submit)
125
+
126
+ async def send_prompt_for_agent(
127
+ self,
128
+ session: TerminalSession,
129
+ text: str,
130
+ agent_type: str = "claude",
131
+ submit: bool = True,
132
+ ) -> None:
133
+ """Send a prompt with agent-specific handling (Claude vs Codex)."""
134
+ await iterm_utils.send_prompt_for_agent(
135
+ self.unwrap_session(session),
136
+ text,
137
+ agent_type=agent_type,
138
+ submit=submit,
139
+ )
140
+
141
+ async def read_screen_text(self, session: TerminalSession) -> str:
142
+ """Read visible screen content from an iTerm2 session."""
143
+ return await iterm_utils.read_screen_text(self.unwrap_session(session))
144
+
145
+ async def split_pane(
146
+ self,
147
+ session: TerminalSession,
148
+ *,
149
+ vertical: bool = True,
150
+ before: bool = False,
151
+ profile: str | None = None,
152
+ profile_customizations: "ItermLocalWriteOnlyProfile" | None = None,
153
+ ) -> TerminalSession:
154
+ """Split an iTerm2 session pane and return the new pane."""
155
+ new_session = await iterm_utils.split_pane(
156
+ self.unwrap_session(session),
157
+ vertical=vertical,
158
+ before=before,
159
+ profile=profile,
160
+ profile_customizations=profile_customizations,
161
+ )
162
+ return self.wrap_session(new_session)
163
+
164
+ async def close_session(self, session: TerminalSession, force: bool = False) -> None:
165
+ """Close an iTerm2 session pane."""
166
+ await iterm_utils.close_pane(self.unwrap_session(session), force=force)
167
+
168
+ async def create_multi_pane_layout(
169
+ self,
170
+ layout: str,
171
+ *,
172
+ profile: str | None = None,
173
+ profile_customizations: dict[str, Any] | None = None,
174
+ ) -> dict[str, TerminalSession]:
175
+ """Create an iTerm2 multi-pane layout and wrap panes as TerminalSessions."""
176
+ panes = await iterm_utils.create_multi_pane_layout(
177
+ self._connection,
178
+ layout,
179
+ profile=profile,
180
+ profile_customizations=profile_customizations,
181
+ )
182
+ return {name: self.wrap_session(session) for name, session in panes.items()}
183
+
184
+ async def list_sessions(self) -> list[TerminalSession]:
185
+ """List all iTerm2 sessions across all windows and tabs."""
186
+ return self.list_handles()
187
+
188
+ async def start_agent_in_session(
189
+ self,
190
+ handle: TerminalSession,
191
+ cli: "AgentCLI",
192
+ project_path: str,
193
+ dangerously_skip_permissions: bool = False,
194
+ env: Optional[dict[str, str]] = None,
195
+ shell_ready_timeout: float = 10.0,
196
+ agent_ready_timeout: float = 30.0,
197
+ stop_hook_marker_id: Optional[str] = None,
198
+ output_capture_path: Optional[str] = None,
199
+ ) -> None:
200
+ """Start a CLI agent in an existing terminal session."""
201
+ await iterm_utils.start_agent_in_session(
202
+ session=self.unwrap_session(handle),
203
+ cli=cli,
204
+ project_path=project_path,
205
+ dangerously_skip_permissions=dangerously_skip_permissions,
206
+ env=env,
207
+ shell_ready_timeout=shell_ready_timeout,
208
+ agent_ready_timeout=agent_ready_timeout,
209
+ stop_hook_marker_id=stop_hook_marker_id,
210
+ output_capture_path=output_capture_path,
211
+ )
212
+
213
+ async def find_available_window(
214
+ self,
215
+ max_panes: int = MAX_PANES_PER_TAB,
216
+ managed_session_ids: Optional[set[str]] = None,
217
+ ) -> Optional[tuple["ItermWindow", "ItermTab", TerminalSession]]:
218
+ """Find a window/tab with space for more panes."""
219
+ result = await iterm_utils.find_available_window(
220
+ self._app,
221
+ max_panes=max_panes,
222
+ managed_session_ids=managed_session_ids,
223
+ )
224
+ if not result:
225
+ return None
226
+ window, tab, session = result
227
+ return window, tab, self.wrap_session(session)
228
+
229
+ async def get_window_for_handle(
230
+ self,
231
+ handle: TerminalSession,
232
+ ) -> Optional["ItermWindow"]:
233
+ """Return the window containing the given terminal session."""
234
+ return await iterm_utils.get_window_for_session(
235
+ self._app, self.unwrap_session(handle)
236
+ )
237
+
238
+ async def activate_app(self) -> None:
239
+ """Bring the terminal application to the foreground."""
240
+ await self._app.async_activate()
241
+
242
+ async def activate_window_for_handle(self, handle: TerminalSession) -> None:
243
+ """Activate the window containing the given terminal session."""
244
+ native_session = self.unwrap_session(handle)
245
+ tab = native_session.tab
246
+ if tab is None:
247
+ return
248
+ window = tab.window
249
+ if window is None:
250
+ return
251
+ await window.async_activate()