daps-shell 0.1.7__tar.gz → 0.2.3__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.
- {daps_shell-0.1.7 → daps_shell-0.2.3}/PKG-INFO +1 -1
- {daps_shell-0.1.7 → daps_shell-0.2.3}/daps/shell.py +194 -79
- daps_shell-0.2.3/daps/ui.html +515 -0
- daps_shell-0.2.3/daps/ui.py +130 -0
- {daps_shell-0.1.7 → daps_shell-0.2.3}/daps_shell.egg-info/PKG-INFO +1 -1
- {daps_shell-0.1.7 → daps_shell-0.2.3}/daps_shell.egg-info/SOURCES.txt +2 -0
- {daps_shell-0.1.7 → daps_shell-0.2.3}/pyproject.toml +4 -1
- {daps_shell-0.1.7 → daps_shell-0.2.3}/LICENSE +0 -0
- {daps_shell-0.1.7 → daps_shell-0.2.3}/README.md +0 -0
- {daps_shell-0.1.7 → daps_shell-0.2.3}/daps/__init__.py +0 -0
- {daps_shell-0.1.7 → daps_shell-0.2.3}/daps/__main__.py +0 -0
- {daps_shell-0.1.7 → daps_shell-0.2.3}/daps_shell.egg-info/dependency_links.txt +0 -0
- {daps_shell-0.1.7 → daps_shell-0.2.3}/daps_shell.egg-info/entry_points.txt +0 -0
- {daps_shell-0.1.7 → daps_shell-0.2.3}/daps_shell.egg-info/requires.txt +0 -0
- {daps_shell-0.1.7 → daps_shell-0.2.3}/daps_shell.egg-info/top_level.txt +0 -0
- {daps_shell-0.1.7 → daps_shell-0.2.3}/setup.cfg +0 -0
|
@@ -6,16 +6,32 @@ from prompt_toolkit.lexers import Lexer
|
|
|
6
6
|
from prompt_toolkit.styles import Style
|
|
7
7
|
from prompt_toolkit.formatted_text import FormattedText
|
|
8
8
|
from prompt_toolkit.history import FileHistory
|
|
9
|
+
from prompt_toolkit.keys import Keys
|
|
10
|
+
from prompt_toolkit.key_binding import KeyBindings
|
|
9
11
|
|
|
10
12
|
plugins = {}
|
|
11
|
-
|
|
13
|
+
_start_time = time.monotonic()
|
|
12
14
|
|
|
13
15
|
THEMES = {
|
|
14
|
-
"default":
|
|
15
|
-
"fire":
|
|
16
|
-
"ocean":
|
|
17
|
-
"candy":
|
|
18
|
-
"mono":
|
|
16
|
+
"default": {"username":"#00d787 bold","at":"#ffffff","hostname":"#5fafff bold","paren":"#00d787","ident":"#00d787 bold","dash":"#ffffff","path":"#00d7af bold","arrow":"#00d787 bold","errcode":"#ff5f5f bold","branch":"#d7af5f","builtin":"#00d787 bold","alias":"#5fafff bold","plugin":"#d7af5f bold","cmd":"#ffffff","cmd-real":"#ffffff underline","args":"#aaaaaa","arg-real":"#aaaaaa underline","time":"#888888"},
|
|
17
|
+
"fire": {"username":"#ff5f00 bold","at":"#ffffff","hostname":"#ff8700 bold","paren":"#ff5f00","ident":"#ff5f00 bold","dash":"#ffffff","path":"#ffaf00 bold","arrow":"#ff5f00 bold","errcode":"#ff0000 bold","branch":"#ffaf5f","builtin":"#ff5f00 bold","alias":"#ffaf00 bold","plugin":"#ff8700 bold","cmd":"#ffffff","cmd-real":"#ffffff underline","args":"#aaaaaa","arg-real":"#aaaaaa underline","time":"#888888"},
|
|
18
|
+
"ocean": {"username":"#0087ff bold","at":"#ffffff","hostname":"#00afff bold","paren":"#0087ff","ident":"#0087ff bold","dash":"#ffffff","path":"#00d7ff bold","arrow":"#0087ff bold","errcode":"#ff5f5f bold","branch":"#5fafff","builtin":"#0087ff bold","alias":"#00afff bold","plugin":"#5fafff bold","cmd":"#ffffff","cmd-real":"#ffffff underline","args":"#aaaaaa","arg-real":"#aaaaaa underline","time":"#888888"},
|
|
19
|
+
"candy": {"username":"#ff5faf bold","at":"#ffffff","hostname":"#af5fff bold","paren":"#ff5faf","ident":"#ff5faf bold","dash":"#ffffff","path":"#ff87d7 bold","arrow":"#ff5faf bold","errcode":"#ff0000 bold","branch":"#ffaf5f","builtin":"#ff5faf bold","alias":"#af5fff bold","plugin":"#ff87d7 bold","cmd":"#ffffff","cmd-real":"#ffffff underline","args":"#aaaaaa","arg-real":"#aaaaaa underline","time":"#888888"},
|
|
20
|
+
"mono": {"username":"#ffffff bold","at":"#aaaaaa","hostname":"#ffffff bold","paren":"#aaaaaa","ident":"#ffffff bold","dash":"#aaaaaa","path":"#ffffff bold","arrow":"#ffffff bold","errcode":"#ff5f5f bold","branch":"#aaaaaa","builtin":"#ffffff bold","alias":"#aaaaaa bold","plugin":"#aaaaaa bold","cmd":"#ffffff","cmd-real":"#ffffff underline","args":"#666666","arg-real":"#666666 underline","time":"#555555"},
|
|
21
|
+
"cyberpunk": {"username":"#ffff00 bold","at":"#ff00ff","hostname":"#ff00ff bold","paren":"#ffff00","ident":"#ffff00 bold","dash":"#ff00ff","path":"#00ffff bold","arrow":"#ffff00 bold","errcode":"#ff0000 bold","branch":"#ff00ff","builtin":"#ffff00 bold","alias":"#ff00ff bold","plugin":"#00ffff bold","cmd":"#ffffff","cmd-real":"#ffffff underline","args":"#aaaaaa","arg-real":"#aaaaaa underline","time":"#888888"},
|
|
22
|
+
"nord": {"username":"#88c0d0 bold","at":"#e5e9f0","hostname":"#81a1c1 bold","paren":"#88c0d0","ident":"#88c0d0 bold","dash":"#e5e9f0","path":"#8fbcbb bold","arrow":"#88c0d0 bold","errcode":"#bf616a bold","branch":"#ebcb8b","builtin":"#88c0d0 bold","alias":"#81a1c1 bold","plugin":"#b48ead bold","cmd":"#eceff4","cmd-real":"#eceff4 underline","args":"#7b88a1","arg-real":"#7b88a1 underline","time":"#616e88"},
|
|
23
|
+
"dracula": {"username":"#ff79c6 bold","at":"#f8f8f2","hostname":"#bd93f9 bold","paren":"#ff79c6","ident":"#ff79c6 bold","dash":"#f8f8f2","path":"#50fa7b bold","arrow":"#ff79c6 bold","errcode":"#ff5555 bold","branch":"#f1fa8c","builtin":"#ff79c6 bold","alias":"#bd93f9 bold","plugin":"#8be9fd bold","cmd":"#f8f8f2","cmd-real":"#f8f8f2 underline","args":"#6272a4","arg-real":"#6272a4 underline","time":"#44475a"},
|
|
24
|
+
"solarized": {"username":"#859900 bold","at":"#839496","hostname":"#268bd2 bold","paren":"#859900","ident":"#859900 bold","dash":"#839496","path":"#2aa198 bold","arrow":"#859900 bold","errcode":"#dc322f bold","branch":"#b58900","builtin":"#859900 bold","alias":"#268bd2 bold","plugin":"#6c71c4 bold","cmd":"#839496","cmd-real":"#839496 underline","args":"#586e75","arg-real":"#586e75 underline","time":"#657b83"},
|
|
25
|
+
"blood": {"username":"#ff0000 bold","at":"#cc0000","hostname":"#aa0000 bold","paren":"#ff0000","ident":"#ff0000 bold","dash":"#cc0000","path":"#ff3333 bold","arrow":"#ff0000 bold","errcode":"#ffffff bold","branch":"#ff6666","builtin":"#ff0000 bold","alias":"#cc0000 bold","plugin":"#ff3333 bold","cmd":"#ffcccc","cmd-real":"#ffcccc underline","args":"#993333","arg-real":"#993333 underline","time":"#661111"},
|
|
26
|
+
"ice": {"username":"#ffffff bold","at":"#aaffff","hostname":"#00ffff bold","paren":"#ffffff","ident":"#ffffff bold","dash":"#aaffff","path":"#aaffff bold","arrow":"#ffffff bold","errcode":"#ff5f5f bold","branch":"#00ffff","builtin":"#ffffff bold","alias":"#00ffff bold","plugin":"#aaffff bold","cmd":"#ffffff","cmd-real":"#ffffff underline","args":"#88cccc","arg-real":"#88cccc underline","time":"#557777"},
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
PROMPT_FORMATS = {
|
|
30
|
+
"default": "{errcode}{time}{user}@{host} ({ident}) - {cwd}{branch}\n> ",
|
|
31
|
+
"minimal": "{errcode}{user} {cwd}{branch} > ",
|
|
32
|
+
"compact": "{errcode}{ident} {cwd}{branch} > ",
|
|
33
|
+
"powerline":"{errcode} {user} | {cwd} | {branch} \n> ",
|
|
34
|
+
"classic": "[{user}@{host} {cwd}]{branch}{ident} ",
|
|
19
35
|
}
|
|
20
36
|
|
|
21
37
|
def loadconf():
|
|
@@ -59,23 +75,30 @@ def reload_plugins(base_config):
|
|
|
59
75
|
|
|
60
76
|
def load_local_conf(base):
|
|
61
77
|
local = os.path.join(os.getcwd(), ".daps")
|
|
62
|
-
if not os.path.exists(local):
|
|
63
|
-
return base
|
|
78
|
+
if not os.path.exists(local): return base
|
|
64
79
|
try:
|
|
65
|
-
with open(local) as f:
|
|
66
|
-
overrides = json.load(f)
|
|
80
|
+
with open(local) as f: overrides = json.load(f)
|
|
67
81
|
merged = dict(base)
|
|
68
82
|
merged["aliases"] = {**base.get("aliases", {}), **overrides.get("aliases", {})}
|
|
69
83
|
for k, v in overrides.items():
|
|
70
84
|
if k != "aliases": merged[k] = v
|
|
71
85
|
return merged
|
|
72
|
-
except Exception:
|
|
73
|
-
return base
|
|
86
|
+
except Exception: return base
|
|
74
87
|
|
|
75
88
|
def save_conf(config):
|
|
76
89
|
whereis = os.path.join(os.path.expanduser("~/.config/daps"), "config.json")
|
|
77
|
-
with open(whereis, "w") as f:
|
|
78
|
-
|
|
90
|
+
with open(whereis, "w") as f: json.dump(config, f, indent=2)
|
|
91
|
+
|
|
92
|
+
def get_bookmarks():
|
|
93
|
+
f = os.path.expanduser("~/.config/daps/bookmarks.json")
|
|
94
|
+
if not os.path.exists(f): return {}
|
|
95
|
+
try:
|
|
96
|
+
with open(f) as fp: return json.load(fp)
|
|
97
|
+
except Exception: return {}
|
|
98
|
+
|
|
99
|
+
def save_bookmarks(bm):
|
|
100
|
+
f = os.path.expanduser("~/.config/daps/bookmarks.json")
|
|
101
|
+
with open(f, "w") as fp: json.dump(bm, fp, indent=2)
|
|
79
102
|
|
|
80
103
|
def git_branch():
|
|
81
104
|
try:
|
|
@@ -84,11 +107,12 @@ def git_branch():
|
|
|
84
107
|
if not b: return None
|
|
85
108
|
dirty = subprocess.run(["git", "status", "--porcelain"], capture_output=True, text=True, timeout=1).stdout.strip()
|
|
86
109
|
return f"{b}{'*' if dirty else ''}"
|
|
87
|
-
except Exception:
|
|
88
|
-
return None
|
|
110
|
+
except Exception: return None
|
|
89
111
|
|
|
90
|
-
def expand_env(s):
|
|
91
|
-
|
|
112
|
+
def expand_env(s): return os.path.expandvars(os.path.expanduser(s))
|
|
113
|
+
def is_executable(cmd): return shutil.which(cmd) is not None
|
|
114
|
+
def is_existing_path(token): return os.path.exists(os.path.expanduser(expand_env(token)))
|
|
115
|
+
def is_ssh(): return "SSH_CLIENT" in os.environ or "SSH_TTY" in os.environ
|
|
92
116
|
|
|
93
117
|
def fmt_time(secs):
|
|
94
118
|
if secs is None: return None
|
|
@@ -96,15 +120,14 @@ def fmt_time(secs):
|
|
|
96
120
|
if secs < 60: return f"{secs:.1f}s"
|
|
97
121
|
return f"{int(secs//60)}m{int(secs%60)}s"
|
|
98
122
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
BUILTINS = ["cd", "exit", "clear", "plugins", "daps", "which"]
|
|
123
|
+
BUILTINS = ["cd", "exit", "clear", "plugins", "daps", "which", "help", "bookmark", "bm"]
|
|
102
124
|
|
|
103
125
|
class DapsCompleter(Completer):
|
|
104
126
|
def __init__(self, aliases):
|
|
105
127
|
self.aliases = aliases
|
|
106
128
|
def _commands(self):
|
|
107
|
-
|
|
129
|
+
bm = list(get_bookmarks().keys())
|
|
130
|
+
return list(self.aliases.keys()) + list(plugins.keys()) + BUILTINS + bm
|
|
108
131
|
def get_completions(self, document, complete_event):
|
|
109
132
|
text = document.text_before_cursor
|
|
110
133
|
parts = text.split()
|
|
@@ -119,9 +142,6 @@ class DapsCompleter(Completer):
|
|
|
119
142
|
if os.path.isdir(m): display += "/"
|
|
120
143
|
yield Completion(display, start_position=-len(word))
|
|
121
144
|
|
|
122
|
-
def is_executable(cmd): return shutil.which(cmd) is not None
|
|
123
|
-
def is_existing_path(token): return os.path.exists(os.path.expanduser(expand_env(token)))
|
|
124
|
-
|
|
125
145
|
class DapsLexer(Lexer):
|
|
126
146
|
def __init__(self, aliases):
|
|
127
147
|
self.aliases = aliases
|
|
@@ -152,28 +172,36 @@ class DapsLexer(Lexer):
|
|
|
152
172
|
return inner
|
|
153
173
|
|
|
154
174
|
def make_style(theme_name):
|
|
155
|
-
|
|
156
|
-
return Style.from_dict(t)
|
|
175
|
+
return Style.from_dict(THEMES.get(theme_name, THEMES["default"]))
|
|
157
176
|
|
|
158
|
-
def build_prompt(user, host, errcode, elapsed, theme_name):
|
|
177
|
+
def build_prompt(user, host, errcode, elapsed, theme_name, fmt_name="default"):
|
|
159
178
|
home = os.path.expanduser("~")
|
|
160
179
|
cwd = os.getcwd()
|
|
161
180
|
display_cwd = cwd.replace(home, "~", 1) if cwd.startswith(home) else cwd
|
|
162
|
-
ident = "#" if user == "root" else ("
|
|
181
|
+
ident = "#" if user == "root" else ("@" if is_ssh() else "$")
|
|
163
182
|
branch = git_branch()
|
|
164
183
|
t = fmt_time(elapsed)
|
|
165
184
|
tokens = []
|
|
166
|
-
if
|
|
167
|
-
tokens += [("class:errcode", f"[{errcode}]
|
|
168
|
-
|
|
169
|
-
tokens += [("class:
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
("class:dash", "
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
185
|
+
if fmt_name == "minimal":
|
|
186
|
+
if errcode and errcode != "0": tokens += [("class:errcode", f"[{errcode}] ")]
|
|
187
|
+
tokens += [("class:username", user), ("class:dash", " "), ("class:path", display_cwd)]
|
|
188
|
+
if branch: tokens += [("class:dash", " "), ("class:branch", f"[{branch}]")]
|
|
189
|
+
tokens += [("", "\n"), ("class:arrow", "> ")]
|
|
190
|
+
elif fmt_name == "compact":
|
|
191
|
+
if errcode and errcode != "0": tokens += [("class:errcode", f"[{errcode}] ")]
|
|
192
|
+
tokens += [("class:ident", ident), ("class:dash", " "), ("class:path", display_cwd)]
|
|
193
|
+
if branch: tokens += [("class:dash", " "), ("class:branch", f"[{branch}]")]
|
|
194
|
+
tokens += [("", "\n"), ("class:arrow", "> ")]
|
|
195
|
+
elif fmt_name == "classic":
|
|
196
|
+
tokens += [("class:paren", "["), ("class:username", user), ("class:at", "@"), ("class:hostname", host), ("class:dash", " "), ("class:path", display_cwd), ("class:paren", "]")]
|
|
197
|
+
if branch: tokens += [("class:branch", f"({branch})")]
|
|
198
|
+
tokens += [("class:ident", ident), ("", " ")]
|
|
199
|
+
else:
|
|
200
|
+
if errcode and errcode != "0": tokens += [("class:errcode", f"[{errcode}]"), ("class:dash", " - ")]
|
|
201
|
+
if t: tokens += [("class:time", f"[{t}]"), ("class:dash", " - ")]
|
|
202
|
+
tokens += [("class:username", user), ("class:at", "@"), ("class:hostname", host), ("class:dash", " "), ("class:paren", "("), ("class:ident", ident), ("class:paren", ")"), ("class:dash", " - "), ("class:path", display_cwd)]
|
|
203
|
+
if branch: tokens += [("class:dash", " "), ("class:branch", f"[{branch}]")]
|
|
204
|
+
tokens += [("", "\n"), ("class:arrow", "> ")]
|
|
177
205
|
return tokens
|
|
178
206
|
|
|
179
207
|
def fmt_error(msg): print(f"\033[91mdaps error\033[0m {msg}", file=sys.stderr)
|
|
@@ -198,6 +226,41 @@ def suggest(cmd, alias):
|
|
|
198
226
|
print(f"\033[93mdaps\033[0m command not found: \033[91m{cmd}\033[0m")
|
|
199
227
|
if matches: print(f" did you mean: {', '.join(f'\033[96m{m}\033[0m' for m in matches)}?")
|
|
200
228
|
|
|
229
|
+
def do_help(alias):
|
|
230
|
+
print(f"\033[96m{'─'*50}\033[0m")
|
|
231
|
+
print(f"\033[96mdaps\033[0m built-in commands:")
|
|
232
|
+
helps = {
|
|
233
|
+
"cd [path]": "change directory (- for prev, fuzzy match on miss)",
|
|
234
|
+
"clear": "clear screen (re-runs greeter if cleargreet=yes)",
|
|
235
|
+
"exit": "exit daps",
|
|
236
|
+
"help": "show this help",
|
|
237
|
+
"which <cmd>": "show what a command resolves to",
|
|
238
|
+
"plugins": "list loaded plugins",
|
|
239
|
+
"bookmark <name> [path]": "save/jump to a directory bookmark",
|
|
240
|
+
"bm": "alias for bookmark",
|
|
241
|
+
"daps config set/get": "manage config",
|
|
242
|
+
"daps config ui": "open web config editor",
|
|
243
|
+
"daps alias list": "list aliases",
|
|
244
|
+
"daps theme <n>": "switch theme",
|
|
245
|
+
"daps prompt <fmt>": "switch prompt format",
|
|
246
|
+
"daps reload": "reload config and plugins",
|
|
247
|
+
"daps update": "upgrade daps-shell",
|
|
248
|
+
"daps env set/get/del/list": "manage env vars",
|
|
249
|
+
"!! / sudo !!": "repeat last command",
|
|
250
|
+
"cmd &": "run in background",
|
|
251
|
+
"cmd \\": "multiline input",
|
|
252
|
+
}
|
|
253
|
+
for k, v in helps.items(): print(f" \033[92m{k:<30}\033[0m {v}")
|
|
254
|
+
if alias:
|
|
255
|
+
print(f"\n\033[96mdaps\033[0m aliases ({len(alias)}):")
|
|
256
|
+
for k, v in alias.items(): print(f" \033[96m{k:<20}\033[0m → {v}")
|
|
257
|
+
if plugins:
|
|
258
|
+
print(f"\n\033[96mdaps\033[0m plugins ({len(plugins)}):")
|
|
259
|
+
for name, mod in plugins.items():
|
|
260
|
+
doc = getattr(mod, "__doc__", None) or (mod.run.__doc__ if hasattr(mod, "run") else None) or "no description"
|
|
261
|
+
print(f" \033[93m{name:<20}\033[0m {doc.strip()}")
|
|
262
|
+
print(f"\033[96m{'─'*50}\033[0m")
|
|
263
|
+
|
|
201
264
|
def do_plugins():
|
|
202
265
|
if not plugins: fmt_info("no plugins loaded"); return
|
|
203
266
|
fmt_info(f"{len(plugins)} plugin(s) loaded:")
|
|
@@ -218,6 +281,27 @@ def do_which(args, alias):
|
|
|
218
281
|
if found: print(f"{cmd}: {found}"); return "0"
|
|
219
282
|
fmt_error(f"which: {cmd}: not found"); return "1"
|
|
220
283
|
|
|
284
|
+
def do_bookmark(args):
|
|
285
|
+
bm = get_bookmarks()
|
|
286
|
+
if not args:
|
|
287
|
+
if not bm: fmt_info("no bookmarks"); return "0"
|
|
288
|
+
fmt_info(f"{len(bm)} bookmark(s):")
|
|
289
|
+
for k, v in bm.items(): print(f" \033[96m{k:<20}\033[0m {v}")
|
|
290
|
+
return "0"
|
|
291
|
+
name = args[0]
|
|
292
|
+
if len(args) >= 2:
|
|
293
|
+
path = os.path.expanduser(" ".join(args[1:]))
|
|
294
|
+
bm[name] = path; save_bookmarks(bm)
|
|
295
|
+
fmt_info(f"bookmark '{name}' → {path}"); return "0"
|
|
296
|
+
if name in bm:
|
|
297
|
+
try: os.chdir(bm[name]); return "0"
|
|
298
|
+
except Exception as e: fmt_error(str(e)); return "1"
|
|
299
|
+
if name == "del" and len(args) >= 2:
|
|
300
|
+
bm.pop(args[1], None); save_bookmarks(bm)
|
|
301
|
+
fmt_info(f"bookmark '{args[1]}' removed"); return "0"
|
|
302
|
+
bm[name] = os.getcwd(); save_bookmarks(bm)
|
|
303
|
+
fmt_info(f"bookmark '{name}' → {os.getcwd()}"); return "0"
|
|
304
|
+
|
|
221
305
|
def do_cd_fuzzy(target):
|
|
222
306
|
try: os.chdir(target); return True
|
|
223
307
|
except FileNotFoundError:
|
|
@@ -233,20 +317,38 @@ def do_cd_fuzzy(target):
|
|
|
233
317
|
except Exception: pass
|
|
234
318
|
return False
|
|
235
319
|
|
|
320
|
+
def do_env(args, base_config):
|
|
321
|
+
envs = base_config.get("env", {})
|
|
322
|
+
if not args or args[0] == "list":
|
|
323
|
+
if not envs: fmt_info("no env vars set"); return "0"
|
|
324
|
+
fmt_info(f"{len(envs)} env var(s):")
|
|
325
|
+
for k, v in envs.items(): print(f" \033[96m{k}\033[0m={v}")
|
|
326
|
+
return "0"
|
|
327
|
+
if args[0] == "set" and len(args) >= 3:
|
|
328
|
+
key, val = args[1], " ".join(args[2:])
|
|
329
|
+
envs[key] = val; base_config["env"] = envs
|
|
330
|
+
os.environ[key] = val; save_conf(base_config)
|
|
331
|
+
fmt_info(f"set {key}={val}"); return "0"
|
|
332
|
+
if args[0] == "get" and len(args) >= 2:
|
|
333
|
+
v = envs.get(args[1]) or os.environ.get(args[1])
|
|
334
|
+
if v: print(f" {args[1]}={v}"); return "0"
|
|
335
|
+
fmt_error(f"env: {args[1]} not set"); return "1"
|
|
336
|
+
if args[0] == "del" and len(args) >= 2:
|
|
337
|
+
envs.pop(args[1], None); base_config["env"] = envs
|
|
338
|
+
os.environ.pop(args[1], None); save_conf(base_config)
|
|
339
|
+
fmt_info(f"unset {args[1]}"); return "0"
|
|
340
|
+
fmt_error(f"usage: daps env set/get/del/list"); return "1"
|
|
341
|
+
|
|
236
342
|
def install_plugin(url):
|
|
237
343
|
name = url.split("/")[-1]
|
|
238
344
|
if not name.endswith(".py"): name += ".py"
|
|
239
345
|
dest = os.path.join(os.path.expanduser("~/.config/daps/plugins"), name)
|
|
240
|
-
try:
|
|
241
|
-
|
|
242
|
-
fmt_info(f"installed plugin: {name}")
|
|
243
|
-
return True
|
|
244
|
-
except Exception as e:
|
|
245
|
-
fmt_error(f"plugin install failed: {e}"); return False
|
|
346
|
+
try: urllib.request.urlretrieve(url, dest); return True, None
|
|
347
|
+
except Exception as e: return False, str(e)
|
|
246
348
|
|
|
247
349
|
def do_daps_cmd(args, base_config):
|
|
248
350
|
if not args:
|
|
249
|
-
fmt_info("usage: daps config
|
|
351
|
+
fmt_info("usage: daps config|plugins|plugin|reload|update|theme|prompt|alias|env|config ui")
|
|
250
352
|
return "0"
|
|
251
353
|
sub = args[0]
|
|
252
354
|
if sub == "plugins": do_plugins(); return "0"
|
|
@@ -256,23 +358,31 @@ def do_daps_cmd(args, base_config):
|
|
|
256
358
|
r = subprocess.run([sys.executable, "-m", "pip", "install", "--upgrade", "daps-shell"])
|
|
257
359
|
return str(r.returncode)
|
|
258
360
|
if sub == "theme":
|
|
259
|
-
if len(args) < 2:
|
|
260
|
-
fmt_info(f"available themes: {', '.join(THEMES.keys())}"); return "0"
|
|
361
|
+
if len(args) < 2: fmt_info(f"available themes: {', '.join(THEMES.keys())}"); return "0"
|
|
261
362
|
if args[1] not in THEMES: fmt_error(f"unknown theme: {args[1]}"); return "1"
|
|
262
|
-
base_config["theme"] = args[1]
|
|
263
|
-
|
|
264
|
-
|
|
363
|
+
base_config["theme"] = args[1]; save_conf(base_config)
|
|
364
|
+
fmt_info(f"theme set to {args[1]}"); return "0"
|
|
365
|
+
if sub == "prompt":
|
|
366
|
+
if len(args) < 2: fmt_info(f"available formats: {', '.join(PROMPT_FORMATS.keys())}"); return "0"
|
|
367
|
+
if args[1] not in PROMPT_FORMATS: fmt_error(f"unknown format: {args[1]}"); return "1"
|
|
368
|
+
base_config["prompt_format"] = args[1]; save_conf(base_config)
|
|
369
|
+
fmt_info(f"prompt format set to {args[1]}"); return "0"
|
|
265
370
|
if sub == "plugin" and len(args) >= 3 and args[1] == "install":
|
|
266
|
-
install_plugin(args[2])
|
|
371
|
+
ok, err = install_plugin(args[2])
|
|
372
|
+
if ok: fmt_info("plugin installed"); return "0"
|
|
373
|
+
fmt_error(f"install failed: {err}"); return "1"
|
|
267
374
|
if sub == "alias" and len(args) >= 2 and args[1] == "list":
|
|
268
375
|
aliases = base_config.get("aliases", {})
|
|
269
376
|
if not aliases: fmt_info("no aliases set"); return "0"
|
|
270
377
|
fmt_info(f"{len(aliases)} alias(es):")
|
|
271
378
|
for k, v in aliases.items(): print(f" \033[96m{k}\033[0m → {v}")
|
|
272
379
|
return "0"
|
|
380
|
+
if sub == "env":
|
|
381
|
+
return do_env(args[1:], base_config)
|
|
273
382
|
if sub == "config":
|
|
274
|
-
if len(args)
|
|
275
|
-
|
|
383
|
+
if len(args) >= 2 and args[1] == "ui":
|
|
384
|
+
from daps.ui import launch; launch(); return "0"
|
|
385
|
+
if len(args) < 2: fmt_info("usage: daps config set/get/set-alias/del-alias/ui"); return "0"
|
|
276
386
|
if args[1] == "get" and len(args) >= 3:
|
|
277
387
|
print(f" {args[2]} = {json.dumps(base_config.get(args[2], None))}"); return "0"
|
|
278
388
|
if args[1] == "set" and len(args) >= 4:
|
|
@@ -294,25 +404,36 @@ def run_bg(cmd_parts):
|
|
|
294
404
|
t.start()
|
|
295
405
|
print(f"\033[96m[bg]\033[0m {' '.join(cmd_parts)}")
|
|
296
406
|
|
|
407
|
+
def apply_saved_env(config):
|
|
408
|
+
for k, v in config.get("env", {}).items(): os.environ[k] = v
|
|
409
|
+
|
|
297
410
|
def main():
|
|
298
411
|
signal.signal(signal.SIGINT, lambda *_: sys.exit(255))
|
|
299
412
|
base_config = loadconf()
|
|
413
|
+
apply_saved_env(base_config)
|
|
300
414
|
history_path = os.path.expanduser("~/.daps.history")
|
|
301
415
|
user = subprocess.run(["whoami"], capture_output=True, text=True).stdout.strip()
|
|
302
416
|
try:
|
|
303
417
|
with open("/etc/hostname") as f: host = f.read().strip()
|
|
304
418
|
except Exception: host = "daps-station"
|
|
305
419
|
|
|
420
|
+
startup_elapsed = time.monotonic() - _start_time
|
|
421
|
+
if startup_elapsed > 0.3:
|
|
422
|
+
print(f"\033[90mstarted in {startup_elapsed*1000:.0f}ms\033[0m")
|
|
423
|
+
|
|
306
424
|
prev_dir = os.getcwd()
|
|
307
425
|
errcode = None
|
|
308
426
|
elapsed = None
|
|
309
427
|
history = []
|
|
428
|
+
bm = get_bookmarks()
|
|
310
429
|
|
|
311
430
|
def make_session(alias, theme):
|
|
431
|
+
kb = KeyBindings()
|
|
312
432
|
return PromptSession(
|
|
313
433
|
history=FileHistory(history_path),
|
|
314
434
|
completer=DapsCompleter(alias), lexer=DapsLexer(alias),
|
|
315
435
|
style=make_style(theme), complete_while_typing=False,
|
|
436
|
+
key_bindings=kb, enable_history_search=True,
|
|
316
437
|
)
|
|
317
438
|
|
|
318
439
|
config = load_local_conf(base_config)
|
|
@@ -328,38 +449,36 @@ def main():
|
|
|
328
449
|
session = make_session(config.get("aliases", {}), config.get("theme", "default"))
|
|
329
450
|
prev_dir = cur_dir
|
|
330
451
|
alias = config.get("aliases", {})
|
|
452
|
+
fmt = config.get("prompt_format", "default")
|
|
331
453
|
try:
|
|
332
|
-
raw = session.prompt(FormattedText(build_prompt(user, host, errcode, elapsed, config.get("theme", "default")))).strip()
|
|
333
|
-
except EOFError:
|
|
334
|
-
runfc(config, "farewell"); sys.exit(0)
|
|
454
|
+
raw = session.prompt(FormattedText(build_prompt(user, host, errcode, elapsed, config.get("theme", "default"), fmt))).strip()
|
|
455
|
+
except EOFError: runfc(config, "farewell"); sys.exit(0)
|
|
335
456
|
except KeyboardInterrupt: print(); continue
|
|
336
|
-
|
|
337
457
|
if not raw: continue
|
|
338
458
|
|
|
339
459
|
if raw == "!!":
|
|
340
460
|
if not history: fmt_error("no previous command"); continue
|
|
341
|
-
raw = history[-1]
|
|
342
|
-
print(f"\033[90m{raw}\033[0m")
|
|
343
|
-
|
|
461
|
+
raw = history[-1]; print(f"\033[90m{raw}\033[0m")
|
|
344
462
|
raw = re.sub(r'\bsudo !!\b', lambda _: f"sudo {history[-1]}" if history else "sudo", raw)
|
|
345
|
-
|
|
346
463
|
history.append(raw)
|
|
347
464
|
if len(history) > 1 and history[-1] == history[-2]: history.pop()
|
|
348
465
|
|
|
349
466
|
bg = raw.endswith("&")
|
|
350
467
|
if bg: raw = raw[:-1].strip()
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
while continuation.endswith("\\"):
|
|
354
|
-
continuation = continuation[:-1]
|
|
468
|
+
while raw.endswith("\\"):
|
|
469
|
+
raw = raw[:-1]
|
|
355
470
|
try: extra = input("... ").strip()
|
|
356
471
|
except (EOFError, KeyboardInterrupt): break
|
|
357
|
-
|
|
358
|
-
raw = continuation
|
|
472
|
+
raw += " " + extra
|
|
359
473
|
|
|
360
474
|
parts = [expand_env(p) for p in raw.split()]
|
|
361
475
|
cmd_base = parts[0]
|
|
362
476
|
args = parts[1:]
|
|
477
|
+
bm = get_bookmarks()
|
|
478
|
+
if cmd_base in bm:
|
|
479
|
+
try: os.chdir(bm[cmd_base]); errcode = "0"
|
|
480
|
+
except Exception as e: fmt_error(str(e)); errcode = "1"
|
|
481
|
+
continue
|
|
363
482
|
|
|
364
483
|
if cmd_base in alias:
|
|
365
484
|
translated = alias[cmd_base]
|
|
@@ -370,26 +489,23 @@ def main():
|
|
|
370
489
|
|
|
371
490
|
ccmd = cmd_parts[0]
|
|
372
491
|
ccmar = cmd_parts[1:]
|
|
373
|
-
|
|
374
492
|
t0 = time.monotonic()
|
|
375
493
|
|
|
376
494
|
if ccmd == "cd":
|
|
377
|
-
if ccmar and ccmar[0] == "-":
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
target = os.path.expanduser(ccmar[0] if ccmar else "~")
|
|
381
|
-
if not do_cd_fuzzy(target):
|
|
382
|
-
fmt_error(f"cd: no such directory: {target}"); errcode = "1"
|
|
495
|
+
if ccmar and ccmar[0] == "-": target = prev_dir
|
|
496
|
+
else: target = os.path.expanduser(ccmar[0] if ccmar else "~")
|
|
497
|
+
if not do_cd_fuzzy(target): fmt_error(f"cd: no such directory: {target}"); errcode = "1"
|
|
383
498
|
else: errcode = "0"
|
|
384
499
|
elif ccmd == "exit": runfc(config, "farewell"); sys.exit(0)
|
|
385
500
|
elif ccmd == "clear": do_clear(config); errcode = "0"
|
|
386
501
|
elif ccmd == "plugins": do_plugins(); errcode = "0"
|
|
502
|
+
elif ccmd == "help": do_help(alias); errcode = "0"
|
|
387
503
|
elif ccmd == "which": errcode = do_which(ccmar, alias)
|
|
504
|
+
elif ccmd in ("bookmark", "bm"): errcode = do_bookmark(ccmar)
|
|
388
505
|
elif ccmd == "daps":
|
|
389
506
|
result = do_daps_cmd(ccmar, base_config)
|
|
390
507
|
if result is None:
|
|
391
|
-
reload_plugins(base_config)
|
|
392
|
-
base_config = loadconf()
|
|
508
|
+
reload_plugins(base_config); base_config = loadconf()
|
|
393
509
|
config = load_local_conf(base_config)
|
|
394
510
|
session = make_session(config.get("aliases", {}), config.get("theme", "default"))
|
|
395
511
|
fmt_info("reloaded"); errcode = "0"
|
|
@@ -411,5 +527,4 @@ def main():
|
|
|
411
527
|
|
|
412
528
|
elapsed = time.monotonic() - t0
|
|
413
529
|
if elapsed < 0.1: elapsed = None
|
|
414
|
-
|
|
415
530
|
except Exception as e: fmt_error(f"unexpected shell error: {type(e).__name__}: {e}")
|
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>daps :: config</title>
|
|
7
|
+
<link href="https://fonts.googleapis.com/css2?family=Share+Tech+Mono&family=Orbitron:wght@400;700;900&display=swap" rel="stylesheet">
|
|
8
|
+
<style>
|
|
9
|
+
*{box-sizing:border-box;margin:0;padding:0}
|
|
10
|
+
:root{--g:#00ff41;--g2:#00cc33;--g3:#008f11;--g4:#003b00;--g5:#001a00;--bg:#000500;--bg2:#010d01;--bg3:#001500;--bg4:#000a00;--dim:#00ff4133;--dim2:#00ff4111;--red:#ff3333;--font:'Share Tech Mono',monospace;--head:'Orbitron',monospace}
|
|
11
|
+
html,body{height:100%;background:var(--bg);color:var(--g);font-family:var(--font);overflow:hidden}
|
|
12
|
+
canvas#matrix{position:fixed;top:0;left:0;width:100%;height:100%;opacity:0.06;pointer-events:none;z-index:0}
|
|
13
|
+
.scanline{position:fixed;top:0;left:0;width:100%;height:100%;background:repeating-linear-gradient(0deg,transparent,transparent 2px,rgba(0,0,0,0.04) 2px,rgba(0,0,0,0.04) 4px);pointer-events:none;z-index:2;opacity:0.4}
|
|
14
|
+
.app{position:relative;z-index:1;display:grid;grid-template-columns:210px 1fr;grid-template-rows:46px 1fr;height:100vh}
|
|
15
|
+
.topbar{grid-column:1/-1;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid var(--g4);padding:0 1.25rem;background:var(--bg4)}
|
|
16
|
+
.logo{font-family:var(--head);font-size:.95rem;font-weight:900;letter-spacing:4px;text-shadow:0 0 15px var(--g)}
|
|
17
|
+
.logo span{color:var(--g3);font-size:9px;letter-spacing:2px;margin-left:10px;font-family:var(--font)}
|
|
18
|
+
.topbar-right{display:flex;align-items:center;gap:8px}
|
|
19
|
+
.status-dot{width:6px;height:6px;border-radius:50%;background:var(--g);box-shadow:0 0 5px var(--g);animation:pulse 2s infinite}
|
|
20
|
+
@keyframes pulse{0%,100%{opacity:1}50%{opacity:0.3}}
|
|
21
|
+
.tbtn{font-family:var(--font);font-size:10px;letter-spacing:1px;padding:4px 10px;background:transparent;border:1px solid var(--g4);color:var(--g3);cursor:pointer;transition:all .2s}
|
|
22
|
+
.tbtn:hover{border-color:var(--g3);color:var(--g)}
|
|
23
|
+
.kbd-hint{font-size:8px;background:var(--bg3);border:1px solid var(--g4);padding:1px 4px;color:var(--g4);margin-left:3px}
|
|
24
|
+
.sidebar{border-right:1px solid var(--g4);background:var(--bg4);display:flex;flex-direction:column;overflow-y:auto}
|
|
25
|
+
.sidebar-label{font-size:8px;letter-spacing:3px;color:var(--g4);padding:10px 14px 3px;font-family:var(--head)}
|
|
26
|
+
.nav-item{display:flex;align-items:center;gap:8px;padding:10px 14px;font-size:11px;letter-spacing:2px;color:var(--g3);cursor:pointer;border-left:2px solid transparent;transition:all .15s;user-select:none}
|
|
27
|
+
.nav-item:hover{color:var(--g);background:var(--dim2)}
|
|
28
|
+
.nav-item.active{color:var(--g);border-left-color:var(--g);background:var(--dim2);text-shadow:0 0 6px var(--g)}
|
|
29
|
+
.nav-icon{font-size:12px;width:16px;text-align:center;opacity:.7}
|
|
30
|
+
.sidebar-sep{height:1px;background:var(--g4);margin:4px 0}
|
|
31
|
+
.undo-bar{margin-top:auto;border-top:1px solid var(--g4);padding:8px 10px;display:flex;gap:5px}
|
|
32
|
+
.undo-bar button{flex:1;font-family:var(--font);font-size:10px;letter-spacing:1px;padding:5px;background:transparent;border:1px solid var(--g4);color:var(--g3);cursor:pointer;transition:all .15s}
|
|
33
|
+
.undo-bar button:hover:not(:disabled){border-color:var(--g3);color:var(--g)}
|
|
34
|
+
.undo-bar button:disabled{opacity:0.25;cursor:default}
|
|
35
|
+
.main{overflow-y:auto;padding:1.25rem;display:flex;flex-direction:column;gap:0}
|
|
36
|
+
.panel{display:none}.panel.active{display:block}
|
|
37
|
+
.sh{font-family:var(--head);font-size:8px;letter-spacing:3px;color:var(--g3);margin-bottom:.85rem;padding-bottom:5px;border-bottom:1px solid var(--g4)}
|
|
38
|
+
input,textarea,select{background:var(--bg3);border:1px solid var(--g3);color:var(--g);font-family:var(--font);font-size:12px;padding:7px 10px;outline:none;transition:border-color .2s,box-shadow .2s}
|
|
39
|
+
input:focus,textarea:focus,select:focus{border-color:var(--g);box-shadow:0 0 7px var(--dim)}
|
|
40
|
+
textarea{resize:vertical;min-height:100px;line-height:1.6;width:100%}
|
|
41
|
+
select option{background:var(--bg)}
|
|
42
|
+
.fl{font-size:9px;color:var(--g3);letter-spacing:1px;margin-bottom:3px}
|
|
43
|
+
button,.btn{font-family:var(--font);font-size:10px;letter-spacing:2px;padding:6px 12px;background:transparent;border:1px solid var(--g3);color:var(--g);cursor:pointer;transition:all .15s}
|
|
44
|
+
button:hover,.btn:hover{background:var(--dim);border-color:var(--g);box-shadow:0 0 7px var(--dim)}
|
|
45
|
+
button:active,.btn:active{transform:scale(0.98)}
|
|
46
|
+
.btn-d{border-color:var(--red);color:var(--red)}.btn-d:hover{background:rgba(255,51,51,0.1);border-color:var(--red);box-shadow:none}
|
|
47
|
+
.btn-p{border-color:var(--g);text-shadow:0 0 5px var(--g)}
|
|
48
|
+
.search-row{position:relative;margin-bottom:.85rem}
|
|
49
|
+
.search-row input{width:100%;padding-left:22px}
|
|
50
|
+
.search-row::before{content:'>';position:absolute;left:8px;top:50%;transform:translateY(-50%);color:var(--g3);font-size:11px;pointer-events:none}
|
|
51
|
+
.alias-list{display:flex;flex-direction:column;gap:5px;margin-bottom:.85rem}
|
|
52
|
+
.alias-item{display:grid;grid-template-columns:130px 1fr auto auto;gap:6px;align-items:center;padding:8px 10px;background:var(--bg3);border:1px solid var(--g4);transition:border-color .15s}
|
|
53
|
+
.alias-item:hover{border-color:var(--g3)}
|
|
54
|
+
.akey{color:var(--g);font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
55
|
+
.aval{color:var(--g2);font-size:11px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
56
|
+
.abtn{padding:3px 7px;font-size:9px;letter-spacing:1px}
|
|
57
|
+
.add-form{background:var(--bg3);border:1px solid var(--g3);padding:.85rem;margin-bottom:.85rem;box-shadow:0 0 12px var(--dim2)}
|
|
58
|
+
.af-grid{display:grid;grid-template-columns:1fr 2fr;gap:6px;margin-bottom:6px}
|
|
59
|
+
.af-btns{display:flex;gap:6px}
|
|
60
|
+
.plugin-item,.mkt-item{display:flex;align-items:center;justify-content:space-between;padding:9px 12px;background:var(--bg3);border:1px solid var(--g4);margin-bottom:5px;transition:border-color .15s}
|
|
61
|
+
.plugin-item:hover,.mkt-item:hover{border-color:var(--g3)}
|
|
62
|
+
.pname{font-size:12px;color:var(--g)}.pdesc{font-size:10px;color:var(--g3);margin-top:1px}
|
|
63
|
+
.mkt-item.inst{opacity:.6;border-color:var(--g4)}
|
|
64
|
+
.inst-row{display:grid;grid-template-columns:1fr auto;gap:6px;margin-bottom:1rem}
|
|
65
|
+
.theme-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(120px,1fr));gap:8px;margin-bottom:1rem}
|
|
66
|
+
.theme-card{padding:10px;border:1px solid var(--g4);cursor:pointer;transition:all .15s;position:relative}
|
|
67
|
+
.theme-card:hover{border-color:var(--g3)}.theme-card.sel{border-color:var(--g);box-shadow:0 0 10px var(--dim)}
|
|
68
|
+
.theme-card.sel::after{content:'ON';position:absolute;top:5px;right:6px;font-size:7px;color:var(--g)}
|
|
69
|
+
.tdots{display:flex;gap:3px;margin-bottom:6px}.tdot{width:9px;height:9px;border-radius:50%}
|
|
70
|
+
.tname{font-size:9px;letter-spacing:2px;color:var(--g2)}
|
|
71
|
+
.fmt-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:6px;margin-bottom:1rem}
|
|
72
|
+
.fmt-card{padding:8px 10px;border:1px solid var(--g4);cursor:pointer;transition:all .15s;font-size:10px;color:var(--g3);letter-spacing:1px}
|
|
73
|
+
.fmt-card:hover{border-color:var(--g3);color:var(--g)}.fmt-card.sel{border-color:var(--g);color:var(--g);text-shadow:0 0 5px var(--g)}
|
|
74
|
+
.prev{background:var(--bg4);border:1px solid var(--g3);padding:.85rem 1rem;font-size:12px;line-height:2;margin-bottom:1rem;min-height:60px}
|
|
75
|
+
.toggle-row{display:flex;align-items:center;justify-content:space-between;padding:9px 0;border-bottom:1px solid var(--g4)}
|
|
76
|
+
.toggle-row:last-child{border-bottom:none}
|
|
77
|
+
.tl{font-size:12px;color:var(--g2)}.ts{font-size:9px;color:var(--g3);margin-top:1px}
|
|
78
|
+
.toggle{position:relative;width:36px;height:18px;cursor:pointer;flex-shrink:0}
|
|
79
|
+
.toggle input{opacity:0;width:0;height:0}
|
|
80
|
+
.toggle-sl{position:absolute;inset:0;background:var(--bg3);border:1px solid var(--g3);transition:.25s}
|
|
81
|
+
.toggle input:checked+.toggle-sl{background:var(--g4);border-color:var(--g)}
|
|
82
|
+
.toggle-sl::before{content:'';position:absolute;width:12px;height:12px;left:2px;top:2px;background:var(--g3);transition:.25s}
|
|
83
|
+
.toggle input:checked+.toggle-sl::before{transform:translateX(18px);background:var(--g);box-shadow:0 0 4px var(--g)}
|
|
84
|
+
.card{background:var(--bg2);border:1px solid var(--g4);padding:1rem 1rem 1rem 1.25rem;margin-bottom:.85rem;position:relative}
|
|
85
|
+
.card::before{content:'';position:absolute;left:0;top:0;width:2px;height:100%;background:var(--g3)}
|
|
86
|
+
.json-st{font-size:10px;margin-top:5px;color:var(--g3);min-height:16px}.json-st.ok{color:var(--g)}.json-st.err{color:var(--red)}
|
|
87
|
+
.hist-list{display:flex;flex-direction:column;gap:4px;max-height:400px;overflow-y:auto}
|
|
88
|
+
.hist-item{display:flex;align-items:center;justify-content:space-between;padding:6px 10px;background:var(--bg3);border:1px solid var(--g4);font-size:11px;color:var(--g2);cursor:pointer;transition:border-color .15s}
|
|
89
|
+
.hist-item:hover{border-color:var(--g3);color:var(--g)}
|
|
90
|
+
.hist-num{font-size:9px;color:var(--g4);margin-right:8px;min-width:30px}
|
|
91
|
+
.bm-list{display:flex;flex-direction:column;gap:5px;margin-bottom:.85rem}
|
|
92
|
+
.bm-item{display:grid;grid-template-columns:120px 1fr auto auto;gap:6px;align-items:center;padding:8px 10px;background:var(--bg3);border:1px solid var(--g4)}
|
|
93
|
+
.bm-item:hover{border-color:var(--g3)}
|
|
94
|
+
.bm-name{color:var(--g);font-size:12px}.bm-path{color:var(--g3);font-size:11px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
95
|
+
.env-list{display:flex;flex-direction:column;gap:5px;margin-bottom:.85rem}
|
|
96
|
+
.env-item{display:grid;grid-template-columns:150px 1fr auto;gap:6px;align-items:center;padding:7px 10px;background:var(--bg3);border:1px solid var(--g4)}
|
|
97
|
+
.env-item:hover{border-color:var(--g3)}
|
|
98
|
+
.ekey{color:var(--g);font-size:12px}.eval{color:var(--g2);font-size:11px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
99
|
+
.log-box{background:var(--bg4);border:1px solid var(--g3);padding:.85rem;font-size:11px;line-height:1.8;min-height:300px;max-height:400px;overflow-y:auto;font-family:var(--font)}
|
|
100
|
+
.log-cmd{color:var(--g)}.log-out{color:var(--g3)}.log-err{color:var(--red)}.log-ts{color:var(--g4);font-size:9px}
|
|
101
|
+
.wizard-step{display:none}.wizard-step.active{display:block}
|
|
102
|
+
.wiz-progress{display:flex;gap:6px;margin-bottom:1.5rem}
|
|
103
|
+
.wiz-dot{width:8px;height:8px;border-radius:50%;background:var(--g4);transition:background .2s}
|
|
104
|
+
.wiz-dot.done{background:var(--g3)}.wiz-dot.current{background:var(--g);box-shadow:0 0 6px var(--g)}
|
|
105
|
+
.toast{position:fixed;bottom:1.25rem;right:1.25rem;background:var(--bg2);border:1px solid var(--g);color:var(--g);font-family:var(--font);font-size:10px;letter-spacing:2px;padding:8px 16px;z-index:100;transform:translateY(50px);opacity:0;transition:all .2s;box-shadow:0 0 12px var(--dim)}
|
|
106
|
+
.toast.show{transform:translateY(0);opacity:1}.toast.err{border-color:var(--red);color:var(--red)}
|
|
107
|
+
.kbdo{position:fixed;inset:0;background:rgba(0,5,0,0.94);z-index:50;display:none;align-items:center;justify-content:center}
|
|
108
|
+
.kbdo.show{display:flex}
|
|
109
|
+
.kbd-box{background:var(--bg2);border:1px solid var(--g3);padding:1.5rem;max-width:440px;width:90%;box-shadow:0 0 30px var(--dim)}
|
|
110
|
+
.kbd-box h2{font-family:var(--head);font-size:10px;letter-spacing:3px;margin-bottom:1.25rem;color:var(--g)}
|
|
111
|
+
.kr{display:flex;align-items:center;justify-content:space-between;padding:6px 0;border-bottom:1px solid var(--g4);font-size:11px}
|
|
112
|
+
.kr:last-child{border-bottom:none}.kr span{color:var(--g3)}
|
|
113
|
+
.kc{display:flex;gap:3px}.kk{background:var(--bg3);border:1px solid var(--g3);padding:1px 6px;font-size:9px;color:var(--g)}
|
|
114
|
+
</style>
|
|
115
|
+
</head>
|
|
116
|
+
<body>
|
|
117
|
+
<canvas id="matrix"></canvas>
|
|
118
|
+
<div class="scanline"></div>
|
|
119
|
+
<div class="app">
|
|
120
|
+
<div class="topbar">
|
|
121
|
+
<div style="display:flex;align-items:center;gap:10px">
|
|
122
|
+
<div class="logo">DAPS <span>// config</span></div>
|
|
123
|
+
<div style="display:flex;align-items:center;gap:5px"><div class="status-dot"></div><span style="font-size:9px;color:var(--g3)" id="save-st">connected</span></div>
|
|
124
|
+
</div>
|
|
125
|
+
<div class="topbar-right">
|
|
126
|
+
<button class="tbtn" onclick="exportCfg()">EXPORT<span class="kbd-hint">E</span></button>
|
|
127
|
+
<button class="tbtn" onclick="document.getElementById('imp-file').click()">IMPORT<span class="kbd-hint">I</span></button>
|
|
128
|
+
<input type="file" id="imp-file" accept=".json" style="display:none" onchange="importCfg(event)">
|
|
129
|
+
<button class="tbtn" onclick="showKbd()">?</button>
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
<div class="sidebar">
|
|
133
|
+
<div class="sidebar-label">main</div>
|
|
134
|
+
<div class="nav-item active" onclick="nav('aliases')"><span class="nav-icon">$</span>ALIASES</div>
|
|
135
|
+
<div class="nav-item" onclick="nav('plugins')"><span class="nav-icon">+</span>PLUGINS</div>
|
|
136
|
+
<div class="nav-item" onclick="nav('marketplace')"><span class="nav-icon">#</span>MARKETPLACE</div>
|
|
137
|
+
<div class="sidebar-sep"></div>
|
|
138
|
+
<div class="sidebar-label">customize</div>
|
|
139
|
+
<div class="nav-item" onclick="nav('themes')"><span class="nav-icon">~</span>THEMES</div>
|
|
140
|
+
<div class="nav-item" onclick="nav('settings')"><span class="nav-icon">@</span>SETTINGS</div>
|
|
141
|
+
<div class="sidebar-sep"></div>
|
|
142
|
+
<div class="sidebar-label">tools</div>
|
|
143
|
+
<div class="nav-item" onclick="nav('history')"><span class="nav-icon">↑</span>HISTORY</div>
|
|
144
|
+
<div class="nav-item" onclick="nav('bookmarks')"><span class="nav-icon">★</span>BOOKMARKS</div>
|
|
145
|
+
<div class="nav-item" onclick="nav('env')"><span class="nav-icon">%</span>ENV VARS</div>
|
|
146
|
+
<div class="nav-item" onclick="nav('log')"><span class="nav-icon">»</span>SHELL LOG</div>
|
|
147
|
+
<div class="sidebar-sep"></div>
|
|
148
|
+
<div class="sidebar-label">advanced</div>
|
|
149
|
+
<div class="nav-item" onclick="nav('wizard')"><span class="nav-icon">✦</span>WIZARD</div>
|
|
150
|
+
<div class="nav-item" onclick="nav('json')"><span class="nav-icon">{}</span>RAW JSON</div>
|
|
151
|
+
<div class="undo-bar">
|
|
152
|
+
<button id="undo-btn" onclick="undo()" disabled>↩ UNDO</button>
|
|
153
|
+
<button id="redo-btn" onclick="redo()" disabled>↪ REDO</button>
|
|
154
|
+
</div>
|
|
155
|
+
</div>
|
|
156
|
+
<div class="main">
|
|
157
|
+
|
|
158
|
+
<div id="panel-aliases" class="panel active">
|
|
159
|
+
<div class="sh">// alias management</div>
|
|
160
|
+
<div class="add-form" id="af" style="display:none">
|
|
161
|
+
<div class="af-grid">
|
|
162
|
+
<div><div class="fl">COMMAND</div><input id="nk" placeholder="ll"/></div>
|
|
163
|
+
<div><div class="fl">EXPANDS TO</div><input id="nv" placeholder="ls -la $*" onkeydown="if(event.key==='Enter')addAlias()"/></div>
|
|
164
|
+
</div>
|
|
165
|
+
<div class="af-btns"><button class="btn btn-p" onclick="addAlias()">SAVE</button><button class="btn" onclick="closeAF()">CANCEL</button></div>
|
|
166
|
+
</div>
|
|
167
|
+
<div style="display:flex;gap:6px;margin-bottom:.85rem">
|
|
168
|
+
<div class="search-row" style="flex:1"><input id="as" placeholder="search aliases..." oninput="renderAliases()"/></div>
|
|
169
|
+
<button class="btn btn-p" onclick="openAF()">+ NEW</button>
|
|
170
|
+
</div>
|
|
171
|
+
<div class="alias-list" id="alias-list"></div>
|
|
172
|
+
</div>
|
|
173
|
+
|
|
174
|
+
<div id="panel-plugins" class="panel">
|
|
175
|
+
<div class="sh">// installed plugins</div>
|
|
176
|
+
<div class="inst-row"><input id="purl" placeholder="https://example.com/plugin.py"/><button class="btn btn-p" onclick="installPlugin()">⬇ INSTALL</button></div>
|
|
177
|
+
<div id="plugin-list"></div>
|
|
178
|
+
</div>
|
|
179
|
+
|
|
180
|
+
<div id="panel-marketplace" class="panel">
|
|
181
|
+
<div class="sh">// dpm marketplace</div>
|
|
182
|
+
<div class="search-row" style="margin-bottom:.85rem"><input id="ms" placeholder="search plugins..." oninput="renderMkt()"/></div>
|
|
183
|
+
<div id="mkt-list"></div>
|
|
184
|
+
</div>
|
|
185
|
+
|
|
186
|
+
<div id="panel-themes" class="panel">
|
|
187
|
+
<div class="sh">// prompt preview</div>
|
|
188
|
+
<div class="prev" id="prev"></div>
|
|
189
|
+
<div class="sh">// theme</div>
|
|
190
|
+
<div class="theme-grid" id="theme-grid"></div>
|
|
191
|
+
<div class="sh">// prompt format</div>
|
|
192
|
+
<div class="fmt-grid" id="fmt-grid"></div>
|
|
193
|
+
<button class="btn btn-p" onclick="saveTheme()">APPLY</button>
|
|
194
|
+
</div>
|
|
195
|
+
|
|
196
|
+
<div id="panel-settings" class="panel">
|
|
197
|
+
<div class="sh">// shell settings</div>
|
|
198
|
+
<div class="card">
|
|
199
|
+
<div class="toggle-row"><div><div class="tl">cleargreet</div><div class="ts">re-run greeter on clear</div></div><label class="toggle"><input type="checkbox" id="tog-cg" onchange="saveSetting('cleargreet',this.checked?'yes':'no')"><div class="toggle-sl"></div></label></div>
|
|
200
|
+
</div>
|
|
201
|
+
<div class="card">
|
|
202
|
+
<div class="sh" style="margin-bottom:.85rem">// commands</div>
|
|
203
|
+
<div class="fl">GREETER</div><input id="gc" placeholder="neofetch" style="width:100%;margin-bottom:.85rem"/>
|
|
204
|
+
<div class="fl">FAREWELL</div><input id="fc" placeholder="echo bye" style="width:100%;margin-bottom:.85rem"/>
|
|
205
|
+
<button class="btn btn-p" onclick="saveCmds()">SAVE</button>
|
|
206
|
+
</div>
|
|
207
|
+
</div>
|
|
208
|
+
|
|
209
|
+
<div id="panel-history" class="panel">
|
|
210
|
+
<div class="sh">// command history</div>
|
|
211
|
+
<div class="search-row" style="margin-bottom:.85rem"><input id="hs" placeholder="search history..." oninput="renderHistory()"/></div>
|
|
212
|
+
<div style="display:flex;gap:6px;margin-bottom:.85rem"><button class="btn" onclick="loadHistory()">REFRESH</button><button class="btn btn-d" onclick="clearHistory()">CLEAR</button></div>
|
|
213
|
+
<div class="hist-list" id="hist-list"></div>
|
|
214
|
+
</div>
|
|
215
|
+
|
|
216
|
+
<div id="panel-bookmarks" class="panel">
|
|
217
|
+
<div class="sh">// directory bookmarks</div>
|
|
218
|
+
<div class="add-form" id="bmf" style="display:none">
|
|
219
|
+
<div class="af-grid">
|
|
220
|
+
<div><div class="fl">NAME</div><input id="bmn" placeholder="projects"/></div>
|
|
221
|
+
<div><div class="fl">PATH</div><input id="bmp" placeholder="~/projects" onkeydown="if(event.key==='Enter')addBm()"/></div>
|
|
222
|
+
</div>
|
|
223
|
+
<div class="af-btns"><button class="btn btn-p" onclick="addBm()">SAVE</button><button class="btn" onclick="document.getElementById('bmf').style.display='none'">CANCEL</button></div>
|
|
224
|
+
</div>
|
|
225
|
+
<div style="display:flex;justify-content:flex-end;margin-bottom:.85rem">
|
|
226
|
+
<button class="btn btn-p" onclick="document.getElementById('bmf').style.display='block';document.getElementById('bmn').focus()">+ NEW</button>
|
|
227
|
+
</div>
|
|
228
|
+
<div class="bm-list" id="bm-list"></div>
|
|
229
|
+
</div>
|
|
230
|
+
|
|
231
|
+
<div id="panel-env" class="panel">
|
|
232
|
+
<div class="sh">// environment variables</div>
|
|
233
|
+
<div class="add-form" id="envf" style="display:none">
|
|
234
|
+
<div class="af-grid">
|
|
235
|
+
<div><div class="fl">KEY</div><input id="envk" placeholder="MY_VAR"/></div>
|
|
236
|
+
<div><div class="fl">VALUE</div><input id="envv" placeholder="value" onkeydown="if(event.key==='Enter')addEnv()"/></div>
|
|
237
|
+
</div>
|
|
238
|
+
<div class="af-btns"><button class="btn btn-p" onclick="addEnv()">SAVE</button><button class="btn" onclick="document.getElementById('envf').style.display='none'">CANCEL</button></div>
|
|
239
|
+
</div>
|
|
240
|
+
<div style="display:flex;justify-content:flex-end;margin-bottom:.85rem">
|
|
241
|
+
<button class="btn btn-p" onclick="document.getElementById('envf').style.display='block';document.getElementById('envk').focus()">+ NEW</button>
|
|
242
|
+
</div>
|
|
243
|
+
<div class="env-list" id="env-list"></div>
|
|
244
|
+
</div>
|
|
245
|
+
|
|
246
|
+
<div id="panel-log" class="panel">
|
|
247
|
+
<div class="sh">// shell log</div>
|
|
248
|
+
<div style="display:flex;gap:6px;margin-bottom:.85rem"><button class="btn" onclick="loadLog()">REFRESH</button><button class="btn btn-d" onclick="clearLog()">CLEAR</button></div>
|
|
249
|
+
<div class="log-box" id="log-box">// no log entries yet — run some commands!</div>
|
|
250
|
+
</div>
|
|
251
|
+
|
|
252
|
+
<div id="panel-wizard" class="panel">
|
|
253
|
+
<div class="sh">// setup wizard</div>
|
|
254
|
+
<div class="wiz-progress" id="wiz-prog"></div>
|
|
255
|
+
<div id="wiz-steps"></div>
|
|
256
|
+
</div>
|
|
257
|
+
|
|
258
|
+
<div id="panel-json" class="panel">
|
|
259
|
+
<div class="sh">// raw config</div>
|
|
260
|
+
<div style="display:flex;gap:6px;margin-bottom:.85rem">
|
|
261
|
+
<button class="btn" onclick="exportCfg()">⬆ EXPORT</button>
|
|
262
|
+
<button class="btn" onclick="document.getElementById('imp-file').click()">⬇ IMPORT</button>
|
|
263
|
+
</div>
|
|
264
|
+
<textarea id="json-raw" spellcheck="false" oninput="validateJson()" style="min-height:350px"></textarea>
|
|
265
|
+
<div class="json-st" id="json-st"></div>
|
|
266
|
+
<div style="display:flex;gap:6px;margin-top:8px">
|
|
267
|
+
<button class="btn btn-p" onclick="saveJson()">SAVE</button>
|
|
268
|
+
<button class="btn" onclick="loadAll()">RELOAD</button>
|
|
269
|
+
</div>
|
|
270
|
+
</div>
|
|
271
|
+
|
|
272
|
+
</div>
|
|
273
|
+
</div>
|
|
274
|
+
|
|
275
|
+
<div class="toast" id="toast"></div>
|
|
276
|
+
<div class="kbdo" id="kbdo" onclick="hideKbd()">
|
|
277
|
+
<div class="kbd-box" onclick="event.stopPropagation()">
|
|
278
|
+
<h2>// KEYBOARD SHORTCUTS</h2>
|
|
279
|
+
<div class="kr"><span>new alias</span><div class="kc"><span class="kk">N</span></div></div>
|
|
280
|
+
<div class="kr"><span>search aliases</span><div class="kc"><span class="kk">/</span></div></div>
|
|
281
|
+
<div class="kr"><span>undo / redo</span><div class="kc"><span class="kk">Ctrl</span><span class="kk">Z</span></div><div class="kc"><span class="kk">Ctrl</span><span class="kk">Y</span></div></div>
|
|
282
|
+
<div class="kr"><span>export</span><div class="kc"><span class="kk">E</span></div></div>
|
|
283
|
+
<div class="kr"><span>import</span><div class="kc"><span class="kk">I</span></div></div>
|
|
284
|
+
<div class="kr"><span>navigate 1-9</span><div class="kc"><span class="kk">1</span><span class="kk">…</span><span class="kk">9</span></div></div>
|
|
285
|
+
<div class="kr"><span>close</span><div class="kc"><span class="kk">Esc</span></div></div>
|
|
286
|
+
<div style="margin-top:1rem;text-align:right"><button class="btn" onclick="hideKbd()">CLOSE</button></div>
|
|
287
|
+
</div>
|
|
288
|
+
</div>
|
|
289
|
+
|
|
290
|
+
<script>
|
|
291
|
+
const THEMES={
|
|
292
|
+
default:{name:'DEFAULT',colors:['#00d787','#5fafff','#00d7af','#d7af5f'],css:{u:'#00d787',h:'#5fafff',p:'#00d7af',b:'#d7af5f',a:'#00d787',e:'#ff5f5f',t:'#888'}},
|
|
293
|
+
fire:{name:'FIRE',colors:['#ff5f00','#ff8700','#ffaf00','#ffaf5f'],css:{u:'#ff5f00',h:'#ff8700',p:'#ffaf00',b:'#ffaf5f',a:'#ff5f00',e:'#ff0000',t:'#888'}},
|
|
294
|
+
ocean:{name:'OCEAN',colors:['#0087ff','#00afff','#00d7ff','#5fafff'],css:{u:'#0087ff',h:'#00afff',p:'#00d7ff',b:'#5fafff',a:'#0087ff',e:'#ff5f5f',t:'#888'}},
|
|
295
|
+
candy:{name:'CANDY',colors:['#ff5faf','#af5fff','#ff87d7','#ffaf5f'],css:{u:'#ff5faf',h:'#af5fff',p:'#ff87d7',b:'#ffaf5f',a:'#ff5faf',e:'#ff0000',t:'#888'}},
|
|
296
|
+
mono:{name:'MONO',colors:['#fff','#aaa','#fff','#aaa'],css:{u:'#fff',h:'#aaa',p:'#fff',b:'#aaa',a:'#fff',e:'#ff5f5f',t:'#555'}},
|
|
297
|
+
cyberpunk:{name:'CYBERPUNK',colors:['#ffff00','#ff00ff','#00ffff','#ff00ff'],css:{u:'#ffff00',h:'#ff00ff',p:'#00ffff',b:'#ff00ff',a:'#ffff00',e:'#ff0000',t:'#888'}},
|
|
298
|
+
nord:{name:'NORD',colors:['#88c0d0','#81a1c1','#8fbcbb','#ebcb8b'],css:{u:'#88c0d0',h:'#81a1c1',p:'#8fbcbb',b:'#ebcb8b',a:'#88c0d0',e:'#bf616a',t:'#616e88'}},
|
|
299
|
+
dracula:{name:'DRACULA',colors:['#ff79c6','#bd93f9','#50fa7b','#f1fa8c'],css:{u:'#ff79c6',h:'#bd93f9',p:'#50fa7b',b:'#f1fa8c',a:'#ff79c6',e:'#ff5555',t:'#44475a'}},
|
|
300
|
+
solarized:{name:'SOLARIZED',colors:['#859900','#268bd2','#2aa198','#b58900'],css:{u:'#859900',h:'#268bd2',p:'#2aa198',b:'#b58900',a:'#859900',e:'#dc322f',t:'#657b83'}},
|
|
301
|
+
blood:{name:'BLOOD',colors:['#ff0000','#aa0000','#ff3333','#ff6666'],css:{u:'#ff0000',h:'#aa0000',p:'#ff3333',b:'#ff6666',a:'#ff0000',e:'#fff',t:'#661111'}},
|
|
302
|
+
ice:{name:'ICE',colors:['#fff','#00ffff','#aaffff','#00ffff'],css:{u:'#fff',h:'#00ffff',p:'#aaffff',b:'#00ffff',a:'#fff',e:'#ff5f5f',t:'#557777'}},
|
|
303
|
+
};
|
|
304
|
+
const FMTS={default:'default',minimal:'minimal',compact:'compact',classic:'classic'};
|
|
305
|
+
const MKT=[
|
|
306
|
+
{name:'dpm',desc:'plugin manager — install from registry',url:'https://raw.githubusercontent.com/daps-shell/plugins/main/dpm.py'},
|
|
307
|
+
{name:'gitstatus',desc:'enhanced git info with diff counts',url:'https://raw.githubusercontent.com/daps-shell/plugins/main/gitstatus.py'},
|
|
308
|
+
{name:'weather',desc:'local weather in your terminal',url:'https://raw.githubusercontent.com/daps-shell/plugins/main/weather.py'},
|
|
309
|
+
{name:'calc',desc:'quick math — calc 2+2',url:'https://raw.githubusercontent.com/daps-shell/plugins/main/calc.py'},
|
|
310
|
+
{name:'note',desc:'quick notes from anywhere',url:'https://raw.githubusercontent.com/daps-shell/plugins/main/note.py'},
|
|
311
|
+
{name:'timer',desc:'countdown timer in the shell',url:'https://raw.githubusercontent.com/daps-shell/plugins/main/timer.py'},
|
|
312
|
+
{name:'todo',desc:'simple task list in the terminal',url:'https://raw.githubusercontent.com/daps-shell/plugins/main/todo.py'},
|
|
313
|
+
{name:'sysinfo',desc:'display system info on demand',url:'https://raw.githubusercontent.com/daps-shell/plugins/main/sysinfo.py'},
|
|
314
|
+
];
|
|
315
|
+
const WIZARD_STEPS=[
|
|
316
|
+
{title:'welcome',html:'<p style="color:var(--g2);font-size:13px;line-height:1.8">Welcome to daps! This wizard will help you set up your shell.<br><br>Use the buttons below to step through setup.</p>'},
|
|
317
|
+
{title:'username & host',html:'<div class="fl">DISPLAY NAME (optional override)</div><input id="wiz-user" placeholder="akaime" style="width:100%;margin-bottom:.85rem"/><div class="fl">HOSTNAME OVERRIDE</div><input id="wiz-host" placeholder="Jupiter" style="width:100%"/>'},
|
|
318
|
+
{title:'greeter command',html:'<div class="fl">GREETER COMMAND</div><input id="wiz-greeter" placeholder="neofetch" style="width:100%;margin-bottom:.5rem"/><div style="font-size:10px;color:var(--g3)">runs on startup — try neofetch, fastfetch, or echo</div>'},
|
|
319
|
+
{title:'pick a theme',html:'<div id="wiz-theme-grid" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(100px,1fr));gap:6px"></div>'},
|
|
320
|
+
{title:'done!',html:'<p style="color:var(--g2);font-size:13px;line-height:1.8">All set! Your config has been saved.<br><br>Run <span style="color:var(--g)">daps reload</span> in your terminal to apply changes.</p>'},
|
|
321
|
+
];
|
|
322
|
+
|
|
323
|
+
let cfg={aliases:{},cleargreet:'no',theme:'default'},selTheme='default',selFmt='default';
|
|
324
|
+
let undoStack=[],redoStack=[],editKey=null,wizStep=0,logEntries=[];
|
|
325
|
+
|
|
326
|
+
async function api(p,m='GET',b=null){
|
|
327
|
+
const o={method:m,headers:{'Content-Type':'application/json'}};
|
|
328
|
+
if(b)o.body=JSON.stringify(b);
|
|
329
|
+
return (await fetch(p,o)).json();
|
|
330
|
+
}
|
|
331
|
+
function toast(msg,err=false){
|
|
332
|
+
const t=document.getElementById('toast');t.textContent=msg;t.className='toast'+(err?' err':'');
|
|
333
|
+
t.classList.add('show');clearTimeout(t._t);t._t=setTimeout(()=>t.classList.remove('show'),2200);
|
|
334
|
+
}
|
|
335
|
+
function setSt(s){document.getElementById('save-st').textContent=s}
|
|
336
|
+
function nav(name){
|
|
337
|
+
const names=['aliases','plugins','marketplace','themes','settings','history','bookmarks','env','log','wizard','json'];
|
|
338
|
+
document.querySelectorAll('.nav-item').forEach((el,i)=>el.classList.toggle('active',names[i]===name));
|
|
339
|
+
document.querySelectorAll('.panel').forEach(p=>p.classList.remove('active'));
|
|
340
|
+
document.getElementById('panel-'+name).classList.add('active');
|
|
341
|
+
if(name==='themes')updatePrev();
|
|
342
|
+
if(name==='history')loadHistory();
|
|
343
|
+
if(name==='bookmarks')renderBm();
|
|
344
|
+
if(name==='env')renderEnv();
|
|
345
|
+
if(name==='log')loadLog();
|
|
346
|
+
if(name==='wizard')initWizard();
|
|
347
|
+
}
|
|
348
|
+
function pushU(){undoStack.push(JSON.stringify(cfg));if(undoStack.length>50)undoStack.shift();redoStack=[];updUndoBtns()}
|
|
349
|
+
function undo(){if(!undoStack.length)return;redoStack.push(JSON.stringify(cfg));cfg=JSON.parse(undoStack.pop());updUndoBtns();renderAll();saveConfig(true);toast('undone')}
|
|
350
|
+
function redo(){if(!redoStack.length)return;undoStack.push(JSON.stringify(cfg));cfg=JSON.parse(redoStack.pop());updUndoBtns();renderAll();saveConfig(true);toast('redone')}
|
|
351
|
+
function updUndoBtns(){document.getElementById('undo-btn').disabled=!undoStack.length;document.getElementById('redo-btn').disabled=!redoStack.length}
|
|
352
|
+
function renderAll(){renderAliases();renderPlugins();renderThemes();renderFmts();renderSettings();updateJsonEditor()}
|
|
353
|
+
function renderAliases(){
|
|
354
|
+
const list=document.getElementById('alias-list'),q=document.getElementById('as').value.toLowerCase();
|
|
355
|
+
const entries=Object.entries(cfg.aliases||{}).filter(([k,v])=>!q||k.includes(q)||v.toLowerCase().includes(q));
|
|
356
|
+
list.innerHTML=entries.length?entries.map(([k,v])=>`<div class="alias-item"><div class="akey">${k}</div><div class="aval">${v}</div><button class="btn abtn" onclick="editAlias('${k}')">EDIT</button><button class="btn btn-d abtn" onclick="delAlias('${k}')">DEL</button></div>`).join(''):'<div style="color:var(--g3);font-size:11px;padding:.85rem 0">// no aliases'+(q?' matching':'')+'</div>';
|
|
357
|
+
}
|
|
358
|
+
function openAF(k='',v=''){editKey=k||null;document.getElementById('nk').value=k;document.getElementById('nv').value=v;document.getElementById('af').style.display='block';document.getElementById(k?'nv':'nk').focus()}
|
|
359
|
+
function closeAF(){document.getElementById('af').style.display='none';editKey=null}
|
|
360
|
+
function editAlias(k){openAF(k,cfg.aliases[k])}
|
|
361
|
+
async function addAlias(){
|
|
362
|
+
const k=document.getElementById('nk').value.trim(),v=document.getElementById('nv').value.trim();
|
|
363
|
+
if(!k||!v){toast('key and value required',true);return}
|
|
364
|
+
pushU();if(editKey&&editKey!==k)delete cfg.aliases[editKey];
|
|
365
|
+
cfg.aliases[k]=v;await saveConfig();renderAliases();closeAF();toast('alias saved');
|
|
366
|
+
}
|
|
367
|
+
async function delAlias(k){pushU();delete cfg.aliases[k];await saveConfig();renderAliases();toast(`'${k}' removed`)}
|
|
368
|
+
function renderPlugins(){
|
|
369
|
+
const list=document.getElementById('plugin-list'),plugins=cfg._plugins||[];
|
|
370
|
+
list.innerHTML=plugins.length?plugins.map(p=>`<div class="plugin-item"><div><div class="pname">${p.name}</div><div class="pdesc">${p.desc||'no description'}</div></div><button class="btn btn-d" style="font-size:9px;padding:4px 8px" onclick="removePlugin('${p.name}')">REMOVE</button></div>`).join(''):'<div style="color:var(--g3);font-size:11px;padding:.85rem 0">// no plugins loaded</div>';
|
|
371
|
+
}
|
|
372
|
+
async function installPlugin(){
|
|
373
|
+
const url=document.getElementById('purl').value.trim();if(!url)return;
|
|
374
|
+
setSt('installing...');const r=await api('/api/plugin/install','POST',{url});setSt('connected');
|
|
375
|
+
if(r.ok){toast('installed');document.getElementById('purl').value='';loadAll();}else toast(r.error||'failed',true);
|
|
376
|
+
}
|
|
377
|
+
async function removePlugin(name){const r=await api('/api/plugin/remove','POST',{name});if(r.ok){toast('removed');loadAll();}else toast('failed',true)}
|
|
378
|
+
function renderMkt(){
|
|
379
|
+
const list=document.getElementById('mkt-list'),q=document.getElementById('ms').value.toLowerCase();
|
|
380
|
+
const inst=(cfg._plugins||[]).map(p=>p.name);
|
|
381
|
+
const items=MKT.filter(p=>!q||p.name.includes(q)||p.desc.toLowerCase().includes(q));
|
|
382
|
+
list.innerHTML=items.map(p=>{const isI=inst.includes(p.name);return`<div class="mkt-item${isI?' inst':''}"><div><div class="pname">${p.name}${isI?' <span style="font-size:9px;color:var(--g3)">[installed]</span>':''}</div><div class="pdesc">${p.desc}</div></div><button class="btn${isI?' btn-d':' btn-p'}" style="font-size:9px;white-space:nowrap" onclick="${isI?`removePlugin('${p.name}')`:`instFromMkt('${p.url}')`}">${isI?'REMOVE':'INSTALL'}</button></div>`}).join('');
|
|
383
|
+
}
|
|
384
|
+
async function instFromMkt(url){document.getElementById('purl').value=url;await installPlugin();renderMkt()}
|
|
385
|
+
function renderThemes(){
|
|
386
|
+
document.getElementById('theme-grid').innerHTML=Object.entries(THEMES).map(([k,t])=>`<div class="theme-card${k===selTheme?' sel':''}" onclick="selT('${k}')"><div class="tdots">${t.colors.map(c=>`<div class="tdot" style="background:${c};box-shadow:0 0 3px ${c}66"></div>`).join('')}</div><div class="tname">${t.name}</div></div>`).join('');
|
|
387
|
+
updatePrev();
|
|
388
|
+
}
|
|
389
|
+
function renderFmts(){
|
|
390
|
+
document.getElementById('fmt-grid').innerHTML=Object.keys(FMTS).map(k=>`<div class="fmt-card${k===selFmt?' sel':''}" onclick="selF('${k}')">${k.toUpperCase()}</div>`).join('');
|
|
391
|
+
}
|
|
392
|
+
function selT(n){selTheme=n;renderThemes()}
|
|
393
|
+
function selF(n){selFmt=n;renderFmts();updatePrev()}
|
|
394
|
+
function updatePrev(){
|
|
395
|
+
const c=THEMES[selTheme]?.css||THEMES.default.css;
|
|
396
|
+
const p=document.getElementById('prev');if(!p)return;
|
|
397
|
+
const fmts={
|
|
398
|
+
default:`<span style="color:${c.t}">[1.2s]</span> - <span style="color:${c.u}">user</span>@<span style="color:${c.h}">hostname</span> <span style="color:${c.u}>($)</span> - <span style="color:${c.p}">~/projects</span> <span style="color:${c.b}">[main*]</span><br><span style="color:${c.a}">></span> ls`,
|
|
399
|
+
minimal:`<span style="color:${c.u}">user</span> <span style="color:${c.p}">~/projects</span> <span style="color:${c.b}">[main]</span><br><span style="color:${c.a}">></span> ls`,
|
|
400
|
+
compact:`<span style="color:${c.u}">$</span> <span style="color:${c.p}">~/projects</span> <span style="color:${c.b}">[main]</span><br><span style="color:${c.a}">></span> ls`,
|
|
401
|
+
classic:`<span style="color:var(--g3)">[</span><span style="color:${c.u}">user</span>@<span style="color:${c.h}">hostname</span> <span style="color:${c.p}">~/projects</span><span style="color:var(--g3)">]</span><span style="color:${c.b}">(main)</span><span style="color:${c.u}">$</span> ls`,
|
|
402
|
+
};
|
|
403
|
+
p.innerHTML=fmts[selFmt]||fmts.default;
|
|
404
|
+
}
|
|
405
|
+
async function saveTheme(){pushU();cfg.theme=selTheme;cfg.prompt_format=selFmt;await saveConfig();toast(`theme: ${selTheme} / format: ${selFmt}`)}
|
|
406
|
+
function renderSettings(){document.getElementById('tog-cg').checked=cfg.cleargreet==='yes';document.getElementById('gc').value=cfg.greeter||'';document.getElementById('fc').value=cfg.farewell||''}
|
|
407
|
+
async function saveSetting(k,v){pushU();cfg[k]=v;await saveConfig();toast(`${k} updated`)}
|
|
408
|
+
async function saveCmds(){pushU();const g=document.getElementById('gc').value.trim(),f=document.getElementById('fc').value.trim();if(g)cfg.greeter=g;else delete cfg.greeter;if(f)cfg.farewell=f;else delete cfg.farewell;await saveConfig();toast('saved')}
|
|
409
|
+
async function loadHistory(){
|
|
410
|
+
const r=await api('/api/history');
|
|
411
|
+
const lines=(r.lines||[]).filter(Boolean);
|
|
412
|
+
renderHistory(lines);
|
|
413
|
+
}
|
|
414
|
+
function renderHistory(lines){
|
|
415
|
+
if(!lines){lines=(document.getElementById('hist-list')._cache||[])}
|
|
416
|
+
document.getElementById('hist-list')._cache=lines;
|
|
417
|
+
const q=document.getElementById('hs').value.toLowerCase();
|
|
418
|
+
const filtered=lines.filter(l=>!q||l.toLowerCase().includes(q));
|
|
419
|
+
document.getElementById('hist-list').innerHTML=filtered.length?filtered.slice(-200).reverse().map((l,i)=>`<div class="hist-item" onclick="copyHist('${l.replace(/'/g,"\\'")}')"><span class="hist-num">${filtered.length-i}</span><span>${l}</span></div>`).join(''):'<div style="color:var(--g3);font-size:11px;padding:.85rem 0">// no history</div>';
|
|
420
|
+
}
|
|
421
|
+
function copyHist(cmd){navigator.clipboard.writeText(cmd).then(()=>toast('copied to clipboard'))}
|
|
422
|
+
async function clearHistory(){const r=await api('/api/history/clear','POST');if(r.ok){toast('history cleared');loadHistory()}else toast('failed',true)}
|
|
423
|
+
function renderBm(){
|
|
424
|
+
const bm=cfg.bookmarks||{};
|
|
425
|
+
document.getElementById('bm-list').innerHTML=Object.entries(bm).length?Object.entries(bm).map(([k,v])=>`<div class="bm-item"><div class="bm-name">${k}</div><div class="bm-path">${v}</div><button class="btn abtn" onclick="">EDIT</button><button class="btn btn-d abtn" onclick="delBm('${k}')">DEL</button></div>`).join(''):'<div style="color:var(--g3);font-size:11px;padding:.85rem 0">// no bookmarks</div>';
|
|
426
|
+
}
|
|
427
|
+
async function addBm(){
|
|
428
|
+
const n=document.getElementById('bmn').value.trim(),p=document.getElementById('bmp').value.trim();
|
|
429
|
+
if(!n||!p){toast('name and path required',true);return}
|
|
430
|
+
pushU();cfg.bookmarks=cfg.bookmarks||{};cfg.bookmarks[n]=p;await saveConfig();renderBm();document.getElementById('bmf').style.display='none';toast('bookmark saved');
|
|
431
|
+
}
|
|
432
|
+
async function delBm(k){pushU();delete cfg.bookmarks[k];await saveConfig();renderBm();toast('removed')}
|
|
433
|
+
function renderEnv(){
|
|
434
|
+
const env=cfg.env||{};
|
|
435
|
+
document.getElementById('env-list').innerHTML=Object.entries(env).length?Object.entries(env).map(([k,v])=>`<div class="env-item"><div class="ekey">${k}</div><div class="eval">${v}</div><button class="btn btn-d abtn" onclick="delEnv('${k}')">DEL</button></div>`).join(''):'<div style="color:var(--g3);font-size:11px;padding:.85rem 0">// no env vars set</div>';
|
|
436
|
+
}
|
|
437
|
+
async function addEnv(){
|
|
438
|
+
const k=document.getElementById('envk').value.trim(),v=document.getElementById('envv').value.trim();
|
|
439
|
+
if(!k||!v){toast('key and value required',true);return}
|
|
440
|
+
pushU();cfg.env=cfg.env||{};cfg.env[k]=v;await saveConfig();renderEnv();document.getElementById('envf').style.display='none';toast('env var saved');
|
|
441
|
+
}
|
|
442
|
+
async function delEnv(k){pushU();delete cfg.env[k];await saveConfig();renderEnv();toast('removed')}
|
|
443
|
+
async function loadLog(){
|
|
444
|
+
const r=await api('/api/log');
|
|
445
|
+
const entries=r.entries||[];
|
|
446
|
+
const box=document.getElementById('log-box');
|
|
447
|
+
if(!entries.length){box.innerHTML='// no log entries yet — run some commands!';return}
|
|
448
|
+
box.innerHTML=entries.slice(-100).reverse().map(e=>`<div><span class="log-ts">${e.ts||''}</span> <span class="log-cmd">> ${e.cmd||''}</span>${e.code&&e.code!=='0'?` <span class="log-err">[${e.code}]</span>`:''}</div>`).join('');
|
|
449
|
+
}
|
|
450
|
+
async function clearLog(){const r=await api('/api/log/clear','POST');if(r.ok){toast('log cleared');loadLog()}else toast('failed',true)}
|
|
451
|
+
function initWizard(){
|
|
452
|
+
wizStep=0;
|
|
453
|
+
const prog=document.getElementById('wiz-prog');
|
|
454
|
+
prog.innerHTML=WIZARD_STEPS.map((_,i)=>`<div class="wiz-dot${i===0?' current':''}" id="wd${i}"></div>`).join('');
|
|
455
|
+
renderWizStep();
|
|
456
|
+
}
|
|
457
|
+
function renderWizStep(){
|
|
458
|
+
const s=WIZARD_STEPS[wizStep];
|
|
459
|
+
document.getElementById('wiz-steps').innerHTML=`<div class="sh">// ${s.title}</div><div style="margin-bottom:1.25rem">${s.html}</div><div style="display:flex;gap:6px">${wizStep>0?'<button class="btn" onclick="wizNav(-1)">BACK</button>':''}<button class="btn btn-p" onclick="wizNav(1)">${wizStep===WIZARD_STEPS.length-1?'FINISH':'NEXT'}</button></div>`;
|
|
460
|
+
if(wizStep===3){
|
|
461
|
+
document.getElementById('wiz-theme-grid').innerHTML=Object.entries(THEMES).map(([k,t])=>`<div class="theme-card${k===selTheme?' sel':''}" onclick="selT('${k}');document.querySelectorAll('#wiz-theme-grid .theme-card').forEach(c=>c.classList.remove('sel'));this.classList.add('sel')"><div class="tdots">${t.colors.map(c=>`<div class="tdot" style="background:${c}"></div>`).join('')}</div><div class="tname" style="font-size:8px">${t.name}</div></div>`).join('');
|
|
462
|
+
}
|
|
463
|
+
WIZARD_STEPS.forEach((_,i)=>{const d=document.getElementById('wd'+i);if(d){d.className='wiz-dot'+(i<wizStep?' done':i===wizStep?' current':'')}});
|
|
464
|
+
}
|
|
465
|
+
async function wizNav(dir){
|
|
466
|
+
if(dir===1){
|
|
467
|
+
if(wizStep===1){const u=document.getElementById('wiz-user')?.value.trim(),h=document.getElementById('wiz-host')?.value.trim();if(h){pushU();cfg._hostname_hint=h;}}
|
|
468
|
+
if(wizStep===2){const g=document.getElementById('wiz-greeter')?.value.trim();if(g){pushU();cfg.greeter=g;cfg.cleargreet='yes';}}
|
|
469
|
+
if(wizStep===3){pushU();cfg.theme=selTheme;}
|
|
470
|
+
if(wizStep===WIZARD_STEPS.length-1){await saveConfig();toast('config saved!');nav('aliases');return}
|
|
471
|
+
}
|
|
472
|
+
wizStep=Math.max(0,Math.min(WIZARD_STEPS.length-1,wizStep+dir));
|
|
473
|
+
renderWizStep();
|
|
474
|
+
}
|
|
475
|
+
function updateJsonEditor(){const s={...cfg};delete s._plugins;document.getElementById('json-raw').value=JSON.stringify(s,null,2);validateJson()}
|
|
476
|
+
function validateJson(){const el=document.getElementById('json-st');try{JSON.parse(document.getElementById('json-raw').value);el.textContent='// valid json';el.className='json-st ok'}catch(e){el.textContent='// '+e.message;el.className='json-st err'}}
|
|
477
|
+
async function saveJson(){try{const p=JSON.parse(document.getElementById('json-raw').value);pushU();cfg=p;await saveConfig();toast('saved');loadAll()}catch(e){toast('invalid json',true)}}
|
|
478
|
+
async function saveConfig(silent=false){
|
|
479
|
+
const s={...cfg};delete s._plugins;setSt('saving...');
|
|
480
|
+
try{const r=await api('/api/config','POST',s);setSt(r.ok?'saved':'error');setTimeout(()=>setSt('connected'),1400);if(!r.ok&&!silent)toast('save failed',true)}catch(e){setSt('error')}
|
|
481
|
+
updateJsonEditor();
|
|
482
|
+
}
|
|
483
|
+
function exportCfg(){const s={...cfg};delete s._plugins;const a=document.createElement('a');a.href=URL.createObjectURL(new Blob([JSON.stringify(s,null,2)],{type:'application/json'}));a.download='daps-config.json';a.click();toast('exported')}
|
|
484
|
+
function importCfg(e){const f=e.target.files[0];if(!f)return;const r=new FileReader();r.onload=async ev=>{try{const p=JSON.parse(ev.target.result);pushU();cfg={...cfg,...p};await saveConfig();renderAll();toast('imported')}catch(e){toast('invalid json',true)}};r.readAsText(f);e.target.value=''}
|
|
485
|
+
function showKbd(){document.getElementById('kbdo').classList.add('show')}
|
|
486
|
+
function hideKbd(){document.getElementById('kbdo').classList.remove('show')}
|
|
487
|
+
document.addEventListener('keydown',e=>{
|
|
488
|
+
const tag=document.activeElement.tagName,typing=tag==='INPUT'||tag==='TEXTAREA';
|
|
489
|
+
if(e.key==='Escape'){hideKbd();closeAF();return}
|
|
490
|
+
if(e.ctrlKey&&e.key==='z'){e.preventDefault();undo();return}
|
|
491
|
+
if(e.ctrlKey&&e.key==='y'){e.preventDefault();redo();return}
|
|
492
|
+
if(typing)return;
|
|
493
|
+
if(e.key==='?'){showKbd();return}
|
|
494
|
+
if(e.key==='n'||e.key==='N'){openAF();return}
|
|
495
|
+
if(e.key==='/'){nav('aliases');setTimeout(()=>document.getElementById('as').focus(),50);return}
|
|
496
|
+
if(e.key==='e'||e.key==='E'){exportCfg();return}
|
|
497
|
+
if(e.key==='i'||e.key==='I'){document.getElementById('imp-file').click();return}
|
|
498
|
+
const nm=['aliases','plugins','marketplace','themes','settings','history','bookmarks','env','log','wizard','json'];
|
|
499
|
+
const i=parseInt(e.key)-1;if(i>=0&&i<nm.length)nav(nm[i]);
|
|
500
|
+
});
|
|
501
|
+
async function loadAll(){
|
|
502
|
+
try{const r=await api('/api/config');cfg=r;selTheme=cfg.theme||'default';selFmt=cfg.prompt_format||'default';renderAll();updUndoBtns()}
|
|
503
|
+
catch(e){toast('could not connect',true)}
|
|
504
|
+
}
|
|
505
|
+
(function(){
|
|
506
|
+
const c=document.getElementById('matrix'),ctx=c.getContext('2d');let w,h,cols,drops;
|
|
507
|
+
const chars='アイウエオカキクケコサシスセソ0123456789ABCDEF'.split('');
|
|
508
|
+
function resize(){w=c.width=innerWidth;h=c.height=innerHeight;cols=Math.floor(w/15);drops=Array(cols).fill(1)}
|
|
509
|
+
resize();window.addEventListener('resize',resize);
|
|
510
|
+
setInterval(()=>{ctx.fillStyle='rgba(0,5,0,0.05)';ctx.fillRect(0,0,w,h);ctx.fillStyle='#00ff41';ctx.font='12px Share Tech Mono';drops.forEach((y,i)=>{ctx.fillText(chars[Math.floor(Math.random()*chars.length)],i*15,y*15);if(y*15>h&&Math.random()>.975)drops[i]=0;drops[i]++})},50);
|
|
511
|
+
})();
|
|
512
|
+
loadAll();
|
|
513
|
+
</script>
|
|
514
|
+
</body>
|
|
515
|
+
</html>
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import os, json, threading, webbrowser, importlib.util, datetime
|
|
2
|
+
from http.server import HTTPServer, BaseHTTPRequestHandler
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
CONF = Path.home() / ".config" / "daps"
|
|
6
|
+
CONF_FILE = CONF / "config.json"
|
|
7
|
+
PLUGIN_DIR = CONF / "plugins"
|
|
8
|
+
LOG_FILE = CONF / "shell.log"
|
|
9
|
+
HISTORY_FILE = Path.home() / ".daps.history"
|
|
10
|
+
HTML = Path(__file__).parent / "ui.html"
|
|
11
|
+
|
|
12
|
+
def read_config():
|
|
13
|
+
if not CONF_FILE.exists(): return {"aliases":{}, "cleargreet":"no"}
|
|
14
|
+
with open(CONF_FILE) as f: return json.load(f)
|
|
15
|
+
|
|
16
|
+
def write_config(data):
|
|
17
|
+
with open(CONF_FILE,"w") as f: json.dump(data, f, indent=2)
|
|
18
|
+
|
|
19
|
+
def get_plugins():
|
|
20
|
+
plugins = []
|
|
21
|
+
if not PLUGIN_DIR.exists(): return plugins
|
|
22
|
+
for f in PLUGIN_DIR.iterdir():
|
|
23
|
+
if f.suffix != ".py": continue
|
|
24
|
+
desc = "no description"
|
|
25
|
+
try:
|
|
26
|
+
spec = importlib.util.spec_from_file_location(f.stem, f)
|
|
27
|
+
mod = importlib.util.module_from_spec(spec)
|
|
28
|
+
spec.loader.exec_module(mod)
|
|
29
|
+
doc = getattr(mod, "__doc__", None) or (mod.run.__doc__ if hasattr(mod, "run") else None)
|
|
30
|
+
if doc: desc = doc.strip()
|
|
31
|
+
except Exception: pass
|
|
32
|
+
plugins.append({"name": f.stem, "desc": desc, "file": str(f)})
|
|
33
|
+
return plugins
|
|
34
|
+
|
|
35
|
+
def install_plugin(url):
|
|
36
|
+
import urllib.request
|
|
37
|
+
name = url.split("/")[-1]
|
|
38
|
+
if not name.endswith(".py"): name += ".py"
|
|
39
|
+
dest = PLUGIN_DIR / name
|
|
40
|
+
try: urllib.request.urlretrieve(url, dest); return True, None
|
|
41
|
+
except Exception as e: return False, str(e)
|
|
42
|
+
|
|
43
|
+
def remove_plugin(name):
|
|
44
|
+
f = PLUGIN_DIR / f"{name}.py"
|
|
45
|
+
if f.exists(): f.unlink(); return True
|
|
46
|
+
return False
|
|
47
|
+
|
|
48
|
+
def read_history():
|
|
49
|
+
if not HISTORY_FILE.exists(): return []
|
|
50
|
+
try:
|
|
51
|
+
lines = HISTORY_FILE.read_text().splitlines()
|
|
52
|
+
return [l for l in lines if l.strip() and not l.startswith("+")]
|
|
53
|
+
except Exception: return []
|
|
54
|
+
|
|
55
|
+
def clear_history():
|
|
56
|
+
if HISTORY_FILE.exists(): HISTORY_FILE.write_text("")
|
|
57
|
+
return True
|
|
58
|
+
|
|
59
|
+
def read_log():
|
|
60
|
+
if not LOG_FILE.exists(): return []
|
|
61
|
+
try:
|
|
62
|
+
entries = []
|
|
63
|
+
for line in LOG_FILE.read_text().splitlines():
|
|
64
|
+
try: entries.append(json.loads(line))
|
|
65
|
+
except Exception: pass
|
|
66
|
+
return entries
|
|
67
|
+
except Exception: return []
|
|
68
|
+
|
|
69
|
+
def clear_log():
|
|
70
|
+
if LOG_FILE.exists(): LOG_FILE.write_text("")
|
|
71
|
+
return True
|
|
72
|
+
|
|
73
|
+
class Handler(BaseHTTPRequestHandler):
|
|
74
|
+
def log_message(self, *_): pass
|
|
75
|
+
|
|
76
|
+
def send_json(self, data, code=200):
|
|
77
|
+
body = json.dumps(data).encode()
|
|
78
|
+
self.send_response(code)
|
|
79
|
+
self.send_header("Content-Type","application/json")
|
|
80
|
+
self.send_header("Content-Length",len(body))
|
|
81
|
+
self.send_header("Access-Control-Allow-Origin","*")
|
|
82
|
+
self.end_headers()
|
|
83
|
+
self.wfile.write(body)
|
|
84
|
+
|
|
85
|
+
def send_html(self):
|
|
86
|
+
body = HTML.read_bytes()
|
|
87
|
+
self.send_response(200)
|
|
88
|
+
self.send_header("Content-Type","text/html")
|
|
89
|
+
self.send_header("Content-Length",len(body))
|
|
90
|
+
self.end_headers()
|
|
91
|
+
self.wfile.write(body)
|
|
92
|
+
|
|
93
|
+
def do_GET(self):
|
|
94
|
+
if self.path in ("/","/index.html"): self.send_html()
|
|
95
|
+
elif self.path == "/api/config":
|
|
96
|
+
cfg = read_config(); cfg["_plugins"] = get_plugins(); self.send_json(cfg)
|
|
97
|
+
elif self.path == "/api/history": self.send_json({"lines": read_history()})
|
|
98
|
+
elif self.path == "/api/log": self.send_json({"entries": read_log()})
|
|
99
|
+
else: self.send_json({"error":"not found"},404)
|
|
100
|
+
|
|
101
|
+
def do_POST(self):
|
|
102
|
+
length = int(self.headers.get("Content-Length",0))
|
|
103
|
+
body = json.loads(self.rfile.read(length)) if length else {}
|
|
104
|
+
if self.path == "/api/config":
|
|
105
|
+
body.pop("_plugins",None); write_config(body); self.send_json({"ok":True})
|
|
106
|
+
elif self.path == "/api/plugin/install":
|
|
107
|
+
ok, err = install_plugin(body.get("url","")); self.send_json({"ok":ok,"error":err})
|
|
108
|
+
elif self.path == "/api/plugin/remove":
|
|
109
|
+
self.send_json({"ok": remove_plugin(body.get("name",""))})
|
|
110
|
+
elif self.path == "/api/history/clear":
|
|
111
|
+
self.send_json({"ok": clear_history()})
|
|
112
|
+
elif self.path == "/api/log/clear":
|
|
113
|
+
self.send_json({"ok": clear_log()})
|
|
114
|
+
else: self.send_json({"error":"not found"},404)
|
|
115
|
+
|
|
116
|
+
def do_OPTIONS(self):
|
|
117
|
+
self.send_response(200)
|
|
118
|
+
self.send_header("Access-Control-Allow-Origin","*")
|
|
119
|
+
self.send_header("Access-Control-Allow-Methods","GET, POST, OPTIONS")
|
|
120
|
+
self.send_header("Access-Control-Allow-Headers","Content-Type")
|
|
121
|
+
self.end_headers()
|
|
122
|
+
|
|
123
|
+
def launch(port=6473):
|
|
124
|
+
server = HTTPServer(("127.0.0.1", port), Handler)
|
|
125
|
+
url = f"http://127.0.0.1:{port}"
|
|
126
|
+
print(f"\033[96mdaps\033[0m config ui → {url}")
|
|
127
|
+
print(f"\033[96mdaps\033[0m ctrl+c to stop")
|
|
128
|
+
threading.Timer(0.3, lambda: webbrowser.open(url)).start()
|
|
129
|
+
try: server.serve_forever()
|
|
130
|
+
except KeyboardInterrupt: print("\n\033[96mdaps\033[0m stopped")
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "daps-shell"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.2.3"
|
|
8
8
|
description = "A customisable Python shell with plugins, aliases, and syntax highlighting"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -19,3 +19,6 @@ daps = "daps.__main__:main"
|
|
|
19
19
|
[tool.setuptools.packages.find]
|
|
20
20
|
where = ["."]
|
|
21
21
|
include = ["daps*"]
|
|
22
|
+
|
|
23
|
+
[tool.setuptools.package-data]
|
|
24
|
+
daps = ["*.html"]
|
|
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
|