portacode 0.3.5.dev0__tar.gz → 0.3.7.dev0__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.
- {portacode-0.3.5.dev0 → portacode-0.3.7.dev0}/PKG-INFO +2 -1
- {portacode-0.3.5.dev0 → portacode-0.3.7.dev0}/portacode/_version.py +2 -2
- {portacode-0.3.5.dev0 → portacode-0.3.7.dev0}/portacode/cli.py +11 -1
- {portacode-0.3.5.dev0 → portacode-0.3.7.dev0}/portacode/connection/handlers/base.py +5 -1
- {portacode-0.3.5.dev0 → portacode-0.3.7.dev0}/portacode/connection/handlers/registry.py +5 -2
- {portacode-0.3.5.dev0 → portacode-0.3.7.dev0}/portacode/connection/handlers/session.py +78 -10
- {portacode-0.3.5.dev0 → portacode-0.3.7.dev0}/portacode/connection/handlers/terminal_handlers.py +69 -2
- {portacode-0.3.5.dev0 → portacode-0.3.7.dev0}/portacode/connection/terminal.py +35 -21
- {portacode-0.3.5.dev0 → portacode-0.3.7.dev0}/portacode.egg-info/PKG-INFO +2 -1
- {portacode-0.3.5.dev0 → portacode-0.3.7.dev0}/setup.py +1 -0
- {portacode-0.3.5.dev0 → portacode-0.3.7.dev0}/.gitignore +0 -0
- {portacode-0.3.5.dev0 → portacode-0.3.7.dev0}/.gitmodules +0 -0
- {portacode-0.3.5.dev0 → portacode-0.3.7.dev0}/LICENSE +0 -0
- {portacode-0.3.5.dev0 → portacode-0.3.7.dev0}/MANIFEST.in +0 -0
- {portacode-0.3.5.dev0 → portacode-0.3.7.dev0}/Makefile +0 -0
- {portacode-0.3.5.dev0 → portacode-0.3.7.dev0}/README.md +0 -0
- {portacode-0.3.5.dev0 → portacode-0.3.7.dev0}/docker-compose.yaml +0 -0
- {portacode-0.3.5.dev0 → portacode-0.3.7.dev0}/portacode/README.md +0 -0
- {portacode-0.3.5.dev0 → portacode-0.3.7.dev0}/portacode/__init__.py +0 -0
- {portacode-0.3.5.dev0 → portacode-0.3.7.dev0}/portacode/__main__.py +0 -0
- {portacode-0.3.5.dev0 → portacode-0.3.7.dev0}/portacode/connection/README.md +0 -0
- {portacode-0.3.5.dev0 → portacode-0.3.7.dev0}/portacode/connection/__init__.py +0 -0
- {portacode-0.3.5.dev0 → portacode-0.3.7.dev0}/portacode/connection/client.py +0 -0
- {portacode-0.3.5.dev0 → portacode-0.3.7.dev0}/portacode/connection/handlers/README.md +0 -0
- {portacode-0.3.5.dev0 → portacode-0.3.7.dev0}/portacode/connection/handlers/__init__.py +0 -0
- {portacode-0.3.5.dev0 → portacode-0.3.7.dev0}/portacode/connection/handlers/file_handlers.py +0 -0
- {portacode-0.3.5.dev0 → portacode-0.3.7.dev0}/portacode/connection/handlers/system_handlers.py +0 -0
- {portacode-0.3.5.dev0 → portacode-0.3.7.dev0}/portacode/connection/multiplex.py +0 -0
- {portacode-0.3.5.dev0 → portacode-0.3.7.dev0}/portacode/data.py +0 -0
- {portacode-0.3.5.dev0 → portacode-0.3.7.dev0}/portacode/keypair.py +0 -0
- {portacode-0.3.5.dev0 → portacode-0.3.7.dev0}/portacode/service.py +0 -0
- {portacode-0.3.5.dev0 → portacode-0.3.7.dev0}/portacode.egg-info/SOURCES.txt +0 -0
- {portacode-0.3.5.dev0 → portacode-0.3.7.dev0}/portacode.egg-info/dependency_links.txt +0 -0
- {portacode-0.3.5.dev0 → portacode-0.3.7.dev0}/portacode.egg-info/entry_points.txt +0 -0
- {portacode-0.3.5.dev0 → portacode-0.3.7.dev0}/portacode.egg-info/requires.txt +0 -0
- {portacode-0.3.5.dev0 → portacode-0.3.7.dev0}/portacode.egg-info/top_level.txt +0 -0
- {portacode-0.3.5.dev0 → portacode-0.3.7.dev0}/pyproject.toml +0 -0
- {portacode-0.3.5.dev0 → portacode-0.3.7.dev0}/setup.cfg +0 -0
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: portacode
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.7.dev0
|
|
4
4
|
Summary: Portacode CLI client and SDK
|
|
5
5
|
Home-page: https://github.com/portacode/portacode
|
|
6
6
|
Author: Meena Erian
|
|
7
|
+
Author-email: hi@menas.pro
|
|
7
8
|
Classifier: Programming Language :: Python :: 3
|
|
8
9
|
Classifier: License :: OSI Approved :: MIT License
|
|
9
10
|
Classifier: Operating System :: OS Independent
|
|
@@ -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.
|
|
21
|
-
__version_tuple__ = version_tuple = (0, 3,
|
|
20
|
+
__version__ = version = '0.3.7.dev'
|
|
21
|
+
__version_tuple__ = version_tuple = (0, 3, 7, 'dev0')
|
|
@@ -29,11 +29,21 @@ def cli() -> None:
|
|
|
29
29
|
@cli.command()
|
|
30
30
|
@click.option("--gateway", "gateway", "-g", help="Gateway websocket URL (overrides env/ default)")
|
|
31
31
|
@click.option("--detach", "detach", "-d", is_flag=True, help="Run connection in background")
|
|
32
|
+
@click.option("--debug", "debug", is_flag=True, help="Enable debug logging")
|
|
32
33
|
@click.option("--non-interactive", "non_interactive", is_flag=True, envvar="PORTACODE_NON_INTERACTIVE", hidden=True,
|
|
33
34
|
help="Skip interactive prompts (used by background service)")
|
|
34
|
-
def connect(gateway: str | None, detach: bool, non_interactive: bool) -> None: # noqa: D401 – Click callback
|
|
35
|
+
def connect(gateway: str | None, detach: bool, debug: bool, non_interactive: bool) -> None: # noqa: D401 – Click callback
|
|
35
36
|
"""Connect this machine to Portacode gateway."""
|
|
36
37
|
|
|
38
|
+
# Set up debug logging if requested
|
|
39
|
+
if debug:
|
|
40
|
+
import logging
|
|
41
|
+
logging.basicConfig(
|
|
42
|
+
level=logging.DEBUG,
|
|
43
|
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
|
44
|
+
)
|
|
45
|
+
click.echo(click.style("🔍 Debug logging enabled", fg="yellow"))
|
|
46
|
+
|
|
37
47
|
# 1. Ensure only a single connection per user
|
|
38
48
|
pid_file = get_pid_file()
|
|
39
49
|
if pid_file.exists():
|
|
@@ -81,11 +81,15 @@ class AsyncHandler(BaseHandler):
|
|
|
81
81
|
|
|
82
82
|
async def handle(self, message: Dict[str, Any], reply_channel: Optional[str] = None) -> None:
|
|
83
83
|
"""Handle the command by executing it and sending the response."""
|
|
84
|
+
logger.info("handler: Processing command %s with reply_channel=%s",
|
|
85
|
+
self.command_name, reply_channel)
|
|
86
|
+
|
|
84
87
|
try:
|
|
85
88
|
response = await self.execute(message)
|
|
89
|
+
logger.info("handler: Command %s executed successfully", self.command_name)
|
|
86
90
|
await self.send_response(response, reply_channel)
|
|
87
91
|
except Exception as exc:
|
|
88
|
-
logger.exception("Error in async handler %s: %s", self.command_name, exc)
|
|
92
|
+
logger.exception("handler: Error in async handler %s: %s", self.command_name, exc)
|
|
89
93
|
await self.send_error(str(exc), reply_channel)
|
|
90
94
|
|
|
91
95
|
|
|
@@ -81,16 +81,19 @@ class CommandRegistry:
|
|
|
81
81
|
Returns:
|
|
82
82
|
True if handler was found and executed, False otherwise
|
|
83
83
|
"""
|
|
84
|
+
logger.info("registry: Dispatching command '%s' with reply_channel=%s", command_name, reply_channel)
|
|
85
|
+
|
|
84
86
|
handler = self.get_handler(command_name)
|
|
85
87
|
if handler is None:
|
|
86
|
-
logger.warning("No handler found for command: %s", command_name)
|
|
88
|
+
logger.warning("registry: No handler found for command: %s", command_name)
|
|
87
89
|
return False
|
|
88
90
|
|
|
89
91
|
try:
|
|
90
92
|
await handler.handle(message, reply_channel)
|
|
93
|
+
logger.info("registry: Successfully dispatched command '%s'", command_name)
|
|
91
94
|
return True
|
|
92
95
|
except Exception as exc:
|
|
93
|
-
logger.exception("Error dispatching command %s: %s", command_name, exc)
|
|
96
|
+
logger.exception("registry: Error dispatching command %s: %s", command_name, exc)
|
|
94
97
|
# Send error response
|
|
95
98
|
error_payload = {"event": "error", "message": str(exc)}
|
|
96
99
|
if reply_channel:
|
|
@@ -83,11 +83,46 @@ class TerminalSession:
|
|
|
83
83
|
logger.warning("Failed to write to terminal %s: %s", self.id, exc)
|
|
84
84
|
|
|
85
85
|
async def stop(self) -> None:
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
86
|
+
"""Stop the terminal session with comprehensive logging."""
|
|
87
|
+
logger.info("session.stop: Starting stop process for session %s (PID: %s)",
|
|
88
|
+
self.id, getattr(self.proc, 'pid', 'unknown'))
|
|
89
|
+
|
|
90
|
+
try:
|
|
91
|
+
# Check if process is still running
|
|
92
|
+
if self.proc.returncode is None:
|
|
93
|
+
logger.info("session.stop: Terminating process for session %s", self.id)
|
|
94
|
+
self.proc.terminate()
|
|
95
|
+
else:
|
|
96
|
+
logger.info("session.stop: Process for session %s already exited (returncode: %s)",
|
|
97
|
+
self.id, self.proc.returncode)
|
|
98
|
+
|
|
99
|
+
# Wait for reader task to complete
|
|
100
|
+
if self._reader_task and not self._reader_task.done():
|
|
101
|
+
logger.info("session.stop: Waiting for reader task to complete for session %s", self.id)
|
|
102
|
+
try:
|
|
103
|
+
await asyncio.wait_for(self._reader_task, timeout=5.0)
|
|
104
|
+
logger.info("session.stop: Reader task completed for session %s", self.id)
|
|
105
|
+
except asyncio.TimeoutError:
|
|
106
|
+
logger.warning("session.stop: Reader task timeout for session %s, cancelling", self.id)
|
|
107
|
+
self._reader_task.cancel()
|
|
108
|
+
try:
|
|
109
|
+
await self._reader_task
|
|
110
|
+
except asyncio.CancelledError:
|
|
111
|
+
pass
|
|
112
|
+
|
|
113
|
+
# Wait for process to exit
|
|
114
|
+
if self.proc.returncode is None:
|
|
115
|
+
logger.info("session.stop: Waiting for process to exit for session %s", self.id)
|
|
116
|
+
await self.proc.wait()
|
|
117
|
+
logger.info("session.stop: Process exited for session %s (returncode: %s)",
|
|
118
|
+
self.id, self.proc.returncode)
|
|
119
|
+
else:
|
|
120
|
+
logger.info("session.stop: Process already exited for session %s (returncode: %s)",
|
|
121
|
+
self.id, self.proc.returncode)
|
|
122
|
+
|
|
123
|
+
except Exception as exc:
|
|
124
|
+
logger.exception("session.stop: Error stopping session %s: %s", self.id, exc)
|
|
125
|
+
raise
|
|
91
126
|
|
|
92
127
|
def snapshot_buffer(self) -> str:
|
|
93
128
|
"""Return concatenated last buffer contents suitable for UI."""
|
|
@@ -168,10 +203,37 @@ class WindowsTerminalSession(TerminalSession):
|
|
|
168
203
|
logger.warning("Failed to write to terminal %s: %s", self.id, exc)
|
|
169
204
|
|
|
170
205
|
async def stop(self) -> None:
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
206
|
+
"""Stop the Windows terminal session with comprehensive logging."""
|
|
207
|
+
logger.info("session.stop: Starting stop process for Windows session %s (PID: %s)",
|
|
208
|
+
self.id, getattr(self._pty, 'pid', 'unknown'))
|
|
209
|
+
|
|
210
|
+
try:
|
|
211
|
+
# Check if PTY is still alive
|
|
212
|
+
if self._pty.isalive():
|
|
213
|
+
logger.info("session.stop: Killing PTY process for session %s", self.id)
|
|
214
|
+
self._pty.kill()
|
|
215
|
+
else:
|
|
216
|
+
logger.info("session.stop: PTY process for session %s already exited", self.id)
|
|
217
|
+
|
|
218
|
+
# Wait for reader task to complete
|
|
219
|
+
if self._reader_task and not self._reader_task.done():
|
|
220
|
+
logger.info("session.stop: Waiting for reader task to complete for Windows session %s", self.id)
|
|
221
|
+
try:
|
|
222
|
+
await asyncio.wait_for(self._reader_task, timeout=5.0)
|
|
223
|
+
logger.info("session.stop: Reader task completed for Windows session %s", self.id)
|
|
224
|
+
except asyncio.TimeoutError:
|
|
225
|
+
logger.warning("session.stop: Reader task timeout for Windows session %s, cancelling", self.id)
|
|
226
|
+
self._reader_task.cancel()
|
|
227
|
+
try:
|
|
228
|
+
await self._reader_task
|
|
229
|
+
except asyncio.CancelledError:
|
|
230
|
+
pass
|
|
231
|
+
|
|
232
|
+
logger.info("session.stop: Successfully stopped Windows session %s", self.id)
|
|
233
|
+
|
|
234
|
+
except Exception as exc:
|
|
235
|
+
logger.exception("session.stop: Error stopping Windows session %s: %s", self.id, exc)
|
|
236
|
+
raise
|
|
175
237
|
|
|
176
238
|
|
|
177
239
|
class SessionManager:
|
|
@@ -270,7 +332,13 @@ class SessionManager:
|
|
|
270
332
|
|
|
271
333
|
def remove_session(self, terminal_id: str) -> Optional[TerminalSession]:
|
|
272
334
|
"""Remove and return a terminal session."""
|
|
273
|
-
|
|
335
|
+
session = self._sessions.pop(terminal_id, None)
|
|
336
|
+
if session:
|
|
337
|
+
logger.info("session_manager: Removed session %s (PID: %s) from session manager",
|
|
338
|
+
terminal_id, getattr(session.proc, 'pid', 'unknown'))
|
|
339
|
+
else:
|
|
340
|
+
logger.warning("session_manager: Attempted to remove non-existent session %s", terminal_id)
|
|
341
|
+
return session
|
|
274
342
|
|
|
275
343
|
def list_sessions(self) -> List[Dict[str, Any]]:
|
|
276
344
|
"""List all terminal sessions."""
|
{portacode-0.3.5.dev0 → portacode-0.3.7.dev0}/portacode/connection/handlers/terminal_handlers.py
RENAMED
|
@@ -100,22 +100,89 @@ class TerminalStopHandler(AsyncHandler):
|
|
|
100
100
|
terminal_id = message.get("terminal_id")
|
|
101
101
|
|
|
102
102
|
if not terminal_id:
|
|
103
|
+
logger.error("terminal_stop: Missing terminal_id in message")
|
|
103
104
|
raise ValueError("terminal_id is required")
|
|
104
105
|
|
|
106
|
+
logger.info("terminal_stop: Processing stop request for terminal_id=%s", terminal_id)
|
|
107
|
+
|
|
105
108
|
session_manager = self.context.get("session_manager")
|
|
106
109
|
if not session_manager:
|
|
110
|
+
logger.error("terminal_stop: Session manager not available in context")
|
|
107
111
|
raise RuntimeError("Session manager not available")
|
|
108
112
|
|
|
113
|
+
# Remove session from manager first
|
|
109
114
|
session = session_manager.remove_session(terminal_id)
|
|
110
115
|
if not session:
|
|
111
|
-
|
|
116
|
+
logger.warning("terminal_stop: Terminal %s not found, may have already been stopped", terminal_id)
|
|
117
|
+
return {
|
|
118
|
+
"event": "terminal_stopped",
|
|
119
|
+
"terminal_id": terminal_id,
|
|
120
|
+
"status": "not_found",
|
|
121
|
+
"message": "Terminal was not found or already stopped"
|
|
122
|
+
}
|
|
112
123
|
|
|
113
|
-
|
|
124
|
+
logger.info("terminal_stop: Found session for terminal %s (PID: %s), starting background stop process",
|
|
125
|
+
terminal_id, getattr(session.proc, 'pid', 'unknown'))
|
|
126
|
+
|
|
127
|
+
# Start stop process in background without blocking the control channel
|
|
128
|
+
asyncio.create_task(self._stop_session_safely(session, terminal_id))
|
|
114
129
|
|
|
115
130
|
return {
|
|
116
131
|
"event": "terminal_stopped",
|
|
117
132
|
"terminal_id": terminal_id,
|
|
133
|
+
"status": "stopping",
|
|
134
|
+
"message": "Terminal stop process initiated"
|
|
118
135
|
}
|
|
136
|
+
|
|
137
|
+
async def _stop_session_safely(self, session, terminal_id: str) -> None:
|
|
138
|
+
"""Safely stop a session in the background with timeout and error handling."""
|
|
139
|
+
logger.info("terminal_stop: Starting background stop process for terminal %s", terminal_id)
|
|
140
|
+
|
|
141
|
+
try:
|
|
142
|
+
# Attempt graceful stop with timeout
|
|
143
|
+
await asyncio.wait_for(session.stop(), timeout=10.0)
|
|
144
|
+
logger.info("terminal_stop: Successfully stopped terminal %s", terminal_id)
|
|
145
|
+
|
|
146
|
+
# Send success notification
|
|
147
|
+
await self.control_channel.send({
|
|
148
|
+
"event": "terminal_stop_completed",
|
|
149
|
+
"terminal_id": terminal_id,
|
|
150
|
+
"status": "success",
|
|
151
|
+
"message": "Terminal stopped successfully"
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
except asyncio.TimeoutError:
|
|
155
|
+
logger.warning("terminal_stop: Stop timeout for terminal %s, forcing kill", terminal_id)
|
|
156
|
+
|
|
157
|
+
# Force kill the process
|
|
158
|
+
try:
|
|
159
|
+
if hasattr(session.proc, 'kill'):
|
|
160
|
+
session.proc.kill()
|
|
161
|
+
logger.info("terminal_stop: Force killed terminal %s", terminal_id)
|
|
162
|
+
elif hasattr(session.proc, 'terminate'):
|
|
163
|
+
session.proc.terminate()
|
|
164
|
+
logger.info("terminal_stop: Force terminated terminal %s", terminal_id)
|
|
165
|
+
except Exception as kill_exc:
|
|
166
|
+
logger.error("terminal_stop: Failed to force kill terminal %s: %s", terminal_id, kill_exc)
|
|
167
|
+
|
|
168
|
+
# Send timeout notification
|
|
169
|
+
await self.control_channel.send({
|
|
170
|
+
"event": "terminal_stop_completed",
|
|
171
|
+
"terminal_id": terminal_id,
|
|
172
|
+
"status": "timeout",
|
|
173
|
+
"message": "Terminal stop timed out, process was force killed"
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
except Exception as exc:
|
|
177
|
+
logger.exception("terminal_stop: Error stopping terminal %s: %s", terminal_id, exc)
|
|
178
|
+
|
|
179
|
+
# Send error notification
|
|
180
|
+
await self.control_channel.send({
|
|
181
|
+
"event": "terminal_stop_completed",
|
|
182
|
+
"terminal_id": terminal_id,
|
|
183
|
+
"status": "error",
|
|
184
|
+
"message": f"Error stopping terminal: {str(exc)}"
|
|
185
|
+
})
|
|
119
186
|
|
|
120
187
|
|
|
121
188
|
class TerminalListHandler(AsyncHandler):
|
|
@@ -113,31 +113,45 @@ class TerminalManager:
|
|
|
113
113
|
# ---------------------------------------------------------------------
|
|
114
114
|
|
|
115
115
|
async def _control_loop(self) -> None:
|
|
116
|
+
logger.info("terminal_manager: Starting control loop")
|
|
116
117
|
while True:
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
118
|
+
try:
|
|
119
|
+
message = await self._control_channel.recv()
|
|
120
|
+
logger.debug("terminal_manager: Received message: %s", message)
|
|
121
|
+
|
|
122
|
+
# Older parts of the system may send *raw* str. Ensure dict.
|
|
123
|
+
if isinstance(message, str):
|
|
124
|
+
try:
|
|
125
|
+
message = json.loads(message)
|
|
126
|
+
logger.debug("terminal_manager: Parsed string message to dict")
|
|
127
|
+
except Exception:
|
|
128
|
+
logger.warning("terminal_manager: Discarding non-JSON control frame: %s", message)
|
|
129
|
+
continue
|
|
130
|
+
if not isinstance(message, dict):
|
|
131
|
+
logger.warning("terminal_manager: Invalid control frame type: %r", type(message))
|
|
124
132
|
continue
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
133
|
+
cmd = message.get("cmd")
|
|
134
|
+
if not cmd:
|
|
135
|
+
# Ignore frames that are *events* coming from the remote side
|
|
136
|
+
if message.get("event"):
|
|
137
|
+
logger.debug("terminal_manager: Ignoring event message: %s", message.get("event"))
|
|
138
|
+
continue
|
|
139
|
+
logger.warning("terminal_manager: Missing 'cmd' in control frame: %s", message)
|
|
132
140
|
continue
|
|
133
|
-
|
|
141
|
+
reply_chan = message.get("reply_channel")
|
|
142
|
+
|
|
143
|
+
logger.info("terminal_manager: Processing command '%s' with reply_channel=%s", cmd, reply_chan)
|
|
144
|
+
|
|
145
|
+
# Dispatch command through registry
|
|
146
|
+
handled = await self._command_registry.dispatch(cmd, message, reply_chan)
|
|
147
|
+
if not handled:
|
|
148
|
+
logger.warning("terminal_manager: Command '%s' was not handled by any handler", cmd)
|
|
149
|
+
await self._send_error(f"Unknown cmd: {cmd}", reply_chan)
|
|
150
|
+
|
|
151
|
+
except Exception as exc:
|
|
152
|
+
logger.exception("terminal_manager: Error in control loop: %s", exc)
|
|
153
|
+
# Continue processing other messages
|
|
134
154
|
continue
|
|
135
|
-
reply_chan = message.get("reply_channel")
|
|
136
|
-
|
|
137
|
-
# Dispatch command through registry
|
|
138
|
-
handled = await self._command_registry.dispatch(cmd, message, reply_chan)
|
|
139
|
-
if not handled:
|
|
140
|
-
await self._send_error(f"Unknown cmd: {cmd}", reply_chan)
|
|
141
155
|
|
|
142
156
|
# ------------------------------------------------------------------
|
|
143
157
|
# Extension API
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: portacode
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.7.dev0
|
|
4
4
|
Summary: Portacode CLI client and SDK
|
|
5
5
|
Home-page: https://github.com/portacode/portacode
|
|
6
6
|
Author: Meena Erian
|
|
7
|
+
Author-email: hi@menas.pro
|
|
7
8
|
Classifier: Programming Language :: Python :: 3
|
|
8
9
|
Classifier: License :: OSI Approved :: MIT License
|
|
9
10
|
Classifier: Operating System :: OS Independent
|
|
@@ -14,6 +14,7 @@ setup(
|
|
|
14
14
|
long_description=README,
|
|
15
15
|
long_description_content_type="text/markdown",
|
|
16
16
|
author="Meena Erian",
|
|
17
|
+
author_email="hi@menas.pro",
|
|
17
18
|
url="https://github.com/portacode/portacode",
|
|
18
19
|
packages=find_packages(exclude=("tests", "server")),
|
|
19
20
|
python_requires=">=3.8",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{portacode-0.3.5.dev0 → portacode-0.3.7.dev0}/portacode/connection/handlers/file_handlers.py
RENAMED
|
File without changes
|
{portacode-0.3.5.dev0 → portacode-0.3.7.dev0}/portacode/connection/handlers/system_handlers.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|