- Add `conn shell` CLI subcommand for spawning local interactive shells with AI Copilot support. - Implement `protocol="local"` on core `node` class with PTY process spawning, dynamic PWD tracking (/proc/PID/cwd + OSC 7), and AI metadata mapping. - Implement `_is_child_connpy_active()` to inspect PTY slave foreground process group (tcgetpgrp + /proc/PGID/cmdline). - Automatically pass `Ctrl+Space` (b'\x00') down to child foreground `conn` / `connpy` processes when running nested sessions in local shell. - Add `--shell-command`, `--shell-prompt`, and `--shell-os` configuration options to `conn config`. - Update `README.md` and `connpy/__init__.py` documentation to include `conn shell` and PAT API Token management. - Add comprehensive unit tests in `test_local_shell.py` and `test_completion.py`. - Bump version to v6.1.0 and regenerate full HTML documentation in `docs/` via pdoc.
56 lines
1.6 KiB
Python
56 lines
1.6 KiB
Python
import os
|
|
import sys
|
|
import shutil
|
|
import shlex
|
|
import socket
|
|
from .. import printer
|
|
from ..core import node
|
|
|
|
class ShellHandler:
|
|
def __init__(self, app):
|
|
self.app = app
|
|
|
|
def dispatch(self, args):
|
|
shell_config = self.app.config.config.get("shell", {}) if hasattr(self.app.config, "config") else {}
|
|
command = getattr(args, 'command_override', None) or shell_config.get("command") or os.environ.get("SHELL", "/bin/bash")
|
|
|
|
try:
|
|
exe = shlex.split(command)[0]
|
|
except Exception:
|
|
exe = command
|
|
|
|
if not shutil.which(exe):
|
|
printer.error(f"Shell command executable not found: {exe}")
|
|
sys.exit(1)
|
|
|
|
node_info = self._build_local_identity(shell_config)
|
|
|
|
tags = {
|
|
"os": node_info["os"],
|
|
"prompt": node_info["prompt"]
|
|
}
|
|
|
|
n = node(
|
|
unique=node_info["name"],
|
|
host=command,
|
|
protocol="local",
|
|
config=self.app.config,
|
|
tags=tags
|
|
)
|
|
|
|
capture_file = getattr(args, 'capture_file', None)
|
|
if capture_file:
|
|
n.logs = capture_file
|
|
elif shell_config.get("logging"):
|
|
n.logs = shell_config.get("log_path", os.path.expanduser("~/.config/conn/shell_logs/session.log"))
|
|
|
|
n.interact(debug=getattr(args, 'debug', False))
|
|
|
|
def _build_local_identity(self, shell_config):
|
|
return {
|
|
"name": "local-shell",
|
|
"host": socket.gethostname(),
|
|
"os": shell_config.get("os", "linux"),
|
|
"prompt": shell_config.get("prompt", r'\$\s*$|#\s*$')
|
|
}
|