indent 0.1.21__py3-none-any.whl → 0.1.23__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.

Potentially problematic release.


This version of indent might be problematic. Click here for more details.

@@ -0,0 +1,73 @@
1
+ import time
2
+
3
+ import psutil
4
+
5
+ from exponent.core.remote_execution.types import PortInfo
6
+
7
+
8
+ def get_port_usage() -> list[PortInfo] | None:
9
+ """
10
+ Get information about all listening ports on the system.
11
+
12
+ Returns:
13
+ List of PortInfo objects containing process name, port, protocol, pid, and uptime.
14
+ Returns None if there's a permission error.
15
+ Returns empty list if no listening ports are found.
16
+ """
17
+ try:
18
+ connections = psutil.net_connections(kind="tcp")
19
+ except (psutil.AccessDenied, PermissionError):
20
+ # If we don't have permission to see connections, return None
21
+ return None
22
+ except Exception:
23
+ # For any other unexpected errors, return None
24
+ return None
25
+
26
+ port_info_list: list[PortInfo] = []
27
+ current_time = time.time()
28
+
29
+ for conn in connections:
30
+ # Only include TCP ports in LISTEN state
31
+ if conn.status != "LISTEN":
32
+ continue
33
+
34
+ # Skip if no local address (shouldn't happen for LISTEN, but be safe)
35
+ if not conn.laddr:
36
+ continue
37
+
38
+ port = conn.laddr.port
39
+ pid = conn.pid
40
+
41
+ # Try to get process information
42
+ process_name = "unknown"
43
+ uptime_seconds = None
44
+
45
+ if pid:
46
+ try:
47
+ process = psutil.Process(pid)
48
+ process_name = process.name()
49
+
50
+ # Calculate uptime
51
+ create_time = process.create_time()
52
+ uptime_seconds = current_time - create_time
53
+ except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
54
+ # Process disappeared or we don't have permission
55
+ pass
56
+ except Exception:
57
+ # Any other unexpected error, just skip process info
58
+ pass
59
+
60
+ port_info = PortInfo(
61
+ process_name=process_name,
62
+ port=port,
63
+ protocol="TCP",
64
+ pid=pid,
65
+ uptime_seconds=uptime_seconds,
66
+ )
67
+ port_info_list.append(port_info)
68
+
69
+ # Limit to 50 ports to avoid bloating the heartbeat payload
70
+ if len(port_info_list) >= 50:
71
+ break
72
+
73
+ return port_info_list
@@ -4,6 +4,7 @@ import platform
4
4
 
5
5
  from exponent.core.remote_execution.git import get_git_info
6
6
  from exponent.core.remote_execution.languages import python_execution
7
+ from exponent.core.remote_execution.port_utils import get_port_usage
7
8
  from exponent.core.remote_execution.types import (
8
9
  SystemInfo,
9
10
  )
@@ -17,6 +18,7 @@ async def get_system_info(working_directory: str) -> SystemInfo:
17
18
  shell=_get_user_shell(),
18
19
  git=await get_git_info(working_directory),
19
20
  python_env=python_execution.get_python_env_info(),
21
+ port_usage=get_port_usage(),
20
22
  )
21
23
 
22
24
 
@@ -0,0 +1,429 @@
1
+ import asyncio
2
+ import fcntl
3
+ import os
4
+ import pty
5
+ import signal
6
+ import struct
7
+ import sys
8
+ import termios
9
+ import time
10
+ import traceback
11
+ from collections.abc import Callable
12
+
13
+ import structlog
14
+
15
+ from exponent.core.remote_execution.terminal_types import (
16
+ TerminalMessage,
17
+ TerminalOutput,
18
+ TerminalResetSessions,
19
+ )
20
+
21
+ logger = structlog.get_logger(__name__)
22
+
23
+
24
+ class TerminalSession:
25
+ """
26
+ Manages a PTY session for terminal emulation.
27
+ Runs on the CLI machine and streams output back to server.
28
+ """
29
+
30
+ def __init__(
31
+ self,
32
+ session_id: str,
33
+ output_callback: Callable[[str], None],
34
+ cols: int = 80,
35
+ rows: int = 24,
36
+ ):
37
+ self.session_id = session_id
38
+ self.output_callback = output_callback # Called with terminal output
39
+ self.cols = cols
40
+ self.rows = rows
41
+ self.master_fd: int | None = None
42
+ self.pid: int | None = None
43
+ self._running = False
44
+ self._read_task: asyncio.Task[None] | None = None
45
+
46
+ async def start(
47
+ self,
48
+ command: list[str] | None = None,
49
+ env: dict[str, str] | None = None,
50
+ ) -> None:
51
+ """Start the terminal session with PTY"""
52
+ if self._running:
53
+ raise RuntimeError(f"Terminal session {self.session_id} already running")
54
+
55
+ # Default to bash if no command specified
56
+ if command is None:
57
+ command = ["/bin/bash"]
58
+
59
+ # Spawn process with PTY
60
+ try:
61
+ self.pid, self.master_fd = pty.fork()
62
+ except OSError as e:
63
+ logger.error(
64
+ "Failed to fork PTY",
65
+ session_id=self.session_id,
66
+ error=str(e),
67
+ )
68
+ raise RuntimeError(f"Failed to fork PTY: {e}") from e
69
+
70
+ if self.pid == 0:
71
+ # Child process - execute command
72
+ try:
73
+ # Set up environment
74
+ if env:
75
+ for key, value in env.items():
76
+ os.environ[key] = value
77
+
78
+ # Set terminal environment
79
+ os.environ["TERM"] = "xterm-256color"
80
+ os.environ["COLORTERM"] = "truecolor"
81
+
82
+ # Execute command
83
+ os.execvp(command[0], command)
84
+ except Exception as e:
85
+ # If exec fails, log and exit child process
86
+ traceback.print_exc()
87
+ sys.stderr.write(f"Failed to execute command {command}: {e}\n")
88
+ sys.stderr.flush()
89
+ os._exit(1)
90
+ else:
91
+ # Parent process - set up non-blocking I/O
92
+ flags = fcntl.fcntl(self.master_fd, fcntl.F_GETFL)
93
+ fcntl.fcntl(self.master_fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)
94
+
95
+ # Set initial size
96
+ self.resize(self.cols, self.rows)
97
+
98
+ # Start reading from PTY
99
+ self._running = True
100
+ self._read_task = asyncio.create_task(self._read_from_pty())
101
+
102
+ logger.info(
103
+ "Terminal session started",
104
+ session_id=self.session_id,
105
+ pid=self.pid,
106
+ command=command,
107
+ )
108
+
109
+ async def _read_from_pty(self) -> None:
110
+ """Continuously read from PTY using event loop's add_reader (non-blocking)"""
111
+ if self.master_fd is None:
112
+ return
113
+
114
+ loop = asyncio.get_event_loop()
115
+ read_queue: asyncio.Queue[bytes | None] = asyncio.Queue()
116
+
117
+ def read_callback() -> None:
118
+ """Called by event loop when data is available on the FD"""
119
+ if self.master_fd is None:
120
+ return
121
+ try:
122
+ data = os.read(self.master_fd, 4096)
123
+ if data:
124
+ # Put data in queue to be processed by async task
125
+ read_queue.put_nowait(data)
126
+ else:
127
+ # EOF - PTY closed
128
+ read_queue.put_nowait(None)
129
+ except OSError as e:
130
+ if e.errno == 11: # EAGAIN - shouldn't happen with add_reader
131
+ pass
132
+ else:
133
+ # PTY closed or error
134
+ logger.info(
135
+ "PTY read error in callback",
136
+ session_id=self.session_id,
137
+ error=str(e),
138
+ errno=e.errno,
139
+ )
140
+ read_queue.put_nowait(None)
141
+ except Exception as e:
142
+ logger.error(
143
+ "Unexpected error in PTY read callback",
144
+ session_id=self.session_id,
145
+ error=str(e),
146
+ )
147
+ read_queue.put_nowait(None)
148
+
149
+ # Register the FD with the event loop
150
+ loop.add_reader(self.master_fd, read_callback)
151
+
152
+ try:
153
+ while self._running:
154
+ # Wait for data from the queue (non-blocking for event loop)
155
+ data = await read_queue.get()
156
+
157
+ if data is None:
158
+ # EOF or error
159
+ logger.info(
160
+ "PTY closed (EOF)",
161
+ session_id=self.session_id,
162
+ )
163
+ break
164
+
165
+ # Process the data
166
+ decoded = data.decode("utf-8", errors="replace")
167
+ self.output_callback(decoded)
168
+ finally:
169
+ # Unregister the FD from the event loop
170
+ loop.remove_reader(self.master_fd)
171
+ logger.info(
172
+ "PTY read loop exited",
173
+ session_id=self.session_id,
174
+ running=self._running,
175
+ )
176
+
177
+ async def write_input(self, data: str) -> None:
178
+ """Write user input to PTY"""
179
+ if not self._running or self.master_fd is None:
180
+ raise RuntimeError(f"Terminal session {self.session_id} not running")
181
+
182
+ try:
183
+ os.write(self.master_fd, data.encode("utf-8"))
184
+ except OSError as e:
185
+ logger.error(
186
+ "Error writing to PTY",
187
+ session_id=self.session_id,
188
+ error=str(e),
189
+ )
190
+ raise
191
+
192
+ def resize(self, cols: int, rows: int) -> None:
193
+ """Resize the PTY to match terminal dimensions"""
194
+ if self.master_fd is None:
195
+ return
196
+
197
+ self.cols = cols
198
+ self.rows = rows
199
+
200
+ try:
201
+ size = struct.pack("HHHH", rows, cols, 0, 0)
202
+ fcntl.ioctl(self.master_fd, termios.TIOCSWINSZ, size)
203
+ logger.debug(
204
+ "Terminal resized",
205
+ session_id=self.session_id,
206
+ cols=cols,
207
+ rows=rows,
208
+ )
209
+ except Exception as e:
210
+ logger.error(
211
+ "Error resizing PTY",
212
+ session_id=self.session_id,
213
+ error=str(e),
214
+ )
215
+
216
+ async def stop(self) -> tuple[bool, int | None]:
217
+ """
218
+ Stop the terminal session and clean up resources.
219
+ Returns (success, exit_code)
220
+ """
221
+ if not self._running:
222
+ return True, None
223
+
224
+ self._running = False
225
+
226
+ # Cancel read task
227
+ if self._read_task and not self._read_task.done():
228
+ self._read_task.cancel()
229
+ try:
230
+ await self._read_task
231
+ except asyncio.CancelledError:
232
+ pass
233
+
234
+ exit_code = None
235
+
236
+ # Close file descriptor
237
+ if self.master_fd is not None:
238
+ try:
239
+ os.close(self.master_fd)
240
+ except Exception as e:
241
+ logger.error(
242
+ "Error closing PTY fd",
243
+ session_id=self.session_id,
244
+ error=str(e),
245
+ )
246
+ self.master_fd = None
247
+
248
+ # Kill child process
249
+ if self.pid is not None:
250
+ try:
251
+ os.kill(self.pid, signal.SIGTERM)
252
+ # Wait for process to terminate (with timeout)
253
+ for _ in range(10): # Wait up to 1 second
254
+ try:
255
+ pid, status = os.waitpid(self.pid, os.WNOHANG)
256
+ if pid != 0:
257
+ exit_code = os.WEXITSTATUS(status)
258
+ break
259
+ except ChildProcessError:
260
+ break
261
+ await asyncio.sleep(0.1)
262
+ else:
263
+ # Force kill if still running
264
+ try:
265
+ os.kill(self.pid, signal.SIGKILL)
266
+ os.waitpid(self.pid, 0)
267
+ except Exception:
268
+ pass
269
+ except Exception as e:
270
+ logger.error(
271
+ "Error killing PTY process",
272
+ session_id=self.session_id,
273
+ error=str(e),
274
+ )
275
+ self.pid = None
276
+
277
+ logger.info(
278
+ "Terminal session stopped",
279
+ session_id=self.session_id,
280
+ exit_code=exit_code,
281
+ )
282
+
283
+ return True, exit_code
284
+
285
+ @property
286
+ def is_running(self) -> bool:
287
+ """Check if terminal session is running"""
288
+ return self._running and self.master_fd is not None
289
+
290
+
291
+ class TerminalSessionManager:
292
+ """Manages multiple terminal sessions"""
293
+
294
+ def __init__(self, output_queue: asyncio.Queue[TerminalMessage]) -> None:
295
+ self._sessions: dict[str, TerminalSession] = {}
296
+ self._lock = asyncio.Lock()
297
+ self._websocket: object | None = None
298
+ self._output_queue = output_queue
299
+
300
+ # Send reset message immediately to clear stale sessions
301
+ try:
302
+ reset_message = TerminalResetSessions()
303
+ self._output_queue.put_nowait(reset_message)
304
+ logger.info("Sent TerminalResetSessions message")
305
+ except asyncio.QueueFull:
306
+ logger.error("Failed to queue terminal reset message - queue full")
307
+
308
+ def set_websocket(self, websocket: object) -> None:
309
+ """Set the websocket for sending output"""
310
+ self._websocket = websocket
311
+
312
+ async def start_session(
313
+ self,
314
+ websocket: object,
315
+ session_id: str,
316
+ command: list[str] | None = None,
317
+ cols: int = 80,
318
+ rows: int = 24,
319
+ env: dict[str, str] | None = None,
320
+ ) -> str:
321
+ """Start a new terminal session"""
322
+ async with self._lock:
323
+ if session_id in self._sessions:
324
+ raise RuntimeError(f"Terminal session {session_id} already exists")
325
+
326
+ # Store websocket reference
327
+ self._websocket = websocket
328
+
329
+ # Create output callback that queues data to be sent
330
+ def output_callback(data: str) -> None:
331
+ # Queue the output to be sent asynchronously
332
+ try:
333
+ terminal_output = TerminalOutput(
334
+ session_id=session_id,
335
+ data=data,
336
+ timestamp=time.time(),
337
+ )
338
+ self._output_queue.put_nowait(terminal_output)
339
+ except asyncio.QueueFull:
340
+ logger.error(
341
+ "Terminal output queue full",
342
+ session_id=session_id,
343
+ )
344
+
345
+ session = TerminalSession(
346
+ session_id=session_id,
347
+ output_callback=output_callback,
348
+ cols=cols,
349
+ rows=rows,
350
+ )
351
+
352
+ await session.start(command=command, env=env)
353
+ self._sessions[session_id] = session
354
+
355
+ return session_id
356
+
357
+ async def send_input(self, session_id: str, data: str) -> bool:
358
+ """Send input to a terminal session"""
359
+ async with self._lock:
360
+ session = self._sessions.get(session_id)
361
+ if session is None:
362
+ return False
363
+
364
+ try:
365
+ await session.write_input(data)
366
+ return True
367
+ except Exception as e:
368
+ logger.error(
369
+ "Failed to send input to terminal",
370
+ session_id=session_id,
371
+ error=str(e),
372
+ )
373
+ return False
374
+
375
+ async def resize_terminal(self, session_id: str, rows: int, cols: int) -> bool:
376
+ """Resize a terminal session"""
377
+ async with self._lock:
378
+ session = self._sessions.get(session_id)
379
+ if session is None:
380
+ return False
381
+
382
+ try:
383
+ session.resize(cols, rows)
384
+ return True
385
+ except Exception as e:
386
+ logger.error(
387
+ "Failed to resize terminal",
388
+ session_id=session_id,
389
+ error=str(e),
390
+ )
391
+ return False
392
+
393
+ async def stop_session(self, session_id: str) -> bool:
394
+ """Stop a terminal session"""
395
+ async with self._lock:
396
+ session = self._sessions.pop(session_id, None)
397
+ if session is None:
398
+ return True # Already stopped
399
+
400
+ try:
401
+ await session.stop()
402
+ return True
403
+ except Exception as e:
404
+ logger.error(
405
+ "Failed to stop terminal",
406
+ session_id=session_id,
407
+ error=str(e),
408
+ )
409
+ return False
410
+
411
+ async def stop_all_sessions(self) -> None:
412
+ """Stop all terminal sessions (cleanup on disconnect)"""
413
+ async with self._lock:
414
+ session_ids = list(self._sessions.keys())
415
+ for session_id in session_ids:
416
+ session = self._sessions.pop(session_id, None)
417
+ if session:
418
+ try:
419
+ await session.stop()
420
+ logger.info(
421
+ "Stopped terminal session on cleanup",
422
+ session_id=session_id,
423
+ )
424
+ except Exception as e:
425
+ logger.error(
426
+ "Error stopping terminal session on cleanup",
427
+ session_id=session_id,
428
+ error=str(e),
429
+ )
@@ -0,0 +1,29 @@
1
+ """Type definitions for terminal output streaming."""
2
+
3
+ import msgspec
4
+
5
+
6
+ class TerminalOutput(msgspec.Struct, tag="terminal_output"):
7
+ """Terminal output data from CLI to web client."""
8
+
9
+ session_id: str
10
+ data: str
11
+ timestamp: float
12
+
13
+
14
+ class TerminalStatus(msgspec.Struct, tag="terminal_status"):
15
+ """Terminal status update from CLI to web client."""
16
+
17
+ session_id: str
18
+ status: str
19
+ message: str
20
+ exit_code: int | None = None
21
+
22
+
23
+ class TerminalResetSessions(msgspec.Struct, tag="terminal_reset_sessions"):
24
+ """Sent from CLI when terminal session manager starts to clear stale sessions."""
25
+
26
+ # No fields needed - just a signal
27
+
28
+
29
+ TerminalMessage = TerminalOutput | TerminalStatus | TerminalResetSessions
@@ -32,15 +32,15 @@ from exponent.core.remote_execution.cli_rpc_types import (
32
32
 
33
33
  if TYPE_CHECKING:
34
34
  from exponent.core.remote_execution.client import RemoteExecutionClient
35
+ from exponent.core.remote_execution.cli_rpc_types import (
36
+ StreamingCodeExecutionRequest,
37
+ StreamingCodeExecutionResponse,
38
+ )
35
39
  from exponent.core.remote_execution.code_execution import (
36
40
  execute_code_streaming,
37
41
  )
38
42
  from exponent.core.remote_execution.file_write import execute_full_file_rewrite
39
43
  from exponent.core.remote_execution.truncation import truncate_tool_result
40
- from exponent.core.remote_execution.types import (
41
- StreamingCodeExecutionRequest,
42
- StreamingCodeExecutionResponse,
43
- )
44
44
  from exponent.core.remote_execution.utils import (
45
45
  assert_unreachable,
46
46
  safe_get_file_metadata,
@@ -17,7 +17,7 @@ from exponent.core.remote_execution.cli_rpc_types import (
17
17
  )
18
18
  from exponent.core.remote_execution.utils import truncate_output
19
19
 
20
- DEFAULT_CHARACTER_LIMIT = 90_000
20
+ DEFAULT_CHARACTER_LIMIT = 50_000
21
21
  DEFAULT_LIST_ITEM_LIMIT = 1000
22
22
  DEFAULT_LIST_PREVIEW_ITEMS = 10
23
23
 
@@ -120,6 +120,14 @@ class PythonEnvInfo(BaseModel):
120
120
  provider: Literal["venv", "pyenv", "pipenv", "conda"] | None = "pyenv"
121
121
 
122
122
 
123
+ class PortInfo(BaseModel):
124
+ process_name: str
125
+ port: int
126
+ protocol: str
127
+ pid: int | None
128
+ uptime_seconds: float | None
129
+
130
+
123
131
  class SystemInfo(BaseModel):
124
132
  name: str
125
133
  cwd: str
@@ -127,6 +135,7 @@ class SystemInfo(BaseModel):
127
135
  shell: str
128
136
  git: GitInfo | None
129
137
  python_env: PythonEnvInfo | None
138
+ port_usage: list[PortInfo] | None = None
130
139
 
131
140
 
132
141
  class HeartbeatInfo(BaseModel):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: indent
3
- Version: 0.1.21
3
+ Version: 0.1.23
4
4
  Summary: Indent is an AI Pair Programmer
5
5
  Author-email: Sashank Thupukari <sashank@exponent.run>
6
6
  Requires-Python: <3.13,>=3.10
@@ -22,6 +22,7 @@ Requires-Dist: msgspec>=0.19.0
22
22
  Requires-Dist: packaging~=24.1
23
23
  Requires-Dist: pip<26,>=25.0.1
24
24
  Requires-Dist: prompt-toolkit<4,>=3.0.36
25
+ Requires-Dist: psutil<7,>=5.9.0
25
26
  Requires-Dist: pydantic-ai==0.0.30
26
27
  Requires-Dist: pydantic-settings<3,>=2.2.1
27
28
  Requires-Dist: pydantic[email]<3,>=2.6.4
@@ -1,7 +1,7 @@
1
- exponent/__init__.py,sha256=QwRysHnO-cJwIFSvXReHDj93DEPhlibOTtnnb25J9-c,706
1
+ exponent/__init__.py,sha256=eZj6tqY-zTtH5r_8y6a4Vovz6LQ_hDHSesTIiwuyahQ,706
2
2
  exponent/cli.py,sha256=QnIeDTgWaQJrRs5WESCkQpVEQiJiAO4qWgB0rYlkd78,3344
3
3
  exponent/py.typed,sha256=9XZl5avs8yHp89XP_1Fjtbeg_2rjYorCC9I0k_j-h2c,334
4
- exponent/commands/cloud_commands.py,sha256=_qivNDIHIJXxLgVUm5vUNyuzG15llMqB8xY2MZ7F_lc,16710
4
+ exponent/commands/cloud_commands.py,sha256=yd0d7l8AaFZIgrFPRYspsKlmcyFqkK_ovSQKhK-YpVU,16773
5
5
  exponent/commands/common.py,sha256=M2KI9yKjB8fecPoDBphMa123c35-iNeaE9q4DxhkaFU,12817
6
6
  exponent/commands/config_commands.py,sha256=mmQYuyRosODgawoHWsn9xnWnV37GiQaxJjMv-_xreAU,8902
7
7
  exponent/commands/run_commands.py,sha256=xn0SJX0PPrmHu8Nh-kG-lJBhGC4qFCc04aLIzdVyFho,6389
@@ -13,13 +13,13 @@ exponent/core/config.py,sha256=TNFLUgLnfSocRMVSav_7E4VcaNHXZ_3Mg5Lp1smP46U,5731
13
13
  exponent/core/graphql/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
14
  exponent/core/graphql/client.py,sha256=SRagD3YPyoYZSO1RtfO-OXD7b5dm1NvgoL6CTbN380o,2009
15
15
  exponent/core/graphql/get_chats_query.py,sha256=9-2N1VfapXUZB3IFIKw5U_gKdmfyviJp5JSUntB_Yyk,1177
16
- exponent/core/graphql/mutations.py,sha256=eVoazBZowqLk3Jz-67tR_ATqrQKPhk5Teeq7YV-70b0,2931
16
+ exponent/core/graphql/mutations.py,sha256=Szs8wS_5EpVuZdt09QbstIm_8i-_-EGT4Z17pou5714,2971
17
17
  exponent/core/graphql/queries.py,sha256=RYsk8bub0esspqgakilhzX07yJf2652Ey9tBZK1l_lY,3297
18
18
  exponent/core/graphql/subscriptions.py,sha256=SQngv_nYVNJjiZ_P2k0UcLIu1pzc4vi7q7lhH89NCZM,393
19
19
  exponent/core/remote_execution/checkpoints.py,sha256=3QGYMLa8vT7XmxMYTRcGrW8kNGHwRC0AkUfULribJWg,6354
20
- exponent/core/remote_execution/cli_rpc_types.py,sha256=1HPLLtzpeGJfkh8nXlo8d_zDajzfDIwLu83Cfm60Cnc,8642
21
- exponent/core/remote_execution/client.py,sha256=dG2ueANYJH8CznDT5L3-UApkWZ25ANheSvv62JCMq9c,32038
22
- exponent/core/remote_execution/code_execution.py,sha256=jYPB_7dJzS9BTPLX9fKQpsFPatwjbXuaFFSxT9tDTfI,2388
20
+ exponent/core/remote_execution/cli_rpc_types.py,sha256=RPlvZYb7KLYlb65I8wcVAlkv3cSB7n2okZqMeWz_V4c,11791
21
+ exponent/core/remote_execution/client.py,sha256=bUl2w28c_okWndW9jEreVyYKeFNyL8h8OMZNBb4lf3g,40307
22
+ exponent/core/remote_execution/code_execution.py,sha256=QL78v2yHMrIcbNWIczAICWRceziXgG4pzw7gvhUOLxs,3328
23
23
  exponent/core/remote_execution/default_env.py,sha256=s44A1Cz9EgYuhF17WO3ESVNSLQw57EoOLyi9k6qliIo,911
24
24
  exponent/core/remote_execution/error_info.py,sha256=Rd7OA3ps06qYejPVcOaMBB9AtftP3wqQoOfiILFASnc,1378
25
25
  exponent/core/remote_execution/exceptions.py,sha256=eT57lBnBhvh-KJ5lsKWcfgGA5-WisAxhjZx-Z6OupZY,135
@@ -27,15 +27,18 @@ exponent/core/remote_execution/file_write.py,sha256=8Sa70ANIDHGxIAq4_Uy2Qoo55K7-
27
27
  exponent/core/remote_execution/files.py,sha256=mIVjhStaEKETW6y3pCVeV8eJKNaPtroWGP_kBK1x8uA,8776
28
28
  exponent/core/remote_execution/git.py,sha256=dGjBpeoKJZsYgRwctSq29GmbsNIN9tbSA3VwBnRD0IQ,7810
29
29
  exponent/core/remote_execution/http_fetch.py,sha256=aFEyXd0S-MRfisSMuIFiEyc1AEAj9nUZ9Rj_P_YRows,2827
30
+ exponent/core/remote_execution/port_utils.py,sha256=kWje8ikCzBXMeS7qr6NZZOzQOMoMuacgPUDYyloYgwM,2183
30
31
  exponent/core/remote_execution/session.py,sha256=jlQIdeUj0f7uOk3BgzlJtBJ_GyTIjCchBp5ApQuF2-I,3847
31
- exponent/core/remote_execution/system_context.py,sha256=QY1zY8_fWj3sh-fmLYtewvgxh7uTX4ITIJqlUTDkj6c,648
32
- exponent/core/remote_execution/tool_execution.py,sha256=DVO7jPS6JUGjY3GTevvNn5NlieKDOcx8id_nhi9x6Og,15783
32
+ exponent/core/remote_execution/system_context.py,sha256=I4RNuM60isS-529EuRrrEPPwJssNFC2TZ_7MhBTWEd0,754
33
+ exponent/core/remote_execution/terminal_session.py,sha256=s0ANsr_AGIfCI5u6PSvioOlnbk7ON43YAc30la5k3TM,14219
34
+ exponent/core/remote_execution/terminal_types.py,sha256=t4snBiTtidAEJZTvy789x-5HFqjkV9rlonjDO30PfAY,731
35
+ exponent/core/remote_execution/tool_execution.py,sha256=tDVW1c4ZysfTZqbR-wd7et41Mfv1lFQJCBvQShWnWq4,15791
33
36
  exponent/core/remote_execution/tool_type_utils.py,sha256=7qi6Qd8fvHts019ZSLPbtiy17BUqgqBg3P_gdfvFf7w,1301
34
- exponent/core/remote_execution/truncation.py,sha256=crHzjcUxL3tVe8yuoWNAE5r-uQKjtL-GzZczd-ucrd0,9849
35
- exponent/core/remote_execution/types.py,sha256=fUT5-3TE3sA2dUqWaen9VgpEllz4RA0B3i-FJpEEXOs,15601
37
+ exponent/core/remote_execution/truncation.py,sha256=0zFnmqXES2vtQCSpfXIQn5hgg6bZK4Sad_Cfh27xTZU,9849
38
+ exponent/core/remote_execution/types.py,sha256=2tp73g6WLhL3x-5FyP9jhadcRHIswt4wfJJlEvNwlvk,15782
36
39
  exponent/core/remote_execution/utils.py,sha256=6PlBqYJ3OQwZ0dgXiIu3br04a-d-glDeDZpD0XGGPAE,14793
37
40
  exponent/core/remote_execution/languages/python_execution.py,sha256=nsX_LsXcUcHhiEHpSTjOTVNd7CxM146al0kw_iQX5OU,7724
38
- exponent/core/remote_execution/languages/shell_streaming.py,sha256=gBACa5uFMGuQzEFpKE61ww6niHWFe58NToI1HaIYGVU,7662
41
+ exponent/core/remote_execution/languages/shell_streaming.py,sha256=MpE1XQiu18xWUGp1wD_Hb1nuPCQE-i5-_XO6FnkcNvo,7675
39
42
  exponent/core/remote_execution/languages/types.py,sha256=f7FjSRNRSga-ZaE3LddDhxCirUVjlSYMEdoskG6Pta4,314
40
43
  exponent/core/types/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
41
44
  exponent/core/types/command_data.py,sha256=_HqQsnamRZeVoVaTpeO3ecVUzNBdG62WXlFy6Q7rtUM,5294
@@ -46,7 +49,7 @@ exponent/migration-docs/login.md,sha256=KIeXy3m2nzSUgw-4PW1XzXfHael1D4Zu93CplLMb
46
49
  exponent/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
47
50
  exponent/utils/colors.py,sha256=HBkqe_ZmhJ9YiL2Fpulqek4KvLS5mwBTY4LQSM5N8SM,2762
48
51
  exponent/utils/version.py,sha256=GHZ9ET1kMyDubJZU3w2sah5Pw8XpiEakS5IOlt3wUnQ,8888
49
- indent-0.1.21.dist-info/METADATA,sha256=kUlmIYOdjCG1iPkNC0oFscWTh9tsFMVuFUG_MPmNltQ,1308
50
- indent-0.1.21.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
51
- indent-0.1.21.dist-info/entry_points.txt,sha256=q8q1t1sbl4NULGOR0OV5RmSG4KEjkpEQRU_RUXEGzcs,44
52
- indent-0.1.21.dist-info/RECORD,,
52
+ indent-0.1.23.dist-info/METADATA,sha256=WOFHXH8yRn6F_tajQ4B1H9eIxo5RxFcUHVFKhOps2nM,1340
53
+ indent-0.1.23.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
54
+ indent-0.1.23.dist-info/entry_points.txt,sha256=q8q1t1sbl4NULGOR0OV5RmSG4KEjkpEQRU_RUXEGzcs,44
55
+ indent-0.1.23.dist-info/RECORD,,