portacode 0.3.11.dev0__tar.gz → 0.3.11.dev2__tar.gz

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.
Files changed (38) hide show
  1. {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/PKG-INFO +1 -1
  2. {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/portacode/_version.py +2 -2
  3. {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/portacode/connection/handlers/session.py +23 -9
  4. {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/portacode/connection/handlers/terminal_handlers.py +25 -17
  5. {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/portacode.egg-info/PKG-INFO +1 -1
  6. {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/.gitignore +0 -0
  7. {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/.gitmodules +0 -0
  8. {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/LICENSE +0 -0
  9. {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/MANIFEST.in +0 -0
  10. {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/Makefile +0 -0
  11. {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/README.md +0 -0
  12. {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/docker-compose.yaml +0 -0
  13. {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/portacode/README.md +0 -0
  14. {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/portacode/__init__.py +0 -0
  15. {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/portacode/__main__.py +0 -0
  16. {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/portacode/cli.py +0 -0
  17. {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/portacode/connection/README.md +0 -0
  18. {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/portacode/connection/__init__.py +0 -0
  19. {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/portacode/connection/client.py +0 -0
  20. {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/portacode/connection/handlers/README.md +0 -0
  21. {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/portacode/connection/handlers/__init__.py +0 -0
  22. {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/portacode/connection/handlers/base.py +0 -0
  23. {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/portacode/connection/handlers/file_handlers.py +0 -0
  24. {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/portacode/connection/handlers/registry.py +0 -0
  25. {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/portacode/connection/handlers/system_handlers.py +0 -0
  26. {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/portacode/connection/multiplex.py +0 -0
  27. {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/portacode/connection/terminal.py +0 -0
  28. {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/portacode/data.py +0 -0
  29. {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/portacode/keypair.py +0 -0
  30. {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/portacode/service.py +0 -0
  31. {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/portacode.egg-info/SOURCES.txt +0 -0
  32. {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/portacode.egg-info/dependency_links.txt +0 -0
  33. {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/portacode.egg-info/entry_points.txt +0 -0
  34. {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/portacode.egg-info/requires.txt +0 -0
  35. {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/portacode.egg-info/top_level.txt +0 -0
  36. {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/pyproject.toml +0 -0
  37. {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/setup.cfg +0 -0
  38. {portacode-0.3.11.dev0 → portacode-0.3.11.dev2}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: portacode
3
- Version: 0.3.11.dev0
3
+ Version: 0.3.11.dev2
4
4
  Summary: Portacode CLI client and SDK
5
5
  Home-page: https://github.com/portacode/portacode
6
6
  Author: Meena Erian
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '0.3.11.dev'
21
- __version_tuple__ = version_tuple = (0, 3, 11, 'dev0')
20
+ __version__ = version = '0.3.11.dev2'
21
+ __version_tuple__ = version_tuple = (0, 3, 11, 'dev2')
@@ -36,10 +36,11 @@ def _build_child_env() -> Dict[str, str]:
36
36
  class TerminalSession:
37
37
  """Represents a local shell subprocess bound to a mux channel."""
38
38
 
39
- def __init__(self, session_id: str, proc: Process, channel: "Channel"):
39
+ def __init__(self, session_id: str, proc: Process, channel: "Channel", project_id: Optional[str] = None):
40
40
  self.id = session_id
41
41
  self.proc = proc
42
42
  self.channel = channel
43
+ self.project_id = project_id
43
44
  self._reader_task: Optional[asyncio.Task[None]] = None
44
45
  self._buffer: deque[str] = deque(maxlen=400)
45
46
 
@@ -139,7 +140,7 @@ class TerminalSession:
139
140
  class WindowsTerminalSession(TerminalSession):
140
141
  """Terminal session backed by a Windows ConPTY."""
141
142
 
142
- def __init__(self, session_id: str, pty, channel: "Channel"):
143
+ def __init__(self, session_id: str, pty, channel: "Channel", project_id: Optional[str] = None):
143
144
  # Create a proxy for the PTY process
144
145
  class _WinPTYProxy:
145
146
  def __init__(self, pty):
@@ -157,7 +158,7 @@ class WindowsTerminalSession(TerminalSession):
157
158
  loop = asyncio.get_running_loop()
158
159
  await loop.run_in_executor(None, self._pty.wait)
159
160
 
160
- super().__init__(session_id, _WinPTYProxy(pty), channel)
161
+ super().__init__(session_id, _WinPTYProxy(pty), channel, project_id)
161
162
  self._pty = pty
162
163
 
163
164
  async def start_io_forwarding(self) -> None:
@@ -247,7 +248,7 @@ class SessionManager:
247
248
  """Allocate a new unique channel ID for a terminal session using UUID."""
248
249
  return uuid.uuid4().hex
249
250
 
250
- async def create_session(self, shell: Optional[str] = None, cwd: Optional[str] = None) -> Dict[str, Any]:
251
+ async def create_session(self, shell: Optional[str] = None, cwd: Optional[str] = None, project_id: Optional[str] = None) -> Dict[str, Any]:
251
252
  """Create a new terminal session."""
252
253
  # Use the same UUID for both terminal_id and channel_id to ensure consistency
253
254
  session_uuid = uuid.uuid4().hex
@@ -279,7 +280,7 @@ class SessionManager:
279
280
  raise RuntimeError("pywinpty not installed on client")
280
281
 
281
282
  pty_proc = PtyProcess.spawn(shell, cwd=cwd or None, env=_build_child_env())
282
- session = WindowsTerminalSession(term_id, pty_proc, channel)
283
+ session = WindowsTerminalSession(term_id, pty_proc, channel, project_id)
283
284
  else:
284
285
  # Unix: try real PTY for proper TTY semantics
285
286
  try:
@@ -315,7 +316,7 @@ class SessionManager:
315
316
  cwd=cwd,
316
317
  env=_build_child_env(),
317
318
  )
318
- session = TerminalSession(term_id, proc, channel)
319
+ session = TerminalSession(term_id, proc, channel, project_id)
319
320
 
320
321
  self._sessions[term_id] = session
321
322
  await session.start_io_forwarding()
@@ -326,6 +327,7 @@ class SessionManager:
326
327
  "pid": session.proc.pid,
327
328
  "shell": shell,
328
329
  "cwd": cwd,
330
+ "project_id": project_id,
329
331
  }
330
332
 
331
333
  def get_session(self, terminal_id: str) -> Optional[TerminalSession]:
@@ -342,8 +344,19 @@ class SessionManager:
342
344
  logger.warning("session_manager: Attempted to remove non-existent session %s", terminal_id)
343
345
  return session
344
346
 
345
- def list_sessions(self) -> List[Dict[str, Any]]:
346
- """List all terminal sessions."""
347
+ def list_sessions(self, project_id: Optional[str] = None) -> List[Dict[str, Any]]:
348
+ """List all terminal sessions, optionally filtered by project_id."""
349
+ filtered_sessions = []
350
+ for s in self._sessions.values():
351
+ if project_id == "all":
352
+ filtered_sessions.append(s)
353
+ elif project_id is None:
354
+ if s.project_id is None:
355
+ filtered_sessions.append(s)
356
+ else:
357
+ if s.project_id == project_id:
358
+ filtered_sessions.append(s)
359
+
347
360
  return [
348
361
  {
349
362
  "terminal_id": s.id,
@@ -355,8 +368,9 @@ class SessionManager:
355
368
  "created_at": None, # Could add timestamp if needed
356
369
  "shell": None, # Could store shell info if needed
357
370
  "cwd": None, # Could store cwd info if needed
371
+ "project_id": s.project_id,
358
372
  }
359
- for s in self._sessions.values()
373
+ for s in filtered_sessions
360
374
  ]
361
375
 
362
376
  async def reattach_sessions(self, mux):
@@ -2,7 +2,7 @@
2
2
 
3
3
  import asyncio
4
4
  import logging
5
- from typing import Any, Dict
5
+ from typing import Any, Dict, Optional
6
6
 
7
7
  from .base import AsyncHandler
8
8
  from .session import SessionManager
@@ -55,6 +55,7 @@ class TerminalStartHandler(AsyncHandler):
55
55
  "event": "terminal_exit",
56
56
  "terminal_id": terminal_id,
57
57
  "returncode": session.proc.returncode,
58
+ "project_id": session.project_id,
58
59
  })
59
60
 
60
61
  # Only cleanup session if it still exists (not already removed by stop handler)
@@ -118,28 +119,30 @@ class TerminalStopHandler(AsyncHandler):
118
119
  if not session:
119
120
  logger.warning("terminal_stop: Terminal %s not found, may have already been stopped", terminal_id)
120
121
  # Send completion event immediately for not found terminals
121
- asyncio.create_task(self._send_not_found_completion(terminal_id))
122
+ asyncio.create_task(self._send_not_found_completion(terminal_id, None))
122
123
  return {
123
124
  "event": "terminal_stopped",
124
125
  "terminal_id": terminal_id,
125
126
  "status": "not_found",
126
- "message": "Terminal was not found or already stopped"
127
+ "message": "Terminal was not found or already stopped",
128
+ "project_id": None,
127
129
  }
128
130
 
129
131
  logger.info("terminal_stop: Found session for terminal %s (PID: %s), starting background stop process",
130
132
  terminal_id, getattr(session.proc, 'pid', 'unknown'))
131
133
 
132
134
  # Start stop process in background without blocking the control channel
133
- asyncio.create_task(self._stop_session_safely(session, terminal_id))
135
+ asyncio.create_task(self._stop_session_safely(session, terminal_id, session.project_id))
134
136
 
135
137
  return {
136
138
  "event": "terminal_stopped",
137
139
  "terminal_id": terminal_id,
138
140
  "status": "stopping",
139
- "message": "Terminal stop process initiated"
141
+ "message": "Terminal stop process initiated",
142
+ "project_id": session.project_id,
140
143
  }
141
144
 
142
- async def _stop_session_safely(self, session, terminal_id: str) -> None:
145
+ async def _stop_session_safely(self, session, terminal_id: str, project_id: Optional[str] = None) -> None:
143
146
  """Safely stop a session in the background with timeout and error handling."""
144
147
  logger.info("terminal_stop: Starting background stop process for terminal %s", terminal_id)
145
148
 
@@ -153,7 +156,8 @@ class TerminalStopHandler(AsyncHandler):
153
156
  "event": "terminal_stop_completed",
154
157
  "terminal_id": terminal_id,
155
158
  "status": "success",
156
- "message": "Terminal stopped successfully"
159
+ "message": "Terminal stopped successfully",
160
+ "project_id": project_id,
157
161
  })
158
162
 
159
163
  except asyncio.TimeoutError:
@@ -175,7 +179,8 @@ class TerminalStopHandler(AsyncHandler):
175
179
  "event": "terminal_stop_completed",
176
180
  "terminal_id": terminal_id,
177
181
  "status": "timeout",
178
- "message": "Terminal stop timed out, process was force killed"
182
+ "message": "Terminal stop timed out, process was force killed",
183
+ "project_id": project_id,
179
184
  })
180
185
 
181
186
  except Exception as exc:
@@ -186,16 +191,18 @@ class TerminalStopHandler(AsyncHandler):
186
191
  "event": "terminal_stop_completed",
187
192
  "terminal_id": terminal_id,
188
193
  "status": "error",
189
- "message": f"Error stopping terminal: {str(exc)}"
194
+ "message": f"Error stopping terminal: {str(exc)}",
195
+ "project_id": project_id,
190
196
  })
191
197
 
192
- async def _send_not_found_completion(self, terminal_id: str) -> None:
198
+ async def _send_not_found_completion(self, terminal_id: str, project_id: Optional[str] = None) -> None:
193
199
  """Send completion event for not found terminals."""
194
200
  await self.control_channel.send({
195
201
  "event": "terminal_stop_completed",
196
202
  "terminal_id": terminal_id,
197
203
  "status": "not_found",
198
- "message": "Terminal was not found or already stopped"
204
+ "message": "Terminal was not found or already stopped",
205
+ "project_id": project_id,
199
206
  })
200
207
 
201
208
 
@@ -213,14 +220,15 @@ class TerminalListHandler(AsyncHandler):
213
220
  raise RuntimeError("Session manager not available")
214
221
 
215
222
  # Accept project_id argument: None (default) = only no project, 'all' = all, else = filter by project_id
216
- project_id = message.get("project_id")
217
- all_projects = False
218
- if project_id == "all":
219
- all_projects = True
220
- project_id = None
221
- sessions = session_manager.list_sessions(project_id=project_id, all_projects=all_projects)
223
+ requested_project_id = message.get("project_id")
224
+
225
+ if requested_project_id == "all":
226
+ sessions = session_manager.list_sessions(project_id="all")
227
+ else:
228
+ sessions = session_manager.list_sessions(project_id=requested_project_id)
222
229
 
223
230
  return {
224
231
  "event": "terminal_list",
225
232
  "sessions": sessions,
233
+ "project_id": requested_project_id,
226
234
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: portacode
3
- Version: 0.3.11.dev0
3
+ Version: 0.3.11.dev2
4
4
  Summary: Portacode CLI client and SDK
5
5
  Home-page: https://github.com/portacode/portacode
6
6
  Author: Meena Erian
File without changes