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.
Files changed (39) hide show
  1. {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/PKG-INFO +26 -8
  2. {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/README.md +25 -7
  3. {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/portacode/_version.py +2 -2
  4. {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/portacode/cli.py +5 -0
  5. {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/portacode/connection/handlers/session.py +100 -6
  6. portacode-0.3.3.dev0/portacode/connection/handlers/system_handlers.py +132 -0
  7. {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/portacode.egg-info/PKG-INFO +26 -8
  8. portacode-0.3.1.dev0/portacode/connection/handlers/system_handlers.py +0 -32
  9. {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/.gitignore +0 -0
  10. {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/.gitmodules +0 -0
  11. {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/LICENSE +0 -0
  12. {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/MANIFEST.in +0 -0
  13. {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/Makefile +0 -0
  14. {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/docker-compose.yaml +0 -0
  15. {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/portacode/README.md +0 -0
  16. {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/portacode/__init__.py +0 -0
  17. {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/portacode/__main__.py +0 -0
  18. {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/portacode/connection/README.md +0 -0
  19. {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/portacode/connection/__init__.py +0 -0
  20. {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/portacode/connection/client.py +0 -0
  21. {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/portacode/connection/handlers/README.md +0 -0
  22. {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/portacode/connection/handlers/__init__.py +0 -0
  23. {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/portacode/connection/handlers/base.py +0 -0
  24. {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/portacode/connection/handlers/file_handlers.py +0 -0
  25. {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/portacode/connection/handlers/registry.py +0 -0
  26. {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/portacode/connection/handlers/terminal_handlers.py +0 -0
  27. {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/portacode/connection/multiplex.py +0 -0
  28. {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/portacode/connection/terminal.py +0 -0
  29. {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/portacode/data.py +0 -0
  30. {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/portacode/keypair.py +0 -0
  31. {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/portacode/service.py +0 -0
  32. {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/portacode.egg-info/SOURCES.txt +0 -0
  33. {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/portacode.egg-info/dependency_links.txt +0 -0
  34. {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/portacode.egg-info/entry_points.txt +0 -0
  35. {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/portacode.egg-info/requires.txt +0 -0
  36. {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/portacode.egg-info/top_level.txt +0 -0
  37. {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/pyproject.toml +0 -0
  38. {portacode-0.3.1.dev0 → portacode-0.3.3.dev0}/setup.cfg +0 -0
  39. {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.1.dev0
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
- # Install as a service for persistent connection
84
- portacode service install
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
- # Install as a service for persistent connection
59
- portacode service install
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.1.dev'
21
- __version_tuple__ = version_tuple = (0, 3, 1, 'dev0')
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
- shell = os.getenv("SHELL") if not _IS_WINDOWS else os.getenv("COMSPEC", "cmd.exe")
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
- except Exception:
234
- logger.warning("Failed to allocate PTY, falling back to pipes")
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
- shell,
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=_build_child_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.1.dev0
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
- # Install as a service for persistent connection
84
- portacode service install
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