portacode 1.3.35__py3-none-any.whl → 1.3.37__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 portacode might be problematic. Click here for more details.

portacode/_version.py CHANGED
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '1.3.35'
32
- __version_tuple__ = version_tuple = (1, 3, 35)
31
+ __version__ = version = '1.3.37'
32
+ __version_tuple__ = version_tuple = (1, 3, 37)
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -61,6 +61,9 @@ class GitManager:
61
61
  self.is_git_repo = False
62
62
  self._change_callback = change_callback
63
63
 
64
+ # Track git processes spawned by this specific GitManager instance
65
+ self._tracked_git_processes = set()
66
+
64
67
  # Periodic monitoring attributes
65
68
  self._monitoring_task: Optional[asyncio.Task] = None
66
69
  self._cached_status_summary: Optional[Dict[str, int]] = None
@@ -84,9 +87,29 @@ class GitManager:
84
87
  self.repo = Repo(self.project_path)
85
88
  self.is_git_repo = True
86
89
  logger.info("Initialized Git repo for project: %s", self.project_path)
90
+
91
+ # Track initial git processes after repo creation
92
+ self._track_current_git_processes()
93
+
87
94
  except (InvalidGitRepositoryError, Exception) as e:
88
95
  logger.debug("Not a Git repository or Git error: %s", e)
89
96
 
97
+ def _track_current_git_processes(self):
98
+ """Track currently running git cat-file processes for this repo."""
99
+ try:
100
+ import psutil
101
+ for proc in psutil.process_iter(['pid', 'cmdline', 'cwd']):
102
+ try:
103
+ cmdline = proc.info['cmdline']
104
+ if (cmdline and len(cmdline) >= 2 and
105
+ 'git' in cmdline[0] and 'cat-file' in cmdline[1] and
106
+ proc.info['cwd'] == self.project_path):
107
+ self._tracked_git_processes.add(proc.pid)
108
+ except (psutil.NoSuchProcess, psutil.AccessDenied):
109
+ continue
110
+ except Exception as e:
111
+ logger.debug("Error tracking git processes: %s", e)
112
+
90
113
  def reinitialize(self):
91
114
  """Reinitialize git repo detection (useful when .git directory is created after initialization)."""
92
115
  logger.info("Reinitializing git repo detection for: %s", self.project_path)
@@ -1933,4 +1956,43 @@ class GitManager:
1933
1956
  def cleanup(self):
1934
1957
  """Cleanup resources when GitManager is being destroyed."""
1935
1958
  logger.info("Cleaning up GitManager for %s", self.project_path)
1936
- self.stop_periodic_monitoring()
1959
+ self.stop_periodic_monitoring()
1960
+
1961
+ # CRITICAL: Close GitPython repo to cleanup git cat-file processes
1962
+ if self.repo:
1963
+ try:
1964
+ # Force cleanup of git command processes
1965
+ if hasattr(self.repo.git, 'clear_cache'):
1966
+ self.repo.git.clear_cache()
1967
+ self.repo.close()
1968
+ logger.info("Successfully closed GitPython repo for %s", self.project_path)
1969
+ except Exception as e:
1970
+ logger.warning("Error during git repo cleanup for %s: %s", self.project_path, e)
1971
+ finally:
1972
+ self.repo = None
1973
+
1974
+ # Clean up only the git processes tracked by this specific GitManager instance
1975
+ try:
1976
+ import psutil
1977
+ killed_count = 0
1978
+
1979
+ for pid in list(self._tracked_git_processes):
1980
+ try:
1981
+ proc = psutil.Process(pid)
1982
+ if proc.is_running():
1983
+ proc.terminate()
1984
+ killed_count += 1
1985
+ logger.info("Terminated tracked git process %d", pid)
1986
+ except (psutil.NoSuchProcess, psutil.AccessDenied):
1987
+ pass
1988
+ finally:
1989
+ self._tracked_git_processes.discard(pid)
1990
+
1991
+ if killed_count > 0:
1992
+ logger.info("Cleaned up %d tracked git processes for session", killed_count)
1993
+
1994
+ except Exception as e:
1995
+ logger.warning("Error cleaning up tracked git processes: %s", e)
1996
+
1997
+ # Clear the tracking set
1998
+ self._tracked_git_processes.clear()
@@ -3,6 +3,8 @@
3
3
  import logging
4
4
  import os
5
5
  import platform
6
+ import threading
7
+ import time
6
8
  from pathlib import Path
7
9
  from typing import Any, Dict
8
10
  from portacode import __version__
@@ -12,6 +14,25 @@ from .base import SyncHandler
12
14
 
13
15
  logger = logging.getLogger(__name__)
14
16
 
17
+ # Global CPU monitoring
18
+ _cpu_percent = 0.0
19
+ _cpu_thread = None
20
+ _cpu_lock = threading.Lock()
21
+
22
+ def _cpu_monitor():
23
+ """Background thread to update CPU usage every 5 seconds."""
24
+ global _cpu_percent
25
+ while True:
26
+ _cpu_percent = psutil.cpu_percent(interval=5.0)
27
+
28
+ def _ensure_cpu_thread():
29
+ """Ensure CPU monitoring thread is running (singleton)."""
30
+ global _cpu_thread
31
+ with _cpu_lock:
32
+ if _cpu_thread is None or not _cpu_thread.is_alive():
33
+ _cpu_thread = threading.Thread(target=_cpu_monitor, daemon=True)
34
+ _cpu_thread.start()
35
+
15
36
 
16
37
  def _get_os_info() -> Dict[str, Any]:
17
38
  """Get operating system information with robust error handling."""
@@ -98,15 +119,13 @@ class SystemInfoHandler(SyncHandler):
98
119
  """Get system information including OS details."""
99
120
  logger.debug("Collecting system information...")
100
121
 
122
+ # Ensure CPU monitoring thread is running
123
+ _ensure_cpu_thread()
124
+
101
125
  # Collect basic system metrics
102
126
  info = {}
103
127
 
104
- try:
105
- info["cpu_percent"] = psutil.cpu_percent(interval=0.1)
106
- logger.debug("CPU usage: %s%%", info["cpu_percent"])
107
- except Exception as e:
108
- logger.warning("Failed to get CPU info: %s", e)
109
- info["cpu_percent"] = 0.0
128
+ info["cpu_percent"] = _cpu_percent
110
129
 
111
130
  try:
112
131
  info["memory"] = psutil.virtual_memory()._asdict()
@@ -420,6 +420,14 @@ class TerminalManager:
420
420
  pass
421
421
  self._ctl_task = asyncio.create_task(self._control_loop())
422
422
 
423
+ # Start periodic system info sender
424
+ if getattr(self, "_system_info_task", None):
425
+ try:
426
+ self._system_info_task.cancel()
427
+ except Exception:
428
+ pass
429
+ self._system_info_task = asyncio.create_task(self._periodic_system_info())
430
+
423
431
  # For initial connections, request client sessions after control loop starts
424
432
  if is_initial:
425
433
  asyncio.create_task(self._initial_connection_setup())
@@ -518,6 +526,20 @@ class TerminalManager:
518
526
  # Continue processing other messages
519
527
  continue
520
528
 
529
+ async def _periodic_system_info(self) -> None:
530
+ """Send system_info event every 10 seconds when clients are connected."""
531
+ while True:
532
+ try:
533
+ await asyncio.sleep(10)
534
+ if self._client_session_manager.has_interested_clients():
535
+ from .handlers.system_handlers import SystemInfoHandler
536
+ handler = SystemInfoHandler(self._control_channel, self._context)
537
+ system_info = handler.execute({})
538
+ await self._send_session_aware(system_info)
539
+ except Exception as exc:
540
+ logger.exception("Error in periodic system info: %s", exc)
541
+ continue
542
+
521
543
  async def _send_initial_data_to_clients(self, newly_added_sessions: List[str] = None):
522
544
  """Send initial system info and terminal list to connected clients.
523
545
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: portacode
3
- Version: 1.3.35
3
+ Version: 1.3.37
4
4
  Summary: Portacode CLI client and SDK
5
5
  Home-page: https://github.com/portacode/portacode
6
6
  Author: Meena Erian
@@ -1,7 +1,7 @@
1
1
  portacode/README.md,sha256=4dKtpvR8LNgZPVz37GmkQCMWIr_u25Ao63iW56s7Ke4,775
2
2
  portacode/__init__.py,sha256=oB3sV1wXr-um-RXio73UG8E5Xx6cF2ZVJveqjNmC-vQ,1086
3
3
  portacode/__main__.py,sha256=jmHTGC1hzmo9iKJLv-SSYe9BSIbPPZ2IOpecI03PlTs,296
4
- portacode/_version.py,sha256=1QrJuO_k2xQp0QmWYHVXK99GSTggGIkW59z4kHpoDpc,706
4
+ portacode/_version.py,sha256=fE8KVhDEsGVP9yYUZTuzgGDIbJx4faKQpSpCa5XjGBY,706
5
5
  portacode/cli.py,sha256=eDqcZMVFHKzqqWxedhhx8ylu5WMVCLqeJQkbPR7RcJE,16333
6
6
  portacode/data.py,sha256=5-s291bv8J354myaHm1Y7CQZTZyRzMU3TGe5U4hb-FA,1591
7
7
  portacode/keypair.py,sha256=PAcOYqlVLOoZTPYi6LvLjfsY6BkrWbLOhSZLb8r5sHs,3635
@@ -11,7 +11,7 @@ portacode/connection/README.md,sha256=f9rbuIEKa7cTm9C98rCiBbEtbiIXQU11esGSNhSMiJ
11
11
  portacode/connection/__init__.py,sha256=atqcVGkViIEd7pRa6cP2do07RJOM0UWpbnz5zXjGktU,250
12
12
  portacode/connection/client.py,sha256=Uqsy5xzN0j5AY0xkYs2_qd67N7SUopcnpkCbkmOnMgg,9102
13
13
  portacode/connection/multiplex.py,sha256=L-TxqJ_ZEbfNEfu1cwxgJ5vUdyRzZjsMy2Kx1diiZys,5237
14
- portacode/connection/terminal.py,sha256=kUoB2bGe-ZmGnL5fDUuv8JRYr0Qt8rjYaERJW0ahZRQ,42504
14
+ portacode/connection/terminal.py,sha256=5PlEnbEDbVsvuoaduO06PhU_BIZmycNejP80C47ot44,43511
15
15
  portacode/connection/handlers/README.md,sha256=HsLZG1QK1JNm67HsgL6WoDg9nxzKXxwkc5fJPFJdX5g,12169
16
16
  portacode/connection/handlers/WEBSOCKET_PROTOCOL.md,sha256=xnVrY5uR00z88Q1mBJp-RHVGBoUDS0CWn0PD_D49LDk,74136
17
17
  portacode/connection/handlers/__init__.py,sha256=U8OLLKNPZHca03Arhzbqfs2xzDObQD_Ze3zr5j_G_a4,2193
@@ -22,13 +22,13 @@ portacode/connection/handlers/project_aware_file_handlers.py,sha256=n0M2WmBNWPwz
22
22
  portacode/connection/handlers/project_state_handlers.py,sha256=v6ZefGW9i7n1aZLq2jOGumJIjYb6aHlPI4m1jkYewm8,1686
23
23
  portacode/connection/handlers/registry.py,sha256=qXGE60sYEWg6ZtVQzFcZ5YI2XWR6lMgw4hAL9x5qR1I,6181
24
24
  portacode/connection/handlers/session.py,sha256=XWiD4dofzZB9AH7EDqbWeJ-1CrSNPCUTR2nE2UEZh7Y,35568
25
- portacode/connection/handlers/system_handlers.py,sha256=65V5ctT0dIBc-oWG91e62MbdvU0z6x6JCTQuIqCWmZ0,5242
25
+ portacode/connection/handlers/system_handlers.py,sha256=nOlJ8kkiikdj4_Bz8IfYLLyy-X6LsP4FKoz6_GmQuP8,5706
26
26
  portacode/connection/handlers/tab_factory.py,sha256=yn93h6GASjD1VpvW1oqpax3EpoT0r7r97zFXxML1wdA,16173
27
27
  portacode/connection/handlers/terminal_handlers.py,sha256=HRwHW1GiqG1NtHVEqXHKaYkFfQEzCDDH6YIlHcb4XD8,11866
28
28
  portacode/connection/handlers/project_state/README.md,sha256=trdd4ig6ungmwH5SpbSLfyxbL-QgPlGNU-_XrMEiXtw,10114
29
29
  portacode/connection/handlers/project_state/__init__.py,sha256=5ucIqk6Iclqg6bKkL8r_wVs5Tlt6B9J7yQH6yQUt7gc,2541
30
30
  portacode/connection/handlers/project_state/file_system_watcher.py,sha256=2zingW9BoNKRijghHC2eHHdRoyDRdLmIl1yH1y-iuF8,10831
31
- portacode/connection/handlers/project_state/git_manager.py,sha256=GO0AEXzHEaKOBGZP043_V2KgGz8zqmSahWJ5KHgC_Cs,88845
31
+ portacode/connection/handlers/project_state/git_manager.py,sha256=QGaGR8Y1NJx2vj0t33mEs7dxTWNDGVgu__eq1A3dhcQ,91508
32
32
  portacode/connection/handlers/project_state/handlers.py,sha256=ay9D2KCPnPx-Luhx3_VAVq_HVFgLDDBwJvgaiKeseak,37889
33
33
  portacode/connection/handlers/project_state/manager.py,sha256=XX3wMgGdPbRgBBs_R1dXtQ4D9j-itETrJR_6IfBeDU0,61296
34
34
  portacode/connection/handlers/project_state/models.py,sha256=EZTKvxHKs8QlQUbzI0u2IqfzfRRXZixUIDBwTGCJATI,4313
@@ -38,7 +38,7 @@ portacode/static/js/utils/ntp-clock.js,sha256=KMeHGT-IlUSlxVRZZ899z25dQCJh6EJbgX
38
38
  portacode/utils/NTP_ARCHITECTURE.md,sha256=WkESTbz5SNAgdmDKk3DrHMhtYOPji_Kt3_a9arWdRig,3894
39
39
  portacode/utils/__init__.py,sha256=NgBlWTuNJESfIYJzP_3adI1yJQJR0XJLRpSdVNaBAN0,33
40
40
  portacode/utils/ntp_clock.py,sha256=6QJOVZr9VQuxIyJt9KNG4dR-nZ3bKNyipMxjqDWP89Y,5152
41
- portacode-1.3.35.dist-info/licenses/LICENSE,sha256=2FGbCnUDgRYuQTkB1O1dUUpu5CVAjK1j4_p6ack9Z54,1066
41
+ portacode-1.3.37.dist-info/licenses/LICENSE,sha256=2FGbCnUDgRYuQTkB1O1dUUpu5CVAjK1j4_p6ack9Z54,1066
42
42
  test_modules/README.md,sha256=Do_agkm9WhSzueXjRAkV_xEj6Emy5zB3N3VKY5Roce8,9274
43
43
  test_modules/__init__.py,sha256=1LcbHodIHsB0g-g4NGjSn6AMuCoGbymvXPYLOb6Z7F0,53
44
44
  test_modules/test_device_online.py,sha256=yiSyVaMwKAugqIX_ZIxmLXiOlmA_8IRXiUp12YmpB98,1653
@@ -63,8 +63,8 @@ testing_framework/core/playwright_manager.py,sha256=kWKmlxzftDY0ZWS891zlHu_Ctdw7
63
63
  testing_framework/core/runner.py,sha256=j2QwNJmAxVBmJvcbVS7DgPJUKPNzqfLmt_4NNdaKmZU,19297
64
64
  testing_framework/core/shared_cli_manager.py,sha256=BESSNtyQb7BOlaOvZmm04T8Uezjms4KCBs2MzTxvzYQ,8790
65
65
  testing_framework/core/test_discovery.py,sha256=2FZ9fJ8Dp5dloA-fkgXoJ_gCMC_nYPBnA3Hs2xlagzM,4928
66
- portacode-1.3.35.dist-info/METADATA,sha256=rXmptmCEAAPKyXY3T2lO4yVKuWZEgz9_n9P_KbclyjI,6989
67
- portacode-1.3.35.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
68
- portacode-1.3.35.dist-info/entry_points.txt,sha256=lLUUL-BM6_wwe44Xv0__5NQ1BnAz6jWjSMFvZdWW3zU,48
69
- portacode-1.3.35.dist-info/top_level.txt,sha256=TGhTYUxfW8SyVZc_zGgzjzc24gGT7nSw8Qf73liVRKM,41
70
- portacode-1.3.35.dist-info/RECORD,,
66
+ portacode-1.3.37.dist-info/METADATA,sha256=sRpzOeUTy_7TvPzGWQqZGMF-khXlJ9IKBshPRH2UAOY,6989
67
+ portacode-1.3.37.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
68
+ portacode-1.3.37.dist-info/entry_points.txt,sha256=lLUUL-BM6_wwe44Xv0__5NQ1BnAz6jWjSMFvZdWW3zU,48
69
+ portacode-1.3.37.dist-info/top_level.txt,sha256=TGhTYUxfW8SyVZc_zGgzjzc24gGT7nSw8Qf73liVRKM,41
70
+ portacode-1.3.37.dist-info/RECORD,,