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.
Files changed (45) hide show
  1. shelldeck/__init__.py +0 -0
  2. shelldeck/__main__.py +4 -0
  3. shelldeck/addons/__init__.py +1 -0
  4. shelldeck/addons/fast_context.py +1346 -0
  5. shelldeck/auth.py +207 -0
  6. shelldeck/cli.py +587 -0
  7. shelldeck/db.py +794 -0
  8. shelldeck/gitgraph.py +150 -0
  9. shelldeck/integration/bash.sh +27 -0
  10. shelldeck/integration/shelldeck.fish +5 -0
  11. shelldeck/integration/shelldeck.ps1 +35 -0
  12. shelldeck/integration/zsh/.zshenv +6 -0
  13. shelldeck/integration/zsh/.zshrc +10 -0
  14. shelldeck/pty.py +352 -0
  15. shelldeck/runner.py +75 -0
  16. shelldeck/scheduler.py +283 -0
  17. shelldeck/server.py +1216 -0
  18. shelldeck/shells.py +174 -0
  19. shelldeck/static/app.css +2288 -0
  20. shelldeck/static/app.js +1863 -0
  21. shelldeck/static/gitgraph.js +142 -0
  22. shelldeck/static/history.js +172 -0
  23. shelldeck/static/icon-192.png +0 -0
  24. shelldeck/static/icon-32.png +0 -0
  25. shelldeck/static/icon-512.png +0 -0
  26. shelldeck/static/icon.svg +1 -0
  27. shelldeck/static/index.html +102 -0
  28. shelldeck/static/manifest.webmanifest +1 -0
  29. shelldeck/static/monitor.js +143 -0
  30. shelldeck/static/ui.js +316 -0
  31. shelldeck/static/vendor/LICENSE-xterm.txt +21 -0
  32. shelldeck/static/vendor/addon-fit.js +2 -0
  33. shelldeck/static/vendor/addon-search.js +2 -0
  34. shelldeck/static/vendor/addon-serialize.js +2 -0
  35. shelldeck/static/vendor/addon-web-links.js +2 -0
  36. shelldeck/static/vendor/addon-webgl.js +2 -0
  37. shelldeck/static/vendor/xterm.css +218 -0
  38. shelldeck/static/vendor/xterm.js +2 -0
  39. shelldeck/static/views.js +658 -0
  40. shelldeck/stats.py +128 -0
  41. shelldeck-0.0.1.dist-info/METADATA +254 -0
  42. shelldeck-0.0.1.dist-info/RECORD +45 -0
  43. shelldeck-0.0.1.dist-info/WHEEL +4 -0
  44. shelldeck-0.0.1.dist-info/entry_points.txt +3 -0
  45. 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)