portacode 0.3.1.dev0__tar.gz → 0.3.3.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.1.dev0 → portacode-0.3.3.dev0}/PKG-INFO +26 -8
- {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/README.md +25 -7
- {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/portacode/_version.py +2 -2
- {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/portacode/cli.py +5 -0
- {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/portacode/connection/handlers/session.py +100 -6
- portacode-0.3.3.dev0/portacode/connection/handlers/system_handlers.py +132 -0
- {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/portacode.egg-info/PKG-INFO +26 -8
- portacode-0.3.1.dev0/portacode/connection/handlers/system_handlers.py +0 -32
- {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/.gitignore +0 -0
- {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/.gitmodules +0 -0
- {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/LICENSE +0 -0
- {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/MANIFEST.in +0 -0
- {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/Makefile +0 -0
- {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/docker-compose.yaml +0 -0
- {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/portacode/README.md +0 -0
- {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/portacode/__init__.py +0 -0
- {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/portacode/__main__.py +0 -0
- {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/portacode/connection/README.md +0 -0
- {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/portacode/connection/__init__.py +0 -0
- {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/portacode/connection/client.py +0 -0
- {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/portacode/connection/handlers/README.md +0 -0
- {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/portacode/connection/handlers/__init__.py +0 -0
- {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/portacode/connection/handlers/base.py +0 -0
- {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/portacode/connection/handlers/file_handlers.py +0 -0
- {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/portacode/connection/handlers/registry.py +0 -0
- {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/portacode/connection/handlers/terminal_handlers.py +0 -0
- {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/portacode/connection/multiplex.py +0 -0
- {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/portacode/connection/terminal.py +0 -0
- {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/portacode/data.py +0 -0
- {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/portacode/keypair.py +0 -0
- {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/portacode/service.py +0 -0
- {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/portacode.egg-info/SOURCES.txt +0 -0
- {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/portacode.egg-info/dependency_links.txt +0 -0
- {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/portacode.egg-info/entry_points.txt +0 -0
- {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/portacode.egg-info/requires.txt +0 -0
- {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/portacode.egg-info/top_level.txt +0 -0
- {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/pyproject.toml +0 -0
- {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/setup.cfg +0 -0
- {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: portacode
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.3.dev0
|
|
4
4
|
Summary: Portacode CLI client and SDK
|
|
5
5
|
Home-page: https://github.com/portacode/portacode
|
|
6
6
|
Author: Meena Erian
|
|
@@ -80,8 +80,11 @@ portacode connect
|
|
|
80
80
|
|
|
81
81
|
### Server Administration
|
|
82
82
|
```bash
|
|
83
|
-
#
|
|
84
|
-
|
|
83
|
+
# For a persistent connection, install system-wide first
|
|
84
|
+
sudo pip install portacode --system
|
|
85
|
+
|
|
86
|
+
# Then install as a service
|
|
87
|
+
sudo portacode service install
|
|
85
88
|
|
|
86
89
|
# Your server is now accessible 24/7 from the web dashboard
|
|
87
90
|
```
|
|
@@ -112,23 +115,29 @@ portacode connect
|
|
|
112
115
|
# Run connection in background
|
|
113
116
|
portacode connect --detach
|
|
114
117
|
|
|
118
|
+
# Check version
|
|
119
|
+
portacode --version
|
|
120
|
+
|
|
115
121
|
# Get help
|
|
116
122
|
portacode --help
|
|
117
123
|
```
|
|
118
124
|
|
|
119
125
|
### Service Management
|
|
120
126
|
```bash
|
|
127
|
+
# For system services, install package system-wide first
|
|
128
|
+
sudo pip install portacode --system
|
|
129
|
+
|
|
121
130
|
# Install persistent service (auto-start on boot)
|
|
122
|
-
portacode service install
|
|
131
|
+
sudo portacode service install
|
|
123
132
|
|
|
124
133
|
# Check service status
|
|
125
|
-
portacode service status
|
|
134
|
+
sudo portacode service status
|
|
126
135
|
|
|
127
136
|
# Stop the service
|
|
128
|
-
portacode service stop
|
|
137
|
+
sudo portacode service stop
|
|
129
138
|
|
|
130
139
|
# Remove the service
|
|
131
|
-
portacode service uninstall
|
|
140
|
+
sudo portacode service uninstall
|
|
132
141
|
```
|
|
133
142
|
|
|
134
143
|
## 🌐 Web Dashboard
|
|
@@ -164,7 +173,16 @@ Access your connected devices at [https://remote.portacode.com](https://remote.p
|
|
|
164
173
|
portacode connect
|
|
165
174
|
|
|
166
175
|
# View service logs
|
|
167
|
-
portacode service status --verbose
|
|
176
|
+
sudo portacode service status --verbose
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Service Installation Issues
|
|
180
|
+
```bash
|
|
181
|
+
# If service commands fail, ensure system-wide installation
|
|
182
|
+
sudo pip install portacode --system
|
|
183
|
+
|
|
184
|
+
# Then try service installation again
|
|
185
|
+
sudo portacode service install
|
|
168
186
|
```
|
|
169
187
|
|
|
170
188
|
### Clipboard Issues (Linux)
|
|
@@ -55,8 +55,11 @@ portacode connect
|
|
|
55
55
|
|
|
56
56
|
### Server Administration
|
|
57
57
|
```bash
|
|
58
|
-
#
|
|
59
|
-
|
|
58
|
+
# For a persistent connection, install system-wide first
|
|
59
|
+
sudo pip install portacode --system
|
|
60
|
+
|
|
61
|
+
# Then install as a service
|
|
62
|
+
sudo portacode service install
|
|
60
63
|
|
|
61
64
|
# Your server is now accessible 24/7 from the web dashboard
|
|
62
65
|
```
|
|
@@ -87,23 +90,29 @@ portacode connect
|
|
|
87
90
|
# Run connection in background
|
|
88
91
|
portacode connect --detach
|
|
89
92
|
|
|
93
|
+
# Check version
|
|
94
|
+
portacode --version
|
|
95
|
+
|
|
90
96
|
# Get help
|
|
91
97
|
portacode --help
|
|
92
98
|
```
|
|
93
99
|
|
|
94
100
|
### Service Management
|
|
95
101
|
```bash
|
|
102
|
+
# For system services, install package system-wide first
|
|
103
|
+
sudo pip install portacode --system
|
|
104
|
+
|
|
96
105
|
# Install persistent service (auto-start on boot)
|
|
97
|
-
portacode service install
|
|
106
|
+
sudo portacode service install
|
|
98
107
|
|
|
99
108
|
# Check service status
|
|
100
|
-
portacode service status
|
|
109
|
+
sudo portacode service status
|
|
101
110
|
|
|
102
111
|
# Stop the service
|
|
103
|
-
portacode service stop
|
|
112
|
+
sudo portacode service stop
|
|
104
113
|
|
|
105
114
|
# Remove the service
|
|
106
|
-
portacode service uninstall
|
|
115
|
+
sudo portacode service uninstall
|
|
107
116
|
```
|
|
108
117
|
|
|
109
118
|
## 🌐 Web Dashboard
|
|
@@ -139,7 +148,16 @@ Access your connected devices at [https://remote.portacode.com](https://remote.p
|
|
|
139
148
|
portacode connect
|
|
140
149
|
|
|
141
150
|
# View service logs
|
|
142
|
-
portacode service status --verbose
|
|
151
|
+
sudo portacode service status --verbose
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Service Installation Issues
|
|
155
|
+
```bash
|
|
156
|
+
# If service commands fail, ensure system-wide installation
|
|
157
|
+
sudo pip install portacode --system
|
|
158
|
+
|
|
159
|
+
# Then try service installation again
|
|
160
|
+
sudo portacode service install
|
|
143
161
|
```
|
|
144
162
|
|
|
145
163
|
### Clipboard Issues (Linux)
|
|
@@ -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.3.dev'
|
|
21
|
+
__version_tuple__ = version_tuple = (0, 3, 3, 'dev0')
|
|
@@ -11,6 +11,7 @@ import json
|
|
|
11
11
|
import click
|
|
12
12
|
import pyperclip
|
|
13
13
|
|
|
14
|
+
from . import __version__
|
|
14
15
|
from .data import get_pid_file, is_process_running
|
|
15
16
|
from .keypair import get_or_create_keypair, fingerprint_public_key
|
|
16
17
|
from .connection.client import ConnectionManager, run_until_interrupt
|
|
@@ -20,6 +21,7 @@ GATEWAY_ENV = "PORTACODE_GATEWAY"
|
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
@click.group()
|
|
24
|
+
@click.version_option(__version__, "-v", "--version", message="Portacode %(version)s")
|
|
23
25
|
def cli() -> None:
|
|
24
26
|
"""Portacode command-line interface."""
|
|
25
27
|
|
|
@@ -289,6 +291,7 @@ def service_install() -> None: # noqa: D401
|
|
|
289
291
|
click.echo(f"Installing Portacode system service…")
|
|
290
292
|
if os.geteuid() != 0:
|
|
291
293
|
click.echo(click.style("[sudo] You may be prompted for your password to install the system service.", fg="yellow"))
|
|
294
|
+
click.echo(click.style("💡 For persistent connection, install system-wide: sudo pip install portacode --system", fg="bright_black"))
|
|
292
295
|
try:
|
|
293
296
|
mgr.install()
|
|
294
297
|
st = mgr.status()
|
|
@@ -300,6 +303,8 @@ def service_install() -> None: # noqa: D401
|
|
|
300
303
|
click.echo(f"Inspect log: {mgr.log_path}")
|
|
301
304
|
except Exception as exc:
|
|
302
305
|
click.echo(click.style(f"Failed: {exc}", fg="red"))
|
|
306
|
+
if "No module named" in str(exc) or "command not found" in str(exc):
|
|
307
|
+
click.echo(click.style("💡 Try installing system-wide: sudo pip install portacode --system", fg="bright_cyan"))
|
|
303
308
|
|
|
304
309
|
|
|
305
310
|
@service.command("uninstall")
|
|
@@ -10,6 +10,14 @@ from pathlib import Path
|
|
|
10
10
|
from typing import Any, Dict, Optional, List, TYPE_CHECKING
|
|
11
11
|
from collections import deque
|
|
12
12
|
|
|
13
|
+
# Terminal control imports (only available on Unix)
|
|
14
|
+
try:
|
|
15
|
+
import termios
|
|
16
|
+
import tty
|
|
17
|
+
_HAS_TERMIOS = True
|
|
18
|
+
except ImportError:
|
|
19
|
+
_HAS_TERMIOS = False
|
|
20
|
+
|
|
13
21
|
if TYPE_CHECKING:
|
|
14
22
|
from ..multiplex import Channel
|
|
15
23
|
|
|
@@ -21,6 +29,15 @@ _IS_WINDOWS = sys.platform.startswith("win")
|
|
|
21
29
|
_DEFAULT_ENV = {
|
|
22
30
|
"TERM": "xterm-256color",
|
|
23
31
|
"LANG": "C.UTF-8",
|
|
32
|
+
# Add additional terminal environment variables for better compatibility
|
|
33
|
+
"COLUMNS": "80",
|
|
34
|
+
"LINES": "24",
|
|
35
|
+
"PS1": r'\u@\h:\w\$ ', # Standard bash prompt
|
|
36
|
+
"HISTSIZE": "1000",
|
|
37
|
+
"HISTFILESIZE": "2000",
|
|
38
|
+
# Ensure proper terminal behavior
|
|
39
|
+
"DEBIAN_FRONTEND": "noninteractive", # Prevent interactive prompts
|
|
40
|
+
"TERM_PROGRAM": "portacode",
|
|
24
41
|
}
|
|
25
42
|
|
|
26
43
|
|
|
@@ -29,6 +46,12 @@ def _build_child_env() -> Dict[str, str]:
|
|
|
29
46
|
env = os.environ.copy()
|
|
30
47
|
for k, v in _DEFAULT_ENV.items():
|
|
31
48
|
env.setdefault(k, v)
|
|
49
|
+
|
|
50
|
+
# Ensure SSH_TTY is not set when using pipes to avoid shell confusion
|
|
51
|
+
if "SSH_TTY" in env:
|
|
52
|
+
logger.debug("Removing SSH_TTY from environment to prevent shell confusion")
|
|
53
|
+
del env["SSH_TTY"]
|
|
54
|
+
|
|
32
55
|
return env
|
|
33
56
|
|
|
34
57
|
|
|
@@ -190,9 +213,21 @@ class SessionManager:
|
|
|
190
213
|
channel_id = self._allocate_channel_id()
|
|
191
214
|
channel = self.mux.get_channel(channel_id)
|
|
192
215
|
|
|
193
|
-
# Choose shell
|
|
216
|
+
# Choose shell - prefer bash over sh for better compatibility
|
|
194
217
|
if shell is None:
|
|
195
|
-
|
|
218
|
+
if not _IS_WINDOWS:
|
|
219
|
+
# Try to use bash if available, fallback to default shell
|
|
220
|
+
shell = os.getenv("SHELL")
|
|
221
|
+
if shell is None or shell == "/bin/sh":
|
|
222
|
+
# Try to find bash
|
|
223
|
+
for bash_path in ["/bin/bash", "/usr/bin/bash", "/usr/local/bin/bash"]:
|
|
224
|
+
if os.path.exists(bash_path):
|
|
225
|
+
shell = bash_path
|
|
226
|
+
break
|
|
227
|
+
else:
|
|
228
|
+
shell = "/bin/sh"
|
|
229
|
+
else:
|
|
230
|
+
shell = os.getenv("COMSPEC", "cmd.exe")
|
|
196
231
|
|
|
197
232
|
logger.info("Launching terminal %s using shell=%s on channel=%s", term_id, shell, channel_id)
|
|
198
233
|
|
|
@@ -207,9 +242,30 @@ class SessionManager:
|
|
|
207
242
|
session = WindowsTerminalSession(term_id, pty_proc, channel)
|
|
208
243
|
else:
|
|
209
244
|
# Unix: try real PTY for proper TTY semantics
|
|
245
|
+
pty_success = False
|
|
210
246
|
try:
|
|
211
247
|
import pty
|
|
248
|
+
|
|
249
|
+
logger.debug("Attempting to allocate PTY for terminal %s", term_id)
|
|
212
250
|
master_fd, slave_fd = pty.openpty()
|
|
251
|
+
|
|
252
|
+
# Set terminal attributes for better compatibility
|
|
253
|
+
if _HAS_TERMIOS:
|
|
254
|
+
try:
|
|
255
|
+
# Get current terminal settings
|
|
256
|
+
attrs = termios.tcgetattr(slave_fd)
|
|
257
|
+
# Enable canonical mode and echo
|
|
258
|
+
attrs[3] |= termios.ICANON | termios.ECHO | termios.ECHOE | termios.ECHOK
|
|
259
|
+
# Set input and output modes
|
|
260
|
+
attrs[0] |= termios.ICRNL # Map CR to NL on input
|
|
261
|
+
attrs[1] |= termios.OPOST | termios.ONLCR # Map NL to CR-NL on output
|
|
262
|
+
termios.tcsetattr(slave_fd, termios.TCSANOW, attrs)
|
|
263
|
+
logger.debug("Successfully configured terminal attributes")
|
|
264
|
+
except Exception as e:
|
|
265
|
+
logger.warning("Failed to configure terminal attributes: %s", e)
|
|
266
|
+
else:
|
|
267
|
+
logger.debug("termios not available, skipping terminal attribute configuration")
|
|
268
|
+
|
|
213
269
|
proc = await asyncio.create_subprocess_exec(
|
|
214
270
|
shell,
|
|
215
271
|
stdin=slave_fd,
|
|
@@ -219,6 +275,10 @@ class SessionManager:
|
|
|
219
275
|
cwd=cwd,
|
|
220
276
|
env=_build_child_env(),
|
|
221
277
|
)
|
|
278
|
+
|
|
279
|
+
# Close slave_fd in parent process
|
|
280
|
+
os.close(slave_fd)
|
|
281
|
+
|
|
222
282
|
# Wrap master_fd into a StreamReader
|
|
223
283
|
loop = asyncio.get_running_loop()
|
|
224
284
|
reader = asyncio.StreamReader()
|
|
@@ -230,16 +290,49 @@ class SessionManager:
|
|
|
230
290
|
lambda: asyncio.Protocol(), os.fdopen(master_fd, "wb", buffering=0)
|
|
231
291
|
)
|
|
232
292
|
proc.stdin = asyncio.StreamWriter(writer_transport, writer_protocol, reader, loop)
|
|
233
|
-
|
|
234
|
-
|
|
293
|
+
|
|
294
|
+
pty_success = True
|
|
295
|
+
logger.info("Successfully allocated PTY for terminal %s", term_id)
|
|
296
|
+
|
|
297
|
+
except Exception as e:
|
|
298
|
+
logger.warning("Failed to allocate PTY for terminal %s: %s", term_id, e)
|
|
299
|
+
|
|
300
|
+
# Enhanced fallback with proper shell invocation
|
|
301
|
+
logger.info("Using enhanced pipe fallback for terminal %s", term_id)
|
|
302
|
+
|
|
303
|
+
# Create enhanced environment for pipe mode
|
|
304
|
+
pipe_env = _build_child_env()
|
|
305
|
+
pipe_env["TERM"] = "dumb" # Use dumb terminal for pipes
|
|
306
|
+
pipe_env["PS1"] = "$ " # Simple prompt for pipes
|
|
307
|
+
|
|
308
|
+
# Use shell with explicit interactive and login flags
|
|
309
|
+
shell_args = [shell]
|
|
310
|
+
if shell.endswith("bash"):
|
|
311
|
+
shell_args.extend(["-i", "-l"]) # Interactive and login shell
|
|
312
|
+
elif shell.endswith("sh"):
|
|
313
|
+
shell_args.extend(["-i"]) # Interactive shell
|
|
314
|
+
|
|
235
315
|
proc = await asyncio.create_subprocess_exec(
|
|
236
|
-
|
|
316
|
+
*shell_args,
|
|
237
317
|
stdin=asyncio.subprocess.PIPE,
|
|
238
318
|
stdout=asyncio.subprocess.PIPE,
|
|
239
319
|
stderr=asyncio.subprocess.STDOUT,
|
|
240
320
|
cwd=cwd,
|
|
241
|
-
env=
|
|
321
|
+
env=pipe_env,
|
|
242
322
|
)
|
|
323
|
+
|
|
324
|
+
# Send initial command to set up better terminal behavior
|
|
325
|
+
if proc.stdin:
|
|
326
|
+
initial_setup = (
|
|
327
|
+
"stty -echo 2>/dev/null || true\n" # Disable echo to prevent double output
|
|
328
|
+
"set +o posix 2>/dev/null || true\n" # Disable POSIX mode if possible
|
|
329
|
+
"PS1='$ '\n" # Set simple prompt
|
|
330
|
+
"export PS1\n"
|
|
331
|
+
"clear 2>/dev/null || echo 'Terminal ready'\n"
|
|
332
|
+
)
|
|
333
|
+
proc.stdin.write(initial_setup.encode())
|
|
334
|
+
await proc.stdin.drain()
|
|
335
|
+
|
|
243
336
|
session = TerminalSession(term_id, proc, channel)
|
|
244
337
|
|
|
245
338
|
self._sessions[term_id] = session
|
|
@@ -251,6 +344,7 @@ class SessionManager:
|
|
|
251
344
|
"pid": session.proc.pid,
|
|
252
345
|
"shell": shell,
|
|
253
346
|
"cwd": cwd,
|
|
347
|
+
"pty_mode": pty_success if not _IS_WINDOWS else True,
|
|
254
348
|
}
|
|
255
349
|
|
|
256
350
|
def get_session(self, terminal_id: str) -> Optional[TerminalSession]:
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""System command handlers."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import os
|
|
5
|
+
import platform
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any, Dict
|
|
8
|
+
|
|
9
|
+
import psutil
|
|
10
|
+
|
|
11
|
+
from .base import SyncHandler
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _get_os_info() -> Dict[str, Any]:
|
|
17
|
+
"""Get operating system information with robust error handling."""
|
|
18
|
+
try:
|
|
19
|
+
system = platform.system()
|
|
20
|
+
logger.debug("Detected system: %s", system)
|
|
21
|
+
|
|
22
|
+
if system == "Linux":
|
|
23
|
+
os_type = "Linux"
|
|
24
|
+
default_shell = os.environ.get('SHELL', '/bin/bash')
|
|
25
|
+
default_cwd = os.path.expanduser('~')
|
|
26
|
+
|
|
27
|
+
# Try to get more specific Linux distribution info
|
|
28
|
+
try:
|
|
29
|
+
import distro
|
|
30
|
+
os_version = f"{distro.name()} {distro.version()}"
|
|
31
|
+
logger.debug("Using distro package for OS version: %s", os_version)
|
|
32
|
+
except ImportError:
|
|
33
|
+
logger.debug("distro package not available, trying /etc/os-release")
|
|
34
|
+
# Fallback to basic platform info
|
|
35
|
+
try:
|
|
36
|
+
with open('/etc/os-release', 'r') as f:
|
|
37
|
+
for line in f:
|
|
38
|
+
if line.startswith('PRETTY_NAME='):
|
|
39
|
+
os_version = line.split('=')[1].strip().strip('"')
|
|
40
|
+
logger.debug("Found OS version from /etc/os-release: %s", os_version)
|
|
41
|
+
break
|
|
42
|
+
else:
|
|
43
|
+
os_version = f"{system} {platform.release()}"
|
|
44
|
+
logger.debug("Using platform.release() for OS version: %s", os_version)
|
|
45
|
+
except FileNotFoundError:
|
|
46
|
+
os_version = f"{system} {platform.release()}"
|
|
47
|
+
logger.debug("Using platform.release() fallback for OS version: %s", os_version)
|
|
48
|
+
|
|
49
|
+
elif system == "Darwin": # macOS
|
|
50
|
+
os_type = "macOS"
|
|
51
|
+
os_version = f"macOS {platform.mac_ver()[0]}"
|
|
52
|
+
default_shell = os.environ.get('SHELL', '/bin/bash')
|
|
53
|
+
default_cwd = os.path.expanduser('~')
|
|
54
|
+
|
|
55
|
+
elif system == "Windows":
|
|
56
|
+
os_type = "Windows"
|
|
57
|
+
os_version = f"{platform.system()} {platform.release()}"
|
|
58
|
+
default_shell = os.environ.get('COMSPEC', 'cmd.exe')
|
|
59
|
+
default_cwd = os.path.expanduser('~')
|
|
60
|
+
|
|
61
|
+
else:
|
|
62
|
+
os_type = system
|
|
63
|
+
os_version = f"{system} {platform.release()}"
|
|
64
|
+
default_shell = "/bin/sh" # Safe fallback
|
|
65
|
+
default_cwd = os.path.expanduser('~')
|
|
66
|
+
|
|
67
|
+
result = {
|
|
68
|
+
"os_type": os_type,
|
|
69
|
+
"os_version": os_version,
|
|
70
|
+
"architecture": platform.machine(),
|
|
71
|
+
"default_shell": default_shell,
|
|
72
|
+
"default_cwd": default_cwd,
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
logger.debug("Successfully collected OS info: %s", result)
|
|
76
|
+
return result
|
|
77
|
+
|
|
78
|
+
except Exception as e:
|
|
79
|
+
logger.error("Failed to collect OS info: %s", e, exc_info=True)
|
|
80
|
+
# Return minimal fallback info instead of failing completely
|
|
81
|
+
return {
|
|
82
|
+
"os_type": "Unknown",
|
|
83
|
+
"os_version": "Unknown",
|
|
84
|
+
"architecture": platform.machine() if hasattr(platform, 'machine') else "Unknown",
|
|
85
|
+
"default_shell": "/bin/bash", # Safe fallback
|
|
86
|
+
"default_cwd": os.path.expanduser('~') if hasattr(os.path, 'expanduser') else "",
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class SystemInfoHandler(SyncHandler):
|
|
91
|
+
"""Handler for getting system information."""
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def command_name(self) -> str:
|
|
95
|
+
return "system_info"
|
|
96
|
+
|
|
97
|
+
def execute(self, message: Dict[str, Any]) -> Dict[str, Any]:
|
|
98
|
+
"""Get system information including OS details."""
|
|
99
|
+
logger.debug("Collecting system information...")
|
|
100
|
+
|
|
101
|
+
# Collect basic system metrics
|
|
102
|
+
info = {}
|
|
103
|
+
|
|
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
|
|
110
|
+
|
|
111
|
+
try:
|
|
112
|
+
info["memory"] = psutil.virtual_memory()._asdict()
|
|
113
|
+
logger.debug("Memory usage: %s%%", info["memory"].get("percent", "N/A"))
|
|
114
|
+
except Exception as e:
|
|
115
|
+
logger.warning("Failed to get memory info: %s", e)
|
|
116
|
+
info["memory"] = {"percent": 0.0}
|
|
117
|
+
|
|
118
|
+
try:
|
|
119
|
+
info["disk"] = psutil.disk_usage(str(Path.home()))._asdict()
|
|
120
|
+
logger.debug("Disk usage: %s%%", info["disk"].get("percent", "N/A"))
|
|
121
|
+
except Exception as e:
|
|
122
|
+
logger.warning("Failed to get disk info: %s", e)
|
|
123
|
+
info["disk"] = {"percent": 0.0}
|
|
124
|
+
|
|
125
|
+
# Add OS information - this is critical for proper shell detection
|
|
126
|
+
info["os_info"] = _get_os_info()
|
|
127
|
+
logger.info("System info collected successfully with OS info: %s", info.get("os_info", {}).get("os_type", "Unknown"))
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
"event": "system_info",
|
|
131
|
+
"info": info,
|
|
132
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: portacode
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.3.dev0
|
|
4
4
|
Summary: Portacode CLI client and SDK
|
|
5
5
|
Home-page: https://github.com/portacode/portacode
|
|
6
6
|
Author: Meena Erian
|
|
@@ -80,8 +80,11 @@ portacode connect
|
|
|
80
80
|
|
|
81
81
|
### Server Administration
|
|
82
82
|
```bash
|
|
83
|
-
#
|
|
84
|
-
|
|
83
|
+
# For a persistent connection, install system-wide first
|
|
84
|
+
sudo pip install portacode --system
|
|
85
|
+
|
|
86
|
+
# Then install as a service
|
|
87
|
+
sudo portacode service install
|
|
85
88
|
|
|
86
89
|
# Your server is now accessible 24/7 from the web dashboard
|
|
87
90
|
```
|
|
@@ -112,23 +115,29 @@ portacode connect
|
|
|
112
115
|
# Run connection in background
|
|
113
116
|
portacode connect --detach
|
|
114
117
|
|
|
118
|
+
# Check version
|
|
119
|
+
portacode --version
|
|
120
|
+
|
|
115
121
|
# Get help
|
|
116
122
|
portacode --help
|
|
117
123
|
```
|
|
118
124
|
|
|
119
125
|
### Service Management
|
|
120
126
|
```bash
|
|
127
|
+
# For system services, install package system-wide first
|
|
128
|
+
sudo pip install portacode --system
|
|
129
|
+
|
|
121
130
|
# Install persistent service (auto-start on boot)
|
|
122
|
-
portacode service install
|
|
131
|
+
sudo portacode service install
|
|
123
132
|
|
|
124
133
|
# Check service status
|
|
125
|
-
portacode service status
|
|
134
|
+
sudo portacode service status
|
|
126
135
|
|
|
127
136
|
# Stop the service
|
|
128
|
-
portacode service stop
|
|
137
|
+
sudo portacode service stop
|
|
129
138
|
|
|
130
139
|
# Remove the service
|
|
131
|
-
portacode service uninstall
|
|
140
|
+
sudo portacode service uninstall
|
|
132
141
|
```
|
|
133
142
|
|
|
134
143
|
## 🌐 Web Dashboard
|
|
@@ -164,7 +173,16 @@ Access your connected devices at [https://remote.portacode.com](https://remote.p
|
|
|
164
173
|
portacode connect
|
|
165
174
|
|
|
166
175
|
# View service logs
|
|
167
|
-
portacode service status --verbose
|
|
176
|
+
sudo portacode service status --verbose
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Service Installation Issues
|
|
180
|
+
```bash
|
|
181
|
+
# If service commands fail, ensure system-wide installation
|
|
182
|
+
sudo pip install portacode --system
|
|
183
|
+
|
|
184
|
+
# Then try service installation again
|
|
185
|
+
sudo portacode service install
|
|
168
186
|
```
|
|
169
187
|
|
|
170
188
|
### Clipboard Issues (Linux)
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
"""System command handlers."""
|
|
2
|
-
|
|
3
|
-
import logging
|
|
4
|
-
from pathlib import Path
|
|
5
|
-
from typing import Any, Dict
|
|
6
|
-
|
|
7
|
-
import psutil
|
|
8
|
-
|
|
9
|
-
from .base import SyncHandler
|
|
10
|
-
|
|
11
|
-
logger = logging.getLogger(__name__)
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class SystemInfoHandler(SyncHandler):
|
|
15
|
-
"""Handler for getting system information."""
|
|
16
|
-
|
|
17
|
-
@property
|
|
18
|
-
def command_name(self) -> str:
|
|
19
|
-
return "system_info"
|
|
20
|
-
|
|
21
|
-
def execute(self, message: Dict[str, Any]) -> Dict[str, Any]:
|
|
22
|
-
"""Get system information."""
|
|
23
|
-
info = {
|
|
24
|
-
"cpu_percent": psutil.cpu_percent(interval=0.1),
|
|
25
|
-
"memory": psutil.virtual_memory()._asdict(),
|
|
26
|
-
"disk": psutil.disk_usage(str(Path.home()))._asdict(),
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return {
|
|
30
|
-
"event": "system_info",
|
|
31
|
-
"info": info,
|
|
32
|
-
}
|
|
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.1.dev0 → portacode-0.3.3.dev0}/portacode/connection/handlers/file_handlers.py
RENAMED
|
File without changes
|
|
File without changes
|
{portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/portacode/connection/handlers/terminal_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
|
|
File without changes
|
|
File without changes
|