@pm-2001/shellup 0.1.0 → 0.2.0
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.
- package/README.md +14 -1
- package/dist/cli.js +70 -14
- package/package.json +1 -1
- package/runtime/options.zsh +12 -1
package/README.md
CHANGED
|
@@ -26,6 +26,15 @@ background process, repainting when the result lands.
|
|
|
26
26
|
> npm test 4s
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
+
**Suggestions as you type.** The rest of the command appears in grey, pulled from
|
|
30
|
+
what you've actually run before — press <kbd>→</kbd> to accept it, <kbd>Alt</kbd>+<kbd>→</kbd>
|
|
31
|
+
to take one word. When history has nothing, it falls back to the completion system, so
|
|
32
|
+
`git ` still suggests a subcommand on a brand-new machine.
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
$ git push origin main ← "h origin main" is grey; → accepts
|
|
36
|
+
```
|
|
37
|
+
|
|
29
38
|
**Three themes**, two of which need no special font:
|
|
30
39
|
|
|
31
40
|
| theme | needs a Nerd Font | |
|
|
@@ -38,7 +47,7 @@ background process, repainting when the result lands.
|
|
|
38
47
|
[eza](https://github.com/eza-community/eza), [bat](https://github.com/sharkdp/bat),
|
|
39
48
|
[fzf](https://github.com/junegunn/fzf), [zoxide](https://github.com/ajeetdsouza/zoxide),
|
|
40
49
|
[fd](https://github.com/sharkdp/fd), [ripgrep](https://github.com/BurntSushi/ripgrep),
|
|
41
|
-
[git-delta](https://github.com/dandavison/delta), lazygit, btop, jq, tldr.
|
|
50
|
+
[git-delta](https://github.com/dandavison/delta), [zsh-autosuggestions](https://github.com/zsh-users/zsh-autosuggestions), lazygit, btop, jq, tldr.
|
|
42
51
|
|
|
43
52
|
**zsh defaults worth having**: 100k lines of shared, deduplicated history; completion
|
|
44
53
|
with case-insensitive matching and a menu; `auto_cd` and a directory stack; prefix-aware
|
|
@@ -83,6 +92,10 @@ leaves the file byte-for-byte as it was.
|
|
|
83
92
|
- **`custom.zsh` is sourced last**, so anything you write there wins over shellup's
|
|
84
93
|
defaults without forking anything.
|
|
85
94
|
- **`compinit` checks its cache once a day** rather than rebuilding on every shell start.
|
|
95
|
+
- **macOS per-session history is switched off.** `/etc/zshrc_Apple_Terminal` runs before
|
|
96
|
+
your `.zshrc` and, on a restored Terminal window, repoints `HISTFILE` into
|
|
97
|
+
`~/.zsh_sessions` — which quietly breaks a single shared history. shellup takes Apple's
|
|
98
|
+
documented opt-out and reclaims the file.
|
|
86
99
|
|
|
87
100
|
## Requirements
|
|
88
101
|
|
package/dist/cli.js
CHANGED
|
@@ -20,6 +20,10 @@ function has(bin) {
|
|
|
20
20
|
return false;
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
|
+
function hasFile(paths) {
|
|
24
|
+
const prefix = process.env.HOMEBREW_PREFIX ?? "/opt/homebrew";
|
|
25
|
+
return paths.some((path) => existsSync(path.replace("${HOMEBREW_PREFIX:-/opt/homebrew}", prefix)));
|
|
26
|
+
}
|
|
23
27
|
function detectPackageManager() {
|
|
24
28
|
for (const pm of ["brew", "apt", "dnf", "pacman"]) if (has(pm)) return pm;
|
|
25
29
|
return "none";
|
|
@@ -261,9 +265,40 @@ var TOOLS = [
|
|
|
261
265
|
dnf: "tealdeer",
|
|
262
266
|
pacman: "tealdeer",
|
|
263
267
|
recommended: false
|
|
268
|
+
},
|
|
269
|
+
// Last on purpose: it wraps ZLE widgets, so it has to load after anything that
|
|
270
|
+
// defines its own (fzf, zoxide).
|
|
271
|
+
{
|
|
272
|
+
id: "autosuggestions",
|
|
273
|
+
label: "autosuggestions",
|
|
274
|
+
hint: "greys in the rest of the command as you type \u2014 press \u2192 to accept",
|
|
275
|
+
brew: "zsh-autosuggestions",
|
|
276
|
+
apt: "zsh-autosuggestions",
|
|
277
|
+
dnf: "zsh-autosuggestions",
|
|
278
|
+
pacman: "zsh-autosuggestions",
|
|
279
|
+
recommended: true,
|
|
280
|
+
sourceFiles: [
|
|
281
|
+
"${HOMEBREW_PREFIX:-/opt/homebrew}/share/zsh-autosuggestions/zsh-autosuggestions.zsh",
|
|
282
|
+
"/usr/local/share/zsh-autosuggestions/zsh-autosuggestions.zsh",
|
|
283
|
+
"/usr/share/zsh-autosuggestions/zsh-autosuggestions.zsh",
|
|
284
|
+
"/usr/share/zsh/plugins/zsh-autosuggestions/zsh-autosuggestions.zsh"
|
|
285
|
+
],
|
|
286
|
+
// Set before sourcing, because the plugin reads some of these at load time.
|
|
287
|
+
snippet: [
|
|
288
|
+
`# 'history' replays what you actually ran; 'completion' falls back to the`,
|
|
289
|
+
`# completion system, so \`git \` suggests a subcommand on a fresh machine.`,
|
|
290
|
+
`ZSH_AUTOSUGGEST_STRATEGY=(history completion)`,
|
|
291
|
+
`ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE='fg=8'`,
|
|
292
|
+
`# Stops the plugin doing work on pasted blobs, where it can't help anyway.`,
|
|
293
|
+
`ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE=20`
|
|
294
|
+
].join("\n")
|
|
264
295
|
}
|
|
265
296
|
];
|
|
266
297
|
var toolById = (id) => TOOLS.find((t) => t.id === id);
|
|
298
|
+
function isPresent(tool) {
|
|
299
|
+
if (tool.sourceFiles) return hasFile(tool.sourceFiles);
|
|
300
|
+
return tool.bin ? has(tool.bin) : false;
|
|
301
|
+
}
|
|
267
302
|
function packageFor(tool, pm) {
|
|
268
303
|
return pm === "brew" ? tool.brew : pm === "apt" ? tool.apt : pm === "dnf" ? tool.dnf : pm === "pacman" ? tool.pacman : void 0;
|
|
269
304
|
}
|
|
@@ -288,7 +323,7 @@ function installTools(tools, pm) {
|
|
|
288
323
|
const result = { installed: [], alreadyPresent: [], failed: [], unsupported: [], command: null };
|
|
289
324
|
const wanted = [];
|
|
290
325
|
for (const tool of tools) {
|
|
291
|
-
if (
|
|
326
|
+
if (isPresent(tool)) {
|
|
292
327
|
result.alreadyPresent.push(tool.id);
|
|
293
328
|
continue;
|
|
294
329
|
}
|
|
@@ -310,7 +345,7 @@ function installTools(tools, pm) {
|
|
|
310
345
|
execFileSync2(cmd[0], cmd[1], { stdio: ["ignore", "pipe", "pipe"], timeout: 15 * 60 * 1e3 });
|
|
311
346
|
} catch {
|
|
312
347
|
}
|
|
313
|
-
for (const tool of wanted) (
|
|
348
|
+
for (const tool of wanted) (isPresent(tool) ? result.installed : result.failed).push(tool.id);
|
|
314
349
|
return result;
|
|
315
350
|
}
|
|
316
351
|
function applyGitConfig(entries) {
|
|
@@ -342,15 +377,29 @@ function renderTools(config) {
|
|
|
342
377
|
const parts = [HEADER(version())];
|
|
343
378
|
for (const id of config.tools) {
|
|
344
379
|
const tool = toolById(id);
|
|
345
|
-
if (!tool
|
|
346
|
-
|
|
347
|
-
`
|
|
380
|
+
if (!tool || !tool.snippet && !tool.sourceFiles) continue;
|
|
381
|
+
const rule = `
|
|
348
382
|
# \u2500\u2500 ${tool.label} ${"\u2500".repeat(Math.max(0, 68 - tool.label.length))}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
383
|
+
`;
|
|
384
|
+
const indent = (body) => body.split("\n").map((l) => l.trim() ? ` ${l}` : l).join("\n");
|
|
385
|
+
if (tool.sourceFiles) {
|
|
386
|
+
parts.push(
|
|
387
|
+
rule + (tool.snippet ? tool.snippet + "\n" : "") + `() {
|
|
388
|
+
local p
|
|
389
|
+
for p in \\
|
|
390
|
+
` + tool.sourceFiles.map((f) => ` "${f}"`).join(" \\\n") + `
|
|
391
|
+
do
|
|
392
|
+
[[ -r $p ]] && { source $p; break }
|
|
393
|
+
done
|
|
394
|
+
}
|
|
352
395
|
`
|
|
353
|
-
|
|
396
|
+
);
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
parts.push(rule + `if command -v ${tool.bin} >/dev/null 2>&1; then
|
|
400
|
+
` + indent(tool.snippet) + `
|
|
401
|
+
fi
|
|
402
|
+
`);
|
|
354
403
|
}
|
|
355
404
|
return parts.join("");
|
|
356
405
|
}
|
|
@@ -543,6 +592,13 @@ async function init(opts = {}) {
|
|
|
543
592
|
}
|
|
544
593
|
if (existing) {
|
|
545
594
|
p.log.info(`Existing setup found (theme ${pc3.cyan(existing.theme)}). This will reconfigure it.`);
|
|
595
|
+
const added = TOOLS.filter((t) => t.recommended && !existing.tools.includes(t.id));
|
|
596
|
+
if (added.length) {
|
|
597
|
+
p.log.info(
|
|
598
|
+
`New since your last run: ${added.map((t) => pc3.cyan(t.label)).join(", ")}
|
|
599
|
+
Press ${pc3.cyan("Space")} on the list below to add ${added.length > 1 ? "them" : "it"}.`
|
|
600
|
+
);
|
|
601
|
+
}
|
|
546
602
|
}
|
|
547
603
|
const theme2 = await p.select({
|
|
548
604
|
message: "Pick a prompt theme",
|
|
@@ -569,7 +625,7 @@ async function init(opts = {}) {
|
|
|
569
625
|
initialValues: existing?.tools ?? TOOLS.filter((t) => t.recommended).map((t) => t.id),
|
|
570
626
|
options: TOOLS.map((t) => ({
|
|
571
627
|
value: t.id,
|
|
572
|
-
label: t.label + (
|
|
628
|
+
label: t.label + (isPresent(t) ? pc3.green(" (installed)") : ""),
|
|
573
629
|
hint: t.hint
|
|
574
630
|
}))
|
|
575
631
|
});
|
|
@@ -600,7 +656,7 @@ async function init(opts = {}) {
|
|
|
600
656
|
nerdFont: env.nerdFont,
|
|
601
657
|
installedAt: existing?.installedAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
602
658
|
};
|
|
603
|
-
const missing = selectedTools.map((id) => toolById(id)).filter((t) => t && !
|
|
659
|
+
const missing = selectedTools.map((id) => toolById(id)).filter((t) => t && !isPresent(t));
|
|
604
660
|
if (missing.length && env.packageManager !== "none") {
|
|
605
661
|
const doInstall = opts.yes || await p.confirm({
|
|
606
662
|
message: `Install ${missing.length} missing tool${missing.length > 1 ? "s" : ""} with ${env.packageManager}? (${missing.map((t) => t.label).join(", ")})`,
|
|
@@ -626,7 +682,7 @@ async function init(opts = {}) {
|
|
|
626
682
|
);
|
|
627
683
|
}
|
|
628
684
|
const delta = toolById("delta");
|
|
629
|
-
if (selectedTools.includes("delta") &&
|
|
685
|
+
if (selectedTools.includes("delta") && isPresent(delta) && delta.gitConfig) {
|
|
630
686
|
const doGit = await p.confirm({
|
|
631
687
|
message: "Set git to use delta for diffs? (writes to your global ~/.gitconfig)",
|
|
632
688
|
initialValue: true
|
|
@@ -694,13 +750,13 @@ ${pc4.bold(" Installation")}`);
|
|
|
694
750
|
${pc4.bold(" Tools")}`);
|
|
695
751
|
const selected = new Set(config?.tools ?? []);
|
|
696
752
|
for (const tool of TOOLS) {
|
|
697
|
-
const present =
|
|
753
|
+
const present = isPresent(tool);
|
|
698
754
|
if (!selected.has(tool.id) && !present) continue;
|
|
699
755
|
const state = present ? "ok" : "warn";
|
|
700
756
|
const detail = present ? selected.has(tool.id) ? "installed, integration active" : "installed, not managed by shellup" : "selected but not installed \u2014 integration is dormant";
|
|
701
757
|
console.log(line(state, tool.label, detail));
|
|
702
758
|
}
|
|
703
|
-
const missing = [...selected].map((id) => toolById(id)).filter((t) => t && !
|
|
759
|
+
const missing = [...selected].map((id) => toolById(id)).filter((t) => t && !isPresent(t));
|
|
704
760
|
if (missing.length) problems.push(`install missing tools: ${missing.map((t) => t.label).join(", ")}`);
|
|
705
761
|
console.log();
|
|
706
762
|
if (problems.length) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pm-2001/shellup",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Level up your terminal in 60 seconds — a beautiful prompt, modern tool swaps, and sane defaults, installed by one command and reversible by another.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"terminal",
|
package/runtime/options.zsh
CHANGED
|
@@ -6,7 +6,18 @@
|
|
|
6
6
|
[[ -o interactive ]] || return 0
|
|
7
7
|
|
|
8
8
|
# ── History ────────────────────────────────────────────────────────────────
|
|
9
|
-
|
|
9
|
+
# macOS ships per-session history in /etc/zshrc_Apple_Terminal, which runs BEFORE
|
|
10
|
+
# this file. It normally stands down when SHARE_HISTORY is set — but on a restored
|
|
11
|
+
# Terminal window it repoints HISTFILE into ~/.zsh_sessions straight away, before
|
|
12
|
+
# it can see our options, silently defeating the shared history set up below.
|
|
13
|
+
# SHELL_SESSION_HISTORY=0 is Apple's documented opt-out; the reclaim handles the
|
|
14
|
+
# window that has already been hijacked.
|
|
15
|
+
SHELL_SESSION_HISTORY=0
|
|
16
|
+
if [[ $HISTFILE == */.zsh_sessions/* ]]; then
|
|
17
|
+
HISTFILE="${ZDOTDIR:-$HOME}/.zsh_history"
|
|
18
|
+
[[ -r $HISTFILE ]] && fc -R "$HISTFILE"
|
|
19
|
+
fi
|
|
20
|
+
HISTFILE="${HISTFILE:-${ZDOTDIR:-$HOME}/.zsh_history}"
|
|
10
21
|
HISTSIZE=100000
|
|
11
22
|
SAVEHIST=100000
|
|
12
23
|
setopt append_history # never truncate another shell's writes
|