shelldeck 0.0.1__py3-none-any.whl
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.
- shelldeck/__init__.py +0 -0
- shelldeck/__main__.py +4 -0
- shelldeck/addons/__init__.py +1 -0
- shelldeck/addons/fast_context.py +1346 -0
- shelldeck/auth.py +207 -0
- shelldeck/cli.py +587 -0
- shelldeck/db.py +794 -0
- shelldeck/gitgraph.py +150 -0
- shelldeck/integration/bash.sh +27 -0
- shelldeck/integration/shelldeck.fish +5 -0
- shelldeck/integration/shelldeck.ps1 +35 -0
- shelldeck/integration/zsh/.zshenv +6 -0
- shelldeck/integration/zsh/.zshrc +10 -0
- shelldeck/pty.py +352 -0
- shelldeck/runner.py +75 -0
- shelldeck/scheduler.py +283 -0
- shelldeck/server.py +1216 -0
- shelldeck/shells.py +174 -0
- shelldeck/static/app.css +2288 -0
- shelldeck/static/app.js +1863 -0
- shelldeck/static/gitgraph.js +142 -0
- shelldeck/static/history.js +172 -0
- shelldeck/static/icon-192.png +0 -0
- shelldeck/static/icon-32.png +0 -0
- shelldeck/static/icon-512.png +0 -0
- shelldeck/static/icon.svg +1 -0
- shelldeck/static/index.html +102 -0
- shelldeck/static/manifest.webmanifest +1 -0
- shelldeck/static/monitor.js +143 -0
- shelldeck/static/ui.js +316 -0
- shelldeck/static/vendor/LICENSE-xterm.txt +21 -0
- shelldeck/static/vendor/addon-fit.js +2 -0
- shelldeck/static/vendor/addon-search.js +2 -0
- shelldeck/static/vendor/addon-serialize.js +2 -0
- shelldeck/static/vendor/addon-web-links.js +2 -0
- shelldeck/static/vendor/addon-webgl.js +2 -0
- shelldeck/static/vendor/xterm.css +218 -0
- shelldeck/static/vendor/xterm.js +2 -0
- shelldeck/static/views.js +658 -0
- shelldeck/stats.py +128 -0
- shelldeck-0.0.1.dist-info/METADATA +254 -0
- shelldeck-0.0.1.dist-info/RECORD +45 -0
- shelldeck-0.0.1.dist-info/WHEEL +4 -0
- shelldeck-0.0.1.dist-info/entry_points.txt +3 -0
- shelldeck-0.0.1.dist-info/licenses/LICENSE +21 -0
shelldeck/shells.py
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""Shell kinds per OS and how to launch them.
|
|
2
|
+
|
|
3
|
+
Windows: pwsh (default), powershell, cmd, gitbash, wsl.
|
|
4
|
+
Linux/macOS: login shells from /etc/shells (bash, zsh, fish, ...); default = $SHELL.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import base64
|
|
8
|
+
import os
|
|
9
|
+
import shlex
|
|
10
|
+
import shutil
|
|
11
|
+
import subprocess
|
|
12
|
+
import sys
|
|
13
|
+
from functools import cache
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
WINDOWS = sys.platform == "win32"
|
|
17
|
+
WIN_KINDS = ("pwsh", "powershell", "cmd", "gitbash", "wsl")
|
|
18
|
+
# /etc/shells entries that are not interactive shells
|
|
19
|
+
_NOT_SHELLS = {"rbash", "tmux", "screen", "nologin", "false", "git-shell"}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _git_bash() -> str | None:
|
|
23
|
+
for base in (os.environ.get("PROGRAMFILES"), os.environ.get("LOCALAPPDATA")):
|
|
24
|
+
if base:
|
|
25
|
+
for p in (Path(base) / "Git/bin/bash.exe", Path(base) / "Programs/Git/bin/bash.exe"):
|
|
26
|
+
if p.exists():
|
|
27
|
+
return str(p)
|
|
28
|
+
return None
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@cache
|
|
32
|
+
def _posix_shells() -> dict[str, str]:
|
|
33
|
+
"""name -> path, from /etc/shells plus $SHELL, preferring common shells first."""
|
|
34
|
+
found: dict[str, str] = {}
|
|
35
|
+
candidates = [os.environ.get("SHELL", "")]
|
|
36
|
+
try:
|
|
37
|
+
candidates += Path("/etc/shells").read_text().splitlines()
|
|
38
|
+
except OSError:
|
|
39
|
+
pass
|
|
40
|
+
candidates += ["/bin/bash", "/bin/sh"]
|
|
41
|
+
for line in candidates:
|
|
42
|
+
line = line.strip()
|
|
43
|
+
name = os.path.basename(line)
|
|
44
|
+
if not line or line.startswith("#") or name in _NOT_SHELLS or name in found:
|
|
45
|
+
continue
|
|
46
|
+
if os.access(line, os.X_OK):
|
|
47
|
+
found[name] = line
|
|
48
|
+
order = ["bash", "zsh", "fish"]
|
|
49
|
+
return dict(sorted(found.items(), key=lambda kv: (kv[0] not in order, order.index(kv[0]) if kv[0] in order else 0, kv[0])))
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def kinds() -> tuple[str, ...]:
|
|
53
|
+
return WIN_KINDS if WINDOWS else tuple(_posix_shells())
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def default_kind() -> str:
|
|
57
|
+
if WINDOWS:
|
|
58
|
+
return "pwsh"
|
|
59
|
+
user = os.path.basename(os.environ.get("SHELL", ""))
|
|
60
|
+
names = _posix_shells()
|
|
61
|
+
return user if user in names else next(iter(names), "sh")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def exe(kind: str) -> str | None:
|
|
65
|
+
if not WINDOWS:
|
|
66
|
+
return _posix_shells().get(kind)
|
|
67
|
+
if kind == "pwsh":
|
|
68
|
+
return shutil.which("pwsh") or shutil.which("powershell")
|
|
69
|
+
if kind == "powershell":
|
|
70
|
+
return shutil.which("powershell")
|
|
71
|
+
if kind == "cmd":
|
|
72
|
+
return os.environ.get("COMSPEC") or shutil.which("cmd")
|
|
73
|
+
if kind == "gitbash":
|
|
74
|
+
return _git_bash()
|
|
75
|
+
if kind == "wsl":
|
|
76
|
+
return shutil.which("wsl")
|
|
77
|
+
return None
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def available() -> list[str]:
|
|
81
|
+
return [k for k in kinds() if exe(k)]
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def wsl_distros() -> list[str]:
|
|
85
|
+
if not WINDOWS or not exe("wsl"):
|
|
86
|
+
return []
|
|
87
|
+
try:
|
|
88
|
+
out = subprocess.run(["wsl.exe", "-l", "-q"], capture_output=True, timeout=5, check=False).stdout
|
|
89
|
+
except (OSError, subprocess.TimeoutExpired):
|
|
90
|
+
return []
|
|
91
|
+
# wsl.exe prints UTF-16LE
|
|
92
|
+
text = out.decode("utf-16-le", errors="ignore").replace("\x00", "")
|
|
93
|
+
return [line.strip() for line in text.splitlines() if line.strip()]
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _wsl_prefix(distro: str | None, cwd: str | None) -> list[str]:
|
|
97
|
+
argv = [exe("wsl") or "wsl.exe"]
|
|
98
|
+
if distro:
|
|
99
|
+
argv += ["-d", distro]
|
|
100
|
+
if cwd:
|
|
101
|
+
argv += ["--cd", cwd]
|
|
102
|
+
return argv
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _fallback() -> str:
|
|
106
|
+
return exe(default_kind()) or ("cmd.exe" if WINDOWS else "/bin/sh")
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def interactive_argv(kind: str, distro: str | None = None, cwd: str | None = None) -> list[str]:
|
|
110
|
+
"""argv for an interactive shell. Unknown non-empty kind = legacy literal command line."""
|
|
111
|
+
kind = kind or default_kind()
|
|
112
|
+
if kind == "wsl" and WINDOWS:
|
|
113
|
+
return _wsl_prefix(distro, cwd)
|
|
114
|
+
if kind in kinds():
|
|
115
|
+
path = exe(kind) or _fallback()
|
|
116
|
+
if kind in ("pwsh", "powershell"):
|
|
117
|
+
return [path, "-NoLogo"]
|
|
118
|
+
if kind == "gitbash":
|
|
119
|
+
return [path, "--login", "-i"]
|
|
120
|
+
return [path]
|
|
121
|
+
return shlex.split(kind, posix=not WINDOWS) or [_fallback()]
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
INTEGRATION = Path(__file__).parent / "integration"
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def with_integration(argv: list[str]) -> tuple[list[str], dict[str, str]]:
|
|
128
|
+
"""Add shell integration (prompt/exit code/cwd OSC reports) to an interactive argv.
|
|
129
|
+
|
|
130
|
+
Returns the new argv and extra environment. Unknown shells (and WSL) pass through."""
|
|
131
|
+
name = os.path.basename(argv[0]).lower().removesuffix(".exe")
|
|
132
|
+
rest = argv[1:]
|
|
133
|
+
if name in ("pwsh", "powershell"):
|
|
134
|
+
# inline, not a script path: execution policy can block .ps1 files but not -EncodedCommand
|
|
135
|
+
code = base64.b64encode((INTEGRATION / "shelldeck.ps1").read_text(encoding="utf-8").encode("utf-16-le")).decode()
|
|
136
|
+
return [argv[0], *rest, "-NoExit", "-EncodedCommand", code], {}
|
|
137
|
+
if name == "cmd":
|
|
138
|
+
prompt = os.environ.get("PROMPT") or "$P$G"
|
|
139
|
+
mark = lambda s: f"$E]{s}$E\\" # noqa: E731
|
|
140
|
+
return argv, {"PROMPT": mark("633;P;Cwd=$P") + mark("133;D") + mark("133;A") + prompt + mark("133;B")}
|
|
141
|
+
if name == "bash":
|
|
142
|
+
env = {"SHELLDECK_LOGIN": "1"} if {"--login", "-l"} & set(rest) else {}
|
|
143
|
+
rest = [a for a in rest if a not in ("--login", "-l", "-i")]
|
|
144
|
+
rc = str(INTEGRATION / "bash.sh").replace("\\", "/")
|
|
145
|
+
return [argv[0], "--rcfile", rc, "-i", *rest], env
|
|
146
|
+
if name == "zsh":
|
|
147
|
+
env = {"ZDOTDIR": str(INTEGRATION / "zsh")}
|
|
148
|
+
if os.environ.get("ZDOTDIR"):
|
|
149
|
+
env["SHELLDECK_USER_ZDOTDIR"] = os.environ["ZDOTDIR"]
|
|
150
|
+
return argv, env
|
|
151
|
+
if name == "fish":
|
|
152
|
+
return [*argv, "-C", f"source '{INTEGRATION / 'shelldeck.fish'}'"], {}
|
|
153
|
+
return argv, {}
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def command_argv(kind: str, command: str, distro: str | None = None, cwd: str | None = None) -> list[str]:
|
|
157
|
+
"""argv that runs one command non-interactively and exits."""
|
|
158
|
+
kind = kind if kind in kinds() else default_kind()
|
|
159
|
+
if kind == "wsl" and WINDOWS:
|
|
160
|
+
return [*_wsl_prefix(distro, cwd), "--", "sh", "-lc", command]
|
|
161
|
+
path = exe(kind) or _fallback()
|
|
162
|
+
base = os.path.basename(path).lower()
|
|
163
|
+
if base.startswith("cmd"):
|
|
164
|
+
return [path, "/c", command]
|
|
165
|
+
if kind in ("pwsh", "powershell"):
|
|
166
|
+
return [path, "-NoProfile", "-NonInteractive", "-Command", command]
|
|
167
|
+
return [path, "-lc", command]
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
LABELS = {"pwsh": "pwsh", "powershell": "PowerShell", "cmd": "cmd", "gitbash": "Git Bash", "wsl": "WSL"}
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def label(kind: str) -> str:
|
|
174
|
+
return LABELS.get(kind, kind)
|