@pm-2001/shellup 0.1.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/LICENSE +21 -0
- package/README.md +96 -0
- package/dist/cli.js +883 -0
- package/package.json +57 -0
- package/runtime/aliases.zsh +46 -0
- package/runtime/functions.zsh +56 -0
- package/runtime/keybindings.zsh +45 -0
- package/runtime/options.zsh +59 -0
- package/runtime/prompt.zsh +161 -0
- package/runtime/themes/minimal.zsh +29 -0
- package/runtime/themes/neon.zsh +29 -0
- package/runtime/themes/powerline.zsh +36 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,883 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import pc8 from "picocolors";
|
|
5
|
+
|
|
6
|
+
// src/commands/init.ts
|
|
7
|
+
import * as p from "@clack/prompts";
|
|
8
|
+
import pc3 from "picocolors";
|
|
9
|
+
|
|
10
|
+
// src/core/detect.ts
|
|
11
|
+
import { execFileSync } from "child_process";
|
|
12
|
+
import { existsSync, readdirSync } from "fs";
|
|
13
|
+
import { homedir, platform } from "os";
|
|
14
|
+
import { join, basename } from "path";
|
|
15
|
+
function has(bin) {
|
|
16
|
+
try {
|
|
17
|
+
execFileSync("command", ["-v", bin], { stdio: "ignore", shell: "/bin/sh" });
|
|
18
|
+
return true;
|
|
19
|
+
} catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function detectPackageManager() {
|
|
24
|
+
for (const pm of ["brew", "apt", "dnf", "pacman"]) if (has(pm)) return pm;
|
|
25
|
+
return "none";
|
|
26
|
+
}
|
|
27
|
+
function detectNerdFont() {
|
|
28
|
+
const os = platform();
|
|
29
|
+
if (os === "darwin") {
|
|
30
|
+
const dirs = [join(homedir(), "Library", "Fonts"), "/Library/Fonts"];
|
|
31
|
+
for (const dir of dirs) {
|
|
32
|
+
if (!existsSync(dir)) continue;
|
|
33
|
+
try {
|
|
34
|
+
if (readdirSync(dir).some((f) => /nerd|nf-|powerline/i.test(basename(f)))) return true;
|
|
35
|
+
} catch {
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
const out = execFileSync("fc-list", [], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
42
|
+
return /nerd font|powerline/i.test(out);
|
|
43
|
+
} catch {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function detect() {
|
|
48
|
+
const os = platform();
|
|
49
|
+
const shell = process.env.SHELL ?? "";
|
|
50
|
+
const colorterm = process.env.COLORTERM ?? "";
|
|
51
|
+
return {
|
|
52
|
+
os: os === "darwin" ? "macos" : os === "linux" ? "linux" : "other",
|
|
53
|
+
shell,
|
|
54
|
+
isZsh: shell.includes("zsh"),
|
|
55
|
+
rcFile: join(homedir(), ".zshrc"),
|
|
56
|
+
packageManager: detectPackageManager(),
|
|
57
|
+
nerdFont: detectNerdFont(),
|
|
58
|
+
terminal: process.env.TERM_PROGRAM ?? process.env.TERM ?? "unknown",
|
|
59
|
+
trueColor: colorterm === "truecolor" || colorterm === "24bit"
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// src/core/config.ts
|
|
64
|
+
import { readFileSync, writeFileSync, existsSync as existsSync2, mkdirSync } from "fs";
|
|
65
|
+
|
|
66
|
+
// src/core/paths.ts
|
|
67
|
+
import { homedir as homedir2 } from "os";
|
|
68
|
+
import { join as join2, dirname } from "path";
|
|
69
|
+
import { fileURLToPath } from "url";
|
|
70
|
+
var HOME = homedir2();
|
|
71
|
+
var PKG_ROOT = join2(dirname(fileURLToPath(import.meta.url)), "..");
|
|
72
|
+
var CONFIG_DIR = process.env.SHELLUP_HOME ?? join2(HOME, ".config", "shellup");
|
|
73
|
+
var CONFIG_FILE = join2(CONFIG_DIR, "config.json");
|
|
74
|
+
var INIT_FILE = join2(CONFIG_DIR, "init.zsh");
|
|
75
|
+
var GENERATED_DIR = join2(CONFIG_DIR, "generated");
|
|
76
|
+
var RUNTIME_DIR = join2(CONFIG_DIR, "runtime");
|
|
77
|
+
var BACKUP_DIR = join2(CONFIG_DIR, "backups");
|
|
78
|
+
function tilde(p4) {
|
|
79
|
+
return p4.startsWith(HOME) ? "~" + p4.slice(HOME.length) : p4;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// src/core/config.ts
|
|
83
|
+
var DEFAULT_CONFIG = {
|
|
84
|
+
version: 1,
|
|
85
|
+
theme: "minimal",
|
|
86
|
+
tools: [],
|
|
87
|
+
aliases: true,
|
|
88
|
+
keybindings: true,
|
|
89
|
+
functions: true,
|
|
90
|
+
nerdFont: false,
|
|
91
|
+
shell: "zsh",
|
|
92
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
93
|
+
};
|
|
94
|
+
function configExists() {
|
|
95
|
+
return existsSync2(CONFIG_FILE);
|
|
96
|
+
}
|
|
97
|
+
function loadConfig() {
|
|
98
|
+
if (!configExists()) return null;
|
|
99
|
+
try {
|
|
100
|
+
return { ...DEFAULT_CONFIG, ...JSON.parse(readFileSync(CONFIG_FILE, "utf8")) };
|
|
101
|
+
} catch {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function saveConfig(config) {
|
|
106
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
107
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// src/core/tools.ts
|
|
111
|
+
var TOOLS = [
|
|
112
|
+
{
|
|
113
|
+
id: "eza",
|
|
114
|
+
bin: "eza",
|
|
115
|
+
label: "eza",
|
|
116
|
+
hint: "ls with colors, icons and git status",
|
|
117
|
+
brew: "eza",
|
|
118
|
+
apt: "eza",
|
|
119
|
+
dnf: "eza",
|
|
120
|
+
pacman: "eza",
|
|
121
|
+
recommended: true,
|
|
122
|
+
snippet: [
|
|
123
|
+
`alias ls='eza --group-directories-first'`,
|
|
124
|
+
`alias ll='eza -l --group-directories-first --git --time-style=long-iso'`,
|
|
125
|
+
`alias la='eza -la --group-directories-first --git --time-style=long-iso'`,
|
|
126
|
+
`alias lt='eza --tree --level=2 --group-directories-first'`
|
|
127
|
+
].join("\n")
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
id: "bat",
|
|
131
|
+
bin: "bat",
|
|
132
|
+
label: "bat",
|
|
133
|
+
hint: "cat with syntax highlighting",
|
|
134
|
+
brew: "bat",
|
|
135
|
+
apt: "bat",
|
|
136
|
+
dnf: "bat",
|
|
137
|
+
pacman: "bat",
|
|
138
|
+
recommended: true,
|
|
139
|
+
// bat detects a non-tty stdout and behaves like plain cat, so aliasing is pipe-safe.
|
|
140
|
+
snippet: [
|
|
141
|
+
`export BAT_THEME="\${BAT_THEME:-ansi}"`,
|
|
142
|
+
`alias cat='bat --style=plain --paging=never'`,
|
|
143
|
+
`export MANPAGER="sh -c 'col -bx | bat -l man -p'"`
|
|
144
|
+
].join("\n")
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
id: "fzf",
|
|
148
|
+
bin: "fzf",
|
|
149
|
+
label: "fzf",
|
|
150
|
+
hint: "fuzzy finder \u2014 powers Ctrl-R history search",
|
|
151
|
+
brew: "fzf",
|
|
152
|
+
apt: "fzf",
|
|
153
|
+
dnf: "fzf",
|
|
154
|
+
pacman: "fzf",
|
|
155
|
+
recommended: true,
|
|
156
|
+
snippet: [
|
|
157
|
+
`# fzf >=0.48 ships its own shell integration; older builds keep it in the prefix.`,
|
|
158
|
+
`if fzf --zsh >/dev/null 2>&1; then`,
|
|
159
|
+
` source <(fzf --zsh)`,
|
|
160
|
+
`elif [[ -d "\${HOMEBREW_PREFIX:-/opt/homebrew}/opt/fzf/shell" ]]; then`,
|
|
161
|
+
` source "\${HOMEBREW_PREFIX:-/opt/homebrew}/opt/fzf/shell/key-bindings.zsh"`,
|
|
162
|
+
` source "\${HOMEBREW_PREFIX:-/opt/homebrew}/opt/fzf/shell/completion.zsh" 2>/dev/null`,
|
|
163
|
+
`fi`,
|
|
164
|
+
`export FZF_DEFAULT_OPTS="--height 40% --layout=reverse --border=rounded --info=inline"`,
|
|
165
|
+
`command -v fd >/dev/null 2>&1 && export FZF_DEFAULT_COMMAND='fd --type f --hidden --follow --exclude .git'`
|
|
166
|
+
].join("\n")
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
id: "zoxide",
|
|
170
|
+
bin: "zoxide",
|
|
171
|
+
label: "zoxide",
|
|
172
|
+
hint: "cd that learns \u2014 jump with `z name`",
|
|
173
|
+
brew: "zoxide",
|
|
174
|
+
apt: "zoxide",
|
|
175
|
+
dnf: "zoxide",
|
|
176
|
+
pacman: "zoxide",
|
|
177
|
+
recommended: true,
|
|
178
|
+
snippet: `eval "$(zoxide init zsh)"`
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
id: "fd",
|
|
182
|
+
bin: "fd",
|
|
183
|
+
label: "fd",
|
|
184
|
+
hint: "find, but fast and sane",
|
|
185
|
+
brew: "fd",
|
|
186
|
+
apt: "fd-find",
|
|
187
|
+
dnf: "fd-find",
|
|
188
|
+
pacman: "fd",
|
|
189
|
+
recommended: true
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
id: "ripgrep",
|
|
193
|
+
bin: "rg",
|
|
194
|
+
label: "ripgrep",
|
|
195
|
+
hint: "grep, but fast (rg)",
|
|
196
|
+
brew: "ripgrep",
|
|
197
|
+
apt: "ripgrep",
|
|
198
|
+
dnf: "ripgrep",
|
|
199
|
+
pacman: "ripgrep",
|
|
200
|
+
recommended: true
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
id: "delta",
|
|
204
|
+
bin: "delta",
|
|
205
|
+
label: "git-delta",
|
|
206
|
+
hint: "side-by-side syntax-highlighted git diffs",
|
|
207
|
+
brew: "git-delta",
|
|
208
|
+
apt: "git-delta",
|
|
209
|
+
dnf: "git-delta",
|
|
210
|
+
pacman: "git-delta",
|
|
211
|
+
recommended: true,
|
|
212
|
+
gitConfig: [
|
|
213
|
+
["core.pager", "delta"],
|
|
214
|
+
["interactive.diffFilter", "delta --color-only"],
|
|
215
|
+
["delta.navigate", "true"],
|
|
216
|
+
["delta.line-numbers", "true"],
|
|
217
|
+
["merge.conflictstyle", "zdiff3"]
|
|
218
|
+
]
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
id: "lazygit",
|
|
222
|
+
bin: "lazygit",
|
|
223
|
+
label: "lazygit",
|
|
224
|
+
hint: "full git UI in the terminal (lg)",
|
|
225
|
+
brew: "lazygit",
|
|
226
|
+
apt: "lazygit",
|
|
227
|
+
dnf: "lazygit",
|
|
228
|
+
pacman: "lazygit",
|
|
229
|
+
recommended: false,
|
|
230
|
+
snippet: `alias lg='lazygit'`
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
id: "btop",
|
|
234
|
+
bin: "btop",
|
|
235
|
+
label: "btop",
|
|
236
|
+
hint: "the good-looking top",
|
|
237
|
+
brew: "btop",
|
|
238
|
+
apt: "btop",
|
|
239
|
+
dnf: "btop",
|
|
240
|
+
pacman: "btop",
|
|
241
|
+
recommended: false
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
id: "jq",
|
|
245
|
+
bin: "jq",
|
|
246
|
+
label: "jq",
|
|
247
|
+
hint: "slice and query JSON",
|
|
248
|
+
brew: "jq",
|
|
249
|
+
apt: "jq",
|
|
250
|
+
dnf: "jq",
|
|
251
|
+
pacman: "jq",
|
|
252
|
+
recommended: false
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
id: "tldr",
|
|
256
|
+
bin: "tldr",
|
|
257
|
+
label: "tldr",
|
|
258
|
+
hint: "man pages with actual examples",
|
|
259
|
+
brew: "tealdeer",
|
|
260
|
+
apt: "tealdeer",
|
|
261
|
+
dnf: "tealdeer",
|
|
262
|
+
pacman: "tealdeer",
|
|
263
|
+
recommended: false
|
|
264
|
+
}
|
|
265
|
+
];
|
|
266
|
+
var toolById = (id) => TOOLS.find((t) => t.id === id);
|
|
267
|
+
function packageFor(tool, pm) {
|
|
268
|
+
return pm === "brew" ? tool.brew : pm === "apt" ? tool.apt : pm === "dnf" ? tool.dnf : pm === "pacman" ? tool.pacman : void 0;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// src/core/install.ts
|
|
272
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
273
|
+
function installCommand(pm, pkgs) {
|
|
274
|
+
switch (pm) {
|
|
275
|
+
case "brew":
|
|
276
|
+
return ["brew", ["install", ...pkgs]];
|
|
277
|
+
case "apt":
|
|
278
|
+
return ["sudo", ["apt-get", "install", "-y", ...pkgs]];
|
|
279
|
+
case "dnf":
|
|
280
|
+
return ["sudo", ["dnf", "install", "-y", ...pkgs]];
|
|
281
|
+
case "pacman":
|
|
282
|
+
return ["sudo", ["pacman", "-S", "--noconfirm", ...pkgs]];
|
|
283
|
+
default:
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
function installTools(tools, pm) {
|
|
288
|
+
const result = { installed: [], alreadyPresent: [], failed: [], unsupported: [], command: null };
|
|
289
|
+
const wanted = [];
|
|
290
|
+
for (const tool of tools) {
|
|
291
|
+
if (has(tool.bin)) {
|
|
292
|
+
result.alreadyPresent.push(tool.id);
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
if (!packageFor(tool, pm)) {
|
|
296
|
+
result.unsupported.push(tool.id);
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
wanted.push(tool);
|
|
300
|
+
}
|
|
301
|
+
if (wanted.length === 0) return result;
|
|
302
|
+
const pkgs = wanted.map((t) => packageFor(t, pm));
|
|
303
|
+
const cmd = installCommand(pm, pkgs);
|
|
304
|
+
if (!cmd) {
|
|
305
|
+
result.unsupported.push(...wanted.map((t) => t.id));
|
|
306
|
+
return result;
|
|
307
|
+
}
|
|
308
|
+
result.command = [cmd[0], ...cmd[1]].join(" ");
|
|
309
|
+
try {
|
|
310
|
+
execFileSync2(cmd[0], cmd[1], { stdio: ["ignore", "pipe", "pipe"], timeout: 15 * 60 * 1e3 });
|
|
311
|
+
} catch {
|
|
312
|
+
}
|
|
313
|
+
for (const tool of wanted) (has(tool.bin) ? result.installed : result.failed).push(tool.id);
|
|
314
|
+
return result;
|
|
315
|
+
}
|
|
316
|
+
function applyGitConfig(entries) {
|
|
317
|
+
try {
|
|
318
|
+
for (const [key, value] of entries) {
|
|
319
|
+
execFileSync2("git", ["config", "--global", key, value], { stdio: "ignore" });
|
|
320
|
+
}
|
|
321
|
+
return true;
|
|
322
|
+
} catch {
|
|
323
|
+
return false;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// src/core/render.ts
|
|
328
|
+
import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, cpSync, existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
329
|
+
import { join as join3 } from "path";
|
|
330
|
+
function version() {
|
|
331
|
+
try {
|
|
332
|
+
return JSON.parse(readFileSync2(join3(PKG_ROOT, "package.json"), "utf8")).version ?? "0.0.0";
|
|
333
|
+
} catch {
|
|
334
|
+
return "0.0.0";
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
var HEADER = (v) => `# Generated by shellup v${v} on ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
338
|
+
# Do not edit \u2014 regenerated by \`shellup apply\`.
|
|
339
|
+
# Put your own overrides in ~/.config/shellup/custom.zsh, which is never touched.
|
|
340
|
+
`;
|
|
341
|
+
function renderTools(config) {
|
|
342
|
+
const parts = [HEADER(version())];
|
|
343
|
+
for (const id of config.tools) {
|
|
344
|
+
const tool = toolById(id);
|
|
345
|
+
if (!tool?.snippet) continue;
|
|
346
|
+
parts.push(
|
|
347
|
+
`
|
|
348
|
+
# \u2500\u2500 ${tool.label} ${"\u2500".repeat(Math.max(0, 68 - tool.label.length))}
|
|
349
|
+
if command -v ${tool.bin} >/dev/null 2>&1; then
|
|
350
|
+
` + tool.snippet.split("\n").map((l) => l.trim() ? ` ${l}` : l).join("\n") + `
|
|
351
|
+
fi
|
|
352
|
+
`
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
return parts.join("");
|
|
356
|
+
}
|
|
357
|
+
function renderInit(config) {
|
|
358
|
+
const lines = [
|
|
359
|
+
HEADER(version()),
|
|
360
|
+
"",
|
|
361
|
+
`export SHELLUP_DIR="\${SHELLUP_DIR:-${CONFIG_DIR.replace(process.env.HOME ?? "", "$HOME")}}"`,
|
|
362
|
+
`export SHELLUP_VERSION="${version()}"`,
|
|
363
|
+
"",
|
|
364
|
+
"# Non-interactive shells (scripts, scp, rsync) get nothing: sourcing a prompt",
|
|
365
|
+
"# engine there is wasted work and can break protocols that expect clean stdout.",
|
|
366
|
+
"[[ -o interactive ]] || return 0",
|
|
367
|
+
"",
|
|
368
|
+
'shellup_source() { [ -r "$1" ] && source "$1"; }',
|
|
369
|
+
""
|
|
370
|
+
];
|
|
371
|
+
if (config.functions || config.aliases || config.keybindings) lines.push("# \u2500\u2500 defaults");
|
|
372
|
+
lines.push(`shellup_source "$SHELLUP_DIR/runtime/options.zsh"`);
|
|
373
|
+
lines.push("");
|
|
374
|
+
lines.push("# \u2500\u2500 prompt (engine first, then the theme that renders its variables)");
|
|
375
|
+
lines.push(`shellup_source "$SHELLUP_DIR/runtime/prompt.zsh"`);
|
|
376
|
+
lines.push(`shellup_source "$SHELLUP_DIR/runtime/themes/${config.theme}.zsh"`);
|
|
377
|
+
lines.push("");
|
|
378
|
+
if (config.aliases) lines.push(`shellup_source "$SHELLUP_DIR/runtime/aliases.zsh"`);
|
|
379
|
+
if (config.functions) lines.push(`shellup_source "$SHELLUP_DIR/runtime/functions.zsh"`);
|
|
380
|
+
if (config.keybindings) lines.push(`shellup_source "$SHELLUP_DIR/runtime/keybindings.zsh"`);
|
|
381
|
+
lines.push("");
|
|
382
|
+
lines.push("# \u2500\u2500 tool integrations (last, so tools like fzf bind their own keys over ours)");
|
|
383
|
+
lines.push(`shellup_source "$SHELLUP_DIR/generated/tools.zsh"`);
|
|
384
|
+
lines.push("");
|
|
385
|
+
lines.push("# \u2500\u2500 yours, always last and never regenerated");
|
|
386
|
+
lines.push(`shellup_source "$SHELLUP_DIR/custom.zsh"`);
|
|
387
|
+
lines.push("");
|
|
388
|
+
return lines.join("\n");
|
|
389
|
+
}
|
|
390
|
+
function apply(config) {
|
|
391
|
+
mkdirSync2(CONFIG_DIR, { recursive: true });
|
|
392
|
+
mkdirSync2(GENERATED_DIR, { recursive: true });
|
|
393
|
+
cpSync(join3(PKG_ROOT, "runtime"), RUNTIME_DIR, { recursive: true });
|
|
394
|
+
writeFileSync2(join3(GENERATED_DIR, "tools.zsh"), renderTools(config), "utf8");
|
|
395
|
+
writeFileSync2(INIT_FILE, renderInit(config), "utf8");
|
|
396
|
+
const custom = join3(CONFIG_DIR, "custom.zsh");
|
|
397
|
+
if (!existsSync3(custom)) {
|
|
398
|
+
writeFileSync2(
|
|
399
|
+
custom,
|
|
400
|
+
"# Your own shell config. shellup never overwrites this file.\n# It is sourced last, so anything here wins over shellup's defaults.\n\n",
|
|
401
|
+
"utf8"
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// src/core/shellrc.ts
|
|
407
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as existsSync4, mkdirSync as mkdirSync3, copyFileSync } from "fs";
|
|
408
|
+
import { join as join4 } from "path";
|
|
409
|
+
var START = "# >>> shellup >>>";
|
|
410
|
+
var END = "# <<< shellup <<<";
|
|
411
|
+
function block() {
|
|
412
|
+
return [
|
|
413
|
+
START,
|
|
414
|
+
"# Managed by shellup \u2014 edit ~/.config/shellup/config.json, then run `shellup apply`.",
|
|
415
|
+
`[ -f "${INIT_FILE.replace(process.env.HOME ?? "", "$HOME")}" ] && source "${INIT_FILE.replace(process.env.HOME ?? "", "$HOME")}"`,
|
|
416
|
+
END
|
|
417
|
+
].join("\n");
|
|
418
|
+
}
|
|
419
|
+
function hasBlock(rcFile) {
|
|
420
|
+
return existsSync4(rcFile) && readFileSync3(rcFile, "utf8").includes(START);
|
|
421
|
+
}
|
|
422
|
+
function backupRc(rcFile) {
|
|
423
|
+
if (!existsSync4(rcFile)) return null;
|
|
424
|
+
mkdirSync3(BACKUP_DIR, { recursive: true });
|
|
425
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
426
|
+
const dest = join4(BACKUP_DIR, `zshrc.${stamp}.bak`);
|
|
427
|
+
copyFileSync(rcFile, dest);
|
|
428
|
+
return dest;
|
|
429
|
+
}
|
|
430
|
+
function stripBlock(content) {
|
|
431
|
+
const lines = content.split("\n");
|
|
432
|
+
const out = [];
|
|
433
|
+
let inside = false;
|
|
434
|
+
for (const line2 of lines) {
|
|
435
|
+
if (line2.trim() === START) {
|
|
436
|
+
inside = true;
|
|
437
|
+
if (out.length && out[out.length - 1].trim() === "") out.pop();
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
440
|
+
if (line2.trim() === END) {
|
|
441
|
+
inside = false;
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
if (!inside) out.push(line2);
|
|
445
|
+
}
|
|
446
|
+
return out.join("\n");
|
|
447
|
+
}
|
|
448
|
+
function installBlock(rcFile) {
|
|
449
|
+
const existed = existsSync4(rcFile);
|
|
450
|
+
const original = existed ? readFileSync3(rcFile, "utf8") : "";
|
|
451
|
+
const replaced = original.includes(START);
|
|
452
|
+
const backup = backupRc(rcFile);
|
|
453
|
+
let next = replaced ? stripBlock(original) : original;
|
|
454
|
+
if (next.length && !next.endsWith("\n")) next += "\n";
|
|
455
|
+
next += (next.trim() ? "\n" : "") + block() + "\n";
|
|
456
|
+
writeFileSync3(rcFile, next, "utf8");
|
|
457
|
+
return { backup, replaced };
|
|
458
|
+
}
|
|
459
|
+
function removeBlock(rcFile) {
|
|
460
|
+
if (!hasBlock(rcFile)) return { backup: null, removed: false };
|
|
461
|
+
const backup = backupRc(rcFile);
|
|
462
|
+
writeFileSync3(rcFile, stripBlock(readFileSync3(rcFile, "utf8")), "utf8");
|
|
463
|
+
return { backup, removed: true };
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// src/themes/index.ts
|
|
467
|
+
import pc from "picocolors";
|
|
468
|
+
var THEMES = [
|
|
469
|
+
{
|
|
470
|
+
name: "minimal",
|
|
471
|
+
label: "minimal",
|
|
472
|
+
hint: "clean two-line prompt, no special font needed",
|
|
473
|
+
requiresNerdFont: false,
|
|
474
|
+
preview: [
|
|
475
|
+
`${pc.blue("~/dev/shellup")} ${pc.dim("on")} ${pc.magenta("main")} ${pc.green("+2")} ${pc.yellow("!1")}`,
|
|
476
|
+
`${pc.green(">")} npm test${" ".repeat(18)}${pc.dim("4s")}`
|
|
477
|
+
]
|
|
478
|
+
},
|
|
479
|
+
{
|
|
480
|
+
name: "neon",
|
|
481
|
+
label: "neon",
|
|
482
|
+
hint: "bright boxed prompt, still font-safe",
|
|
483
|
+
requiresNerdFont: false,
|
|
484
|
+
preview: [
|
|
485
|
+
`${pc.dim("\u256D\u2500")} ${pc.cyan("~/dev/shellup")} ${pc.dim("\xB7")} ${pc.magenta("\u2325 main")} ${pc.yellow("\u271A1")} ${pc.cyan("\u21E12")}`,
|
|
486
|
+
`${pc.dim("\u2570\u2500")}${pc.magenta("\u276F")} npm test${" ".repeat(15)}${pc.dim("4s")}`
|
|
487
|
+
]
|
|
488
|
+
},
|
|
489
|
+
{
|
|
490
|
+
name: "powerline",
|
|
491
|
+
label: "powerline",
|
|
492
|
+
hint: "segmented arrows and icons \u2014 requires a Nerd Font",
|
|
493
|
+
requiresNerdFont: true,
|
|
494
|
+
preview: [
|
|
495
|
+
`${pc.bgBlue(pc.black(" ~/dev/shellup "))}${pc.bgGreen(pc.black(" main +2 "))}`,
|
|
496
|
+
`${pc.green("\u276F")} npm test${" ".repeat(18)}${pc.dim("4s")}`
|
|
497
|
+
]
|
|
498
|
+
}
|
|
499
|
+
];
|
|
500
|
+
var themeByName = (name) => THEMES.find((t) => t.name === name);
|
|
501
|
+
|
|
502
|
+
// src/core/ui.ts
|
|
503
|
+
import pc2 from "picocolors";
|
|
504
|
+
var dim = pc2.dim;
|
|
505
|
+
var bold = pc2.bold;
|
|
506
|
+
var SYM = { ok: "\u2714", warn: "!", bad: "\u2716", info: "\xB7" };
|
|
507
|
+
function banner(version2) {
|
|
508
|
+
return `${pc2.bgCyan(pc2.black(" shellup "))} ${dim("v" + version2)}`;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// src/commands/init.ts
|
|
512
|
+
function bail(value) {
|
|
513
|
+
if (p.isCancel(value)) {
|
|
514
|
+
p.cancel("Nothing was changed.");
|
|
515
|
+
process.exit(0);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
async function init(opts = {}) {
|
|
519
|
+
console.log();
|
|
520
|
+
p.intro(banner(version()));
|
|
521
|
+
const env = detect();
|
|
522
|
+
const existing = loadConfig();
|
|
523
|
+
p.note(
|
|
524
|
+
[
|
|
525
|
+
`${pc3.dim("os".padEnd(10))} ${env.os}`,
|
|
526
|
+
`${pc3.dim("shell".padEnd(10))} ${env.shell || "unknown"}${env.isZsh ? "" : pc3.yellow(" (not zsh)")}`,
|
|
527
|
+
`${pc3.dim("packages".padEnd(10))} ${env.packageManager === "none" ? pc3.yellow("none found") : env.packageManager}`,
|
|
528
|
+
`${pc3.dim("font".padEnd(10))} ${env.nerdFont ? pc3.green("Nerd Font found") : pc3.yellow("no Nerd Font")}`,
|
|
529
|
+
`${pc3.dim("terminal".padEnd(10))} ${env.terminal}`
|
|
530
|
+
].join("\n"),
|
|
531
|
+
"Detected"
|
|
532
|
+
);
|
|
533
|
+
if (!env.isZsh) {
|
|
534
|
+
const go = await p.confirm({
|
|
535
|
+
message: `Your login shell isn't zsh. shellup only configures zsh \u2014 install anyway?`,
|
|
536
|
+
initialValue: false
|
|
537
|
+
});
|
|
538
|
+
bail(go);
|
|
539
|
+
if (!go) {
|
|
540
|
+
p.outro("Run `chsh -s $(which zsh)` first, then try again.");
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
if (existing) {
|
|
545
|
+
p.log.info(`Existing setup found (theme ${pc3.cyan(existing.theme)}). This will reconfigure it.`);
|
|
546
|
+
}
|
|
547
|
+
const theme2 = await p.select({
|
|
548
|
+
message: "Pick a prompt theme",
|
|
549
|
+
initialValue: existing?.theme ?? (env.nerdFont ? "powerline" : "minimal"),
|
|
550
|
+
options: THEMES.map((t) => ({
|
|
551
|
+
value: t.name,
|
|
552
|
+
label: t.label + (t.requiresNerdFont && !env.nerdFont ? pc3.yellow(" \u2014 needs a Nerd Font") : ""),
|
|
553
|
+
hint: t.hint
|
|
554
|
+
}))
|
|
555
|
+
});
|
|
556
|
+
bail(theme2);
|
|
557
|
+
const chosen = THEMES.find((t) => t.name === theme2);
|
|
558
|
+
p.note(chosen.preview.join("\n"), `Preview \u2014 ${chosen.label}`);
|
|
559
|
+
if (chosen.requiresNerdFont && !env.nerdFont) {
|
|
560
|
+
p.log.warn(
|
|
561
|
+
`${chosen.label} draws with Nerd Font glyphs. Without one you'll see boxes.
|
|
562
|
+
` + (env.packageManager === "brew" ? ` Install one: ${pc3.cyan("brew install --cask font-jetbrains-mono-nerd-font")}` : ` Get one at: ${pc3.cyan("https://nerdfonts.com")}`) + `
|
|
563
|
+
Then set it as your terminal font.`
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
const selectedTools = await p.multiselect({
|
|
567
|
+
message: "Which tools should shellup set up?",
|
|
568
|
+
required: false,
|
|
569
|
+
initialValues: existing?.tools ?? TOOLS.filter((t) => t.recommended).map((t) => t.id),
|
|
570
|
+
options: TOOLS.map((t) => ({
|
|
571
|
+
value: t.id,
|
|
572
|
+
label: t.label + (has(t.bin) ? pc3.green(" (installed)") : ""),
|
|
573
|
+
hint: t.hint
|
|
574
|
+
}))
|
|
575
|
+
});
|
|
576
|
+
bail(selectedTools);
|
|
577
|
+
const extras = await p.multiselect({
|
|
578
|
+
message: "Shell defaults to enable",
|
|
579
|
+
required: false,
|
|
580
|
+
initialValues: [
|
|
581
|
+
...existing?.aliases ?? true ? ["aliases"] : [],
|
|
582
|
+
...existing?.functions ?? true ? ["functions"] : [],
|
|
583
|
+
...existing?.keybindings ?? true ? ["keybindings"] : []
|
|
584
|
+
],
|
|
585
|
+
options: [
|
|
586
|
+
{ value: "aliases", label: "Aliases", hint: "git shortcuts, .. ... , safety rails on cp/mv" },
|
|
587
|
+
{ value: "functions", label: "Functions", hint: "mkcd, extract, serve, onport, bak, cdr" },
|
|
588
|
+
{ value: "keybindings", label: "Keybindings", hint: "prefix history search on \u2191, word jumps, Ctrl-Z toggle" }
|
|
589
|
+
]
|
|
590
|
+
});
|
|
591
|
+
bail(extras);
|
|
592
|
+
const config = {
|
|
593
|
+
...DEFAULT_CONFIG,
|
|
594
|
+
...existing ?? {},
|
|
595
|
+
theme: theme2,
|
|
596
|
+
tools: selectedTools,
|
|
597
|
+
aliases: extras.includes("aliases"),
|
|
598
|
+
functions: extras.includes("functions"),
|
|
599
|
+
keybindings: extras.includes("keybindings"),
|
|
600
|
+
nerdFont: env.nerdFont,
|
|
601
|
+
installedAt: existing?.installedAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
602
|
+
};
|
|
603
|
+
const missing = selectedTools.map((id) => toolById(id)).filter((t) => t && !has(t.bin));
|
|
604
|
+
if (missing.length && env.packageManager !== "none") {
|
|
605
|
+
const doInstall = opts.yes || await p.confirm({
|
|
606
|
+
message: `Install ${missing.length} missing tool${missing.length > 1 ? "s" : ""} with ${env.packageManager}? (${missing.map((t) => t.label).join(", ")})`,
|
|
607
|
+
initialValue: true
|
|
608
|
+
});
|
|
609
|
+
bail(doInstall);
|
|
610
|
+
if (doInstall) {
|
|
611
|
+
const s2 = p.spinner();
|
|
612
|
+
s2.start(`Installing with ${env.packageManager}\u2026`);
|
|
613
|
+
const res = installTools(missing, env.packageManager);
|
|
614
|
+
s2.stop(
|
|
615
|
+
res.installed.length ? `Installed ${res.installed.join(", ")}` : "Nothing new installed"
|
|
616
|
+
);
|
|
617
|
+
if (res.failed.length) {
|
|
618
|
+
p.log.warn(`Could not install: ${res.failed.join(", ")}
|
|
619
|
+
Try manually: ${pc3.dim(res.command ?? "")}`);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
} else if (missing.length) {
|
|
623
|
+
p.log.warn(
|
|
624
|
+
`No supported package manager found, so these stay uninstalled: ${missing.map((t) => t.label).join(", ")}
|
|
625
|
+
Their config is still written and activates automatically once they're on PATH.`
|
|
626
|
+
);
|
|
627
|
+
}
|
|
628
|
+
const delta = toolById("delta");
|
|
629
|
+
if (selectedTools.includes("delta") && has(delta.bin) && delta.gitConfig) {
|
|
630
|
+
const doGit = await p.confirm({
|
|
631
|
+
message: "Set git to use delta for diffs? (writes to your global ~/.gitconfig)",
|
|
632
|
+
initialValue: true
|
|
633
|
+
});
|
|
634
|
+
bail(doGit);
|
|
635
|
+
if (doGit) {
|
|
636
|
+
applyGitConfig(delta.gitConfig) ? p.log.success("git configured to use delta") : p.log.warn("Could not write git config \u2014 is git installed?");
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
const s = p.spinner();
|
|
640
|
+
s.start("Writing config");
|
|
641
|
+
apply(config);
|
|
642
|
+
saveConfig(config);
|
|
643
|
+
const { backup, replaced } = installBlock(env.rcFile);
|
|
644
|
+
s.stop("Config written");
|
|
645
|
+
p.note(
|
|
646
|
+
[
|
|
647
|
+
`${pc3.dim("config".padEnd(10))} ${tilde(CONFIG_DIR)}`,
|
|
648
|
+
`${pc3.dim("zshrc".padEnd(10))} ${replaced ? "managed block updated" : "3-line managed block added"}`,
|
|
649
|
+
backup ? `${pc3.dim("backup".padEnd(10))} ${tilde(backup)}` : `${pc3.dim("backup".padEnd(10))} ${pc3.dim("no existing .zshrc")}`,
|
|
650
|
+
`${pc3.dim("yours".padEnd(10))} ${tilde(CONFIG_DIR + "/custom.zsh")} ${pc3.dim("(never overwritten)")}`
|
|
651
|
+
].join("\n"),
|
|
652
|
+
"Done"
|
|
653
|
+
);
|
|
654
|
+
p.outro(`Restart your shell to see it: ${pc3.cyan("exec zsh")}`);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// src/commands/doctor.ts
|
|
658
|
+
import pc4 from "picocolors";
|
|
659
|
+
import { existsSync as existsSync5 } from "fs";
|
|
660
|
+
var line = (state, label, detail) => {
|
|
661
|
+
const mark = state === "ok" ? pc4.green(SYM.ok) : state === "warn" ? pc4.yellow(SYM.warn) : pc4.red(SYM.bad);
|
|
662
|
+
return ` ${mark} ${label.padEnd(22)} ${pc4.dim(detail)}`;
|
|
663
|
+
};
|
|
664
|
+
function doctor() {
|
|
665
|
+
const env = detect();
|
|
666
|
+
const config = loadConfig();
|
|
667
|
+
const problems = [];
|
|
668
|
+
console.log(`
|
|
669
|
+
${banner(version())}
|
|
670
|
+
`);
|
|
671
|
+
console.log(pc4.bold(" Environment"));
|
|
672
|
+
console.log(line(env.isZsh ? "ok" : "bad", "shell", env.shell || "unknown"));
|
|
673
|
+
if (!env.isZsh) problems.push("shellup configures zsh; switch with `chsh -s $(which zsh)`");
|
|
674
|
+
console.log(line("ok", "os", env.os));
|
|
675
|
+
console.log(line(env.packageManager === "none" ? "warn" : "ok", "package manager", env.packageManager));
|
|
676
|
+
console.log(line("ok", "terminal", env.terminal));
|
|
677
|
+
console.log(line(env.trueColor ? "ok" : "warn", "truecolor", env.trueColor ? "yes" : "not advertised (COLORTERM unset)"));
|
|
678
|
+
console.log(line(env.nerdFont ? "ok" : "warn", "nerd font", env.nerdFont ? "found" : "none found"));
|
|
679
|
+
console.log(`
|
|
680
|
+
${pc4.bold(" Installation")}`);
|
|
681
|
+
console.log(line(config ? "ok" : "bad", "config", config ? tilde(CONFIG_DIR) : "not set up \u2014 run `shellup init`"));
|
|
682
|
+
if (!config) problems.push("run `shellup init` to set up");
|
|
683
|
+
console.log(line(existsSync5(INIT_FILE) ? "ok" : "bad", "init.zsh", existsSync5(INIT_FILE) ? tilde(INIT_FILE) : "missing \u2014 run `shellup apply`"));
|
|
684
|
+
console.log(line(hasBlock(env.rcFile) ? "ok" : "bad", "zshrc hook", hasBlock(env.rcFile) ? "managed block present" : "missing \u2014 run `shellup apply`"));
|
|
685
|
+
if (config) {
|
|
686
|
+
const theme2 = themeByName(config.theme);
|
|
687
|
+
const themeState = theme2?.requiresNerdFont && !env.nerdFont ? "warn" : "ok";
|
|
688
|
+
console.log(
|
|
689
|
+
line(themeState, "theme", config.theme + (themeState === "warn" ? " \u2014 needs a Nerd Font, glyphs will show as boxes" : ""))
|
|
690
|
+
);
|
|
691
|
+
if (themeState === "warn") problems.push("install a Nerd Font, or run `shellup theme minimal`");
|
|
692
|
+
}
|
|
693
|
+
console.log(`
|
|
694
|
+
${pc4.bold(" Tools")}`);
|
|
695
|
+
const selected = new Set(config?.tools ?? []);
|
|
696
|
+
for (const tool of TOOLS) {
|
|
697
|
+
const present = has(tool.bin);
|
|
698
|
+
if (!selected.has(tool.id) && !present) continue;
|
|
699
|
+
const state = present ? "ok" : "warn";
|
|
700
|
+
const detail = present ? selected.has(tool.id) ? "installed, integration active" : "installed, not managed by shellup" : "selected but not installed \u2014 integration is dormant";
|
|
701
|
+
console.log(line(state, tool.label, detail));
|
|
702
|
+
}
|
|
703
|
+
const missing = [...selected].map((id) => toolById(id)).filter((t) => t && !has(t.bin));
|
|
704
|
+
if (missing.length) problems.push(`install missing tools: ${missing.map((t) => t.label).join(", ")}`);
|
|
705
|
+
console.log();
|
|
706
|
+
if (problems.length) {
|
|
707
|
+
console.log(pc4.bold(pc4.yellow(" To fix")));
|
|
708
|
+
for (const problem of problems) console.log(` ${pc4.dim("\u2192")} ${problem}`);
|
|
709
|
+
} else {
|
|
710
|
+
console.log(` ${pc4.green(SYM.ok)} ${pc4.bold("Everything looks healthy.")}`);
|
|
711
|
+
}
|
|
712
|
+
console.log();
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
// src/commands/theme.ts
|
|
716
|
+
import * as p2 from "@clack/prompts";
|
|
717
|
+
import pc5 from "picocolors";
|
|
718
|
+
async function theme(name) {
|
|
719
|
+
const config = loadConfig();
|
|
720
|
+
if (!config) {
|
|
721
|
+
console.error(pc5.red("shellup isn't set up yet. Run `shellup init` first."));
|
|
722
|
+
process.exitCode = 1;
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
if (name) {
|
|
726
|
+
const meta2 = themeByName(name);
|
|
727
|
+
if (!meta2) {
|
|
728
|
+
console.error(
|
|
729
|
+
pc5.red(`Unknown theme "${name}".`) + ` Available: ${THEMES.map((t) => pc5.cyan(t.name)).join(", ")}`
|
|
730
|
+
);
|
|
731
|
+
process.exitCode = 1;
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
apply({ ...config, theme: meta2.name });
|
|
735
|
+
saveConfig({ ...config, theme: meta2.name });
|
|
736
|
+
console.log(`
|
|
737
|
+
Theme set to ${pc5.cyan(meta2.name)}. Reload with ${pc5.cyan("exec zsh")}.
|
|
738
|
+
`);
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
console.log();
|
|
742
|
+
p2.intro(banner(version()));
|
|
743
|
+
const env = detect();
|
|
744
|
+
const picked = await p2.select({
|
|
745
|
+
message: "Pick a prompt theme",
|
|
746
|
+
initialValue: config.theme,
|
|
747
|
+
options: THEMES.map((t) => ({
|
|
748
|
+
value: t.name,
|
|
749
|
+
label: t.label + (t.requiresNerdFont && !env.nerdFont ? pc5.yellow(" \u2014 needs a Nerd Font") : ""),
|
|
750
|
+
hint: t.hint
|
|
751
|
+
}))
|
|
752
|
+
});
|
|
753
|
+
if (p2.isCancel(picked)) {
|
|
754
|
+
p2.cancel("Theme unchanged.");
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
const meta = themeByName(picked);
|
|
758
|
+
p2.note(meta.preview.join("\n"), `Preview \u2014 ${meta.label}`);
|
|
759
|
+
apply({ ...config, theme: picked });
|
|
760
|
+
saveConfig({ ...config, theme: picked });
|
|
761
|
+
p2.outro(`Theme set to ${pc5.cyan(picked)}. Reload with ${pc5.cyan("exec zsh")}.`);
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
// src/commands/apply.ts
|
|
765
|
+
import pc6 from "picocolors";
|
|
766
|
+
function applyCommand() {
|
|
767
|
+
const config = loadConfig();
|
|
768
|
+
if (!config) {
|
|
769
|
+
console.error(pc6.red("Nothing to apply \u2014 run `shellup init` first."));
|
|
770
|
+
process.exitCode = 1;
|
|
771
|
+
return;
|
|
772
|
+
}
|
|
773
|
+
apply(config);
|
|
774
|
+
const { backup, replaced } = installBlock(detect().rcFile);
|
|
775
|
+
console.log(
|
|
776
|
+
`
|
|
777
|
+
${pc6.green("\u2714")} Regenerated ${pc6.dim(tilde(CONFIG_DIR))}
|
|
778
|
+
${pc6.green("\u2714")} ${replaced ? "Managed block refreshed" : "Managed block added"} in ${pc6.dim("~/.zshrc")}` + (backup ? `
|
|
779
|
+
${pc6.dim("\xB7 backup: " + tilde(backup))}` : "") + `
|
|
780
|
+
|
|
781
|
+
Reload with ${pc6.cyan("exec zsh")}.
|
|
782
|
+
`
|
|
783
|
+
);
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
// src/commands/uninstall.ts
|
|
787
|
+
import * as p3 from "@clack/prompts";
|
|
788
|
+
import pc7 from "picocolors";
|
|
789
|
+
import { rmSync, existsSync as existsSync6 } from "fs";
|
|
790
|
+
async function uninstall() {
|
|
791
|
+
console.log();
|
|
792
|
+
p3.intro(banner(version()));
|
|
793
|
+
const env = detect();
|
|
794
|
+
const go = await p3.confirm({
|
|
795
|
+
message: `Remove shellup's block from ~/.zshrc?`,
|
|
796
|
+
initialValue: true
|
|
797
|
+
});
|
|
798
|
+
if (p3.isCancel(go) || !go) {
|
|
799
|
+
p3.cancel("Nothing was changed.");
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
const { backup, removed } = removeBlock(env.rcFile);
|
|
803
|
+
p3.log.success(removed ? "Removed the managed block from ~/.zshrc" : "No managed block found in ~/.zshrc");
|
|
804
|
+
if (backup) p3.log.info(`Your .zshrc as it was a moment ago: ${tilde(backup)}`);
|
|
805
|
+
const purge = await p3.confirm({
|
|
806
|
+
message: `Also delete ${tilde(CONFIG_DIR)}? (this includes your custom.zsh and rc backups)`,
|
|
807
|
+
initialValue: false
|
|
808
|
+
});
|
|
809
|
+
if (p3.isCancel(purge)) {
|
|
810
|
+
p3.outro("Left the config directory in place.");
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
if (purge) {
|
|
814
|
+
if (existsSync6(CONFIG_DIR)) rmSync(CONFIG_DIR, { recursive: true, force: true });
|
|
815
|
+
p3.log.success(`Deleted ${tilde(CONFIG_DIR)}`);
|
|
816
|
+
} else {
|
|
817
|
+
p3.log.info(`Kept ${tilde(CONFIG_DIR)} \u2014 reinstate any time with ${pc7.cyan("shellup apply")}`);
|
|
818
|
+
p3.log.info(`Backups of your .zshrc are in ${tilde(BACKUP_DIR)}`);
|
|
819
|
+
}
|
|
820
|
+
p3.outro(`Done. Reload with ${pc7.cyan("exec zsh")}.`);
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// src/cli.ts
|
|
824
|
+
function help() {
|
|
825
|
+
console.log(`
|
|
826
|
+
${pc8.bgCyan(pc8.black(" shellup "))} ${pc8.dim("v" + version())} ${pc8.dim("a beautiful, fast zsh in one command")}
|
|
827
|
+
|
|
828
|
+
${pc8.bold("Usage")}
|
|
829
|
+
${pc8.cyan("shellup")} ${pc8.dim("<command>")}
|
|
830
|
+
|
|
831
|
+
${pc8.bold("Commands")}
|
|
832
|
+
${pc8.cyan("init")} Set up your shell \u2014 interactive, backs up your .zshrc
|
|
833
|
+
${pc8.cyan("doctor")} Check what's wired up and what isn't
|
|
834
|
+
${pc8.cyan("theme")} ${pc8.dim("[name]")} Switch prompt theme ${pc8.dim("(" + THEMES.map((t) => t.name).join(", ") + ")")}
|
|
835
|
+
${pc8.cyan("apply")} Regenerate shell files from config.json
|
|
836
|
+
${pc8.cyan("uninstall")} Remove the .zshrc block, optionally the config too
|
|
837
|
+
|
|
838
|
+
${pc8.bold("Flags")}
|
|
839
|
+
${pc8.cyan("-y, --yes")} Skip the install confirmation during init
|
|
840
|
+
${pc8.cyan("-v, --version")} Print version
|
|
841
|
+
${pc8.cyan("-h, --help")} This message
|
|
842
|
+
|
|
843
|
+
${pc8.dim("Config lives in ~/.config/shellup. Your own overrides go in custom.zsh,")}
|
|
844
|
+
${pc8.dim("which shellup sources last and never overwrites.")}
|
|
845
|
+
`);
|
|
846
|
+
}
|
|
847
|
+
async function main() {
|
|
848
|
+
const argv = process.argv.slice(2);
|
|
849
|
+
const flags = new Set(argv.filter((a) => a.startsWith("-")));
|
|
850
|
+
const [command, ...rest] = argv.filter((a) => !a.startsWith("-"));
|
|
851
|
+
if (flags.has("-v") || flags.has("--version")) {
|
|
852
|
+
console.log(version());
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
855
|
+
if (flags.has("-h") || flags.has("--help")) return help();
|
|
856
|
+
switch (command) {
|
|
857
|
+
case void 0:
|
|
858
|
+
case "init":
|
|
859
|
+
return init({ yes: flags.has("-y") || flags.has("--yes") });
|
|
860
|
+
case "doctor":
|
|
861
|
+
return doctor();
|
|
862
|
+
case "theme":
|
|
863
|
+
return theme(rest[0]);
|
|
864
|
+
case "apply":
|
|
865
|
+
return applyCommand();
|
|
866
|
+
case "uninstall":
|
|
867
|
+
return uninstall();
|
|
868
|
+
case "help":
|
|
869
|
+
return help();
|
|
870
|
+
default:
|
|
871
|
+
console.error(`
|
|
872
|
+
${pc8.red("Unknown command:")} ${command}
|
|
873
|
+
Run ${pc8.cyan("shellup --help")} to see what's available.
|
|
874
|
+
`);
|
|
875
|
+
process.exitCode = 1;
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
main().catch((err) => {
|
|
879
|
+
console.error(`
|
|
880
|
+
${pc8.red("shellup failed:")} ${err instanceof Error ? err.message : String(err)}
|
|
881
|
+
`);
|
|
882
|
+
process.exitCode = 1;
|
|
883
|
+
});
|