theamify-cli 1.0.1

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Don Artkins
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,286 @@
1
+ # šŸŽØ theamify
2
+
3
+ > **GRUB Theme Manager & Interactive Browser Wizard**
4
+ > Browse, **preview (terminal thumbnails)**, download and apply GRUB boot themes — powered by the original `theamify` engine, now on npm as **`theamify-cli`**.
5
+
6
+ ---
7
+
8
+ ## šŸš€ Install (global — run from anywhere)
9
+
10
+ **One command, installed globally so `theamify` works from any directory on your machine:**
11
+
12
+ ```bash
13
+ npm install -g theamify-cli
14
+ ```
15
+
16
+ The `-g` global flag puts the `theamify` command on your system `PATH` (or provisions a user-local runtime), so you can call it from **any folder and any terminal**:
17
+
18
+ ```bash
19
+ theamify # ✨ interactive theme browser wizard
20
+ theamify list # list all themes with status
21
+ theamify info <name> # details + terminal preview (needs chafa)
22
+ theamify get <name> # download & cache a theme
23
+ sudo theamify use <name> # apply to GRUB + rebuild
24
+ theamify status # GRUB & dependency status
25
+ theamify doctor # diagnose install, GRUB & dependencies
26
+ theamify uninstall # remove theamify (asks before deleting anything)
27
+ ```
28
+
29
+ > šŸ’” Already installed? Upgrading is the same command — npm's **global** install fetches the latest release and your downloaded themes & registry edits are preserved:
30
+ >
31
+ > ```bash
32
+ > npm install -g theamify-cli@latest
33
+ > ```
34
+
35
+ > 🧰 Prefer a zero-install run (no permanent install)? Use npx directly:
36
+ >
37
+ > ```bash
38
+ > npx -y theamify-cli
39
+ > ```
40
+
41
+ ---
42
+
43
+ ## ✨ What's New in v1.0.0 (npm wizard release)
44
+
45
+ | Feature | Details |
46
+ |---|---|
47
+ | šŸ§™ **Interactive browser wizard** | `theamify` with no args → a clack-powered picker (mirroring GitSwitch) to **browse → preview → apply** |
48
+ | šŸ–¼ļø **Terminal thumbnail preview** | Pick a theme and see its actual boot-screen preview inline via `chafa` before you commit |
49
+ | šŸ“¦ **npm global install** | One command, run from anywhere — engine provisions itself under `~/.local/share/theamify` (writable, no sudo needed) |
50
+ | 🩺 **`theamify doctor`** | Diagnose install, GRUB, active theme, registry & dependencies |
51
+ | āŒØļø **Ctrl+C / Ctrl+Z safe** | Terminal signal safety net so interactive `sudo` during `use` is interruptible |
52
+
53
+ ---
54
+
55
+ ## šŸ“‹ Requirements
56
+
57
+ | Tool | Purpose |
58
+ |---|---|
59
+ | `bash` | Runs the theamify engine |
60
+ | `git` | Downloading / cloning themes |
61
+ | `curl` **or** `wget` | Downloading |
62
+ | `chafa` | Terminal thumbnail previews — **auto-installed by the wizard** |
63
+ | `sudo` | Only applying a GRUB theme (`theamify use`) |
64
+
65
+ ---
66
+
67
+ ## Rosetta Stone — classic tool vs npm CLI
68
+
69
+ The npm CLI is a thin Node wrapper around the original Bash engine, so every
70
+ non-interactive command still behaves identically:
71
+
72
+ | npm CLI | Original tool |
73
+ |---|---|
74
+ | `theamify list` | `theamify list` |
75
+ | `theamify get <n>` | `theamify get <n>` |
76
+ | `sudo theamify use <n>` | `sudo theamify use <n>` |
77
+ | `theamify info <n>` | `theamify info <n>` |
78
+ | `theamify add/del/remove/open/update` | same names |
79
+
80
+ `theamify <no args>` is where the wizard differs — it opens the interactive
81
+ browser/preview/apply flow instead of the flat TUI menu.
82
+
83
+ ---
84
+
85
+ ## Theme Previews in the Terminal
86
+
87
+ chafa is a **first-class dependency of the wizard** — the first step of every
88
+ wizard run ensures it's present (installing it when missing), updates it on
89
+ `theamify update`, and removes it on `theamify uninstall`.
90
+
91
+ ```bash
92
+ theamify # Step 1: ensures chafa (prompts to install if missing)
93
+ ```
94
+
95
+ Or check/install it directly via your package manager:
96
+ ```bash
97
+ sudo apt install chafa
98
+ ```
99
+
100
+ ```bash
101
+ theamify # pick a theme → auto-preview
102
+ theamify info <name> # details + thumbnail preview
103
+ theamify update # also updates chafa
104
+ ```
105
+
106
+ ---
107
+
108
+ ## Themes Included
109
+
110
+ | Name | Style | Source |
111
+ |---|---|---|
112
+ | CyberEXS | Cyberpunk dark | [gnome-look](https://www.gnome-look.org/p/1968990) |
113
+ | CyberSynchro | Cyberpunk teal | [gnome-look](https://www.gnome-look.org/p/1972621) |
114
+ | Space-Isolation | Sci-Fi | [gnome-look](https://www.gnome-look.org/p/2296342) |
115
+ | Kawaii-GRUB | Anime | [gnome-look](https://www.gnome-look.org/p/2218890) |
116
+ | Kayoko-Onikata | Anime / Cat | [gnome-look](https://www.gnome-look.org/p/2350900) |
117
+ | Matrices | Abstract tech | [gnome-look](https://www.gnome-look.org/p/2271298) |
118
+ | Particle | Abstract | [gnome-look](https://www.gnome-look.org/p/2269763) |
119
+ | Zzz-GRUB | Chill / Cat | [gnome-look](https://www.gnome-look.org/p/2354136) |
120
+
121
+ ---
122
+
123
+ ## šŸ“ Project Structure (npm package)
124
+
125
+ ```
126
+ bin/theamify.js # npm bin → src/cli.js
127
+ src/cli.js # command router + signal safety net
128
+ src/core/engine.js # runtime provisioning & engine forwarding
129
+ src/lib/conf.js # themes.conf registry parser
130
+ src/commands/manage.js # doctor / status / uninstall
131
+ src/wizard/browse.js # interactive browser + chafa preview + apply
132
+ vendor/ # bundled bash engine (theamify + lib/ + config/)
133
+ ```
134
+
135
+ ---
136
+
137
+ ## šŸ—‘ Uninstalling
138
+
139
+ ```bash
140
+ theamify uninstall # interactive — removes the engine runtime
141
+ npm uninstall -g theamify-cli
142
+ ```
143
+
144
+ Your active GRUB theme and `/etc/default/grub` settings are always preserved.
145
+
146
+ ---
147
+
148
+ ## šŸ“„ License
149
+
150
+ MIT — see [LICENSE](LICENSE). Don Artkins 2026.
151
+
152
+ ## Project Structure
153
+ ```bash
154
+ theamify/
155
+
156
+ |-- theamify # Main CLI executable
157
+
158
+ |-- install.sh # System-wide installer
159
+
160
+ |-- uninstall.sh # Remover
161
+
162
+ |-- update.sh # Self-updater (git pull + reinstall)
163
+
164
+ |-- lib/
165
+
166
+ | |-- colors.sh # ANSI colors & UI components
167
+
168
+ | |-- utils.sh # Helpers, git clone, dep checks
169
+
170
+ | |-- grub.sh # GRUB detection & apply logic
171
+
172
+ | `-- themes.sh # Theme registry CRUD & download
173
+
174
+ |-- config/
175
+
176
+ | `-- themes.conf # Theme registry database
177
+
178
+ |-- themes/ # Downloaded theme cache (auto-populated)
179
+
180
+ |-- .repo_cache/ # Git clone cache (auto-populated)
181
+
182
+ |-- .gitignore
183
+
184
+ |-- LICENSE # MIT
185
+
186
+ `-- CONTRIBUTING.md
187
+ ```
188
+
189
+ ## Install
190
+
191
+ ```bash
192
+ git clone https://github.com/DonArtkins/theamify
193
+ cd theamify
194
+ sudo ./install.sh
195
+ ```
196
+
197
+ After install, `theamify` is available system-wide - run it from any terminal.
198
+ Only `theamify use` requires `sudo`. All other commands run as a normal user.
199
+
200
+ ### Updating an existing install
201
+
202
+ `install.sh` deliberately leaves an already-installed `config/themes.conf`
203
+ alone, so your own `add`/`del` edits via `theamify` survive a reinstall.
204
+ That also means a plain `sudo ./install.sh` will **not** pick up registry
205
+ changes from this checkout (new/removed/edited entries) - pass `--sync-conf`
206
+ when you want that:
207
+
208
+ ```bash
209
+ sudo ./install.sh --sync-conf
210
+ ```
211
+
212
+ See [CONTRIBUTING.md](CONTRIBUTING.md#developing-against-an-existing-install)
213
+ for the full dev-vs-installed workflow.
214
+
215
+ ## Usage
216
+
217
+ ```bash
218
+ theamify # Interactive TUI menu
219
+ theamify list # List all themes with status
220
+ theamify info <name> # Show details + preview (needs chafa)
221
+ theamify get <name> # Download & cache a theme
222
+ theamify get --all # Download every theme
223
+ sudo theamify use <name> # Apply theme to GRUB + rebuild
224
+ theamify update <name> # Re-download one theme
225
+ theamify update # Re-download all cached themes
226
+ theamify open <name> # Open source page in browser
227
+ theamify add <github-url> # Add new theme to registry
228
+ theamify del <name> # Remove theme from registry
229
+ theamify remove <name> # Clear local cache (keeps registry entry)
230
+ theamify status # Show GRUB + dependency status
231
+ theamify clean # Clear repo clone cache
232
+ ```
233
+
234
+ ## Theme Previews in Terminal
235
+
236
+ Install `chafa` for inline image previews in `theamify info`:
237
+
238
+ ```bash
239
+ sudo apt install chafa
240
+ ```
241
+
242
+ ## Themes Included
243
+
244
+ | Name | Style | Source |
245
+ |------------------|-----------------|--------|
246
+ | CyberEXS | Cyberpunk dark | [gnome-look](https://www.gnome-look.org/p/1968990) |
247
+ | CyberSynchro | Cyberpunk teal | [gnome-look](https://www.gnome-look.org/p/1972621) |
248
+ | Space-Isolation | Sci-Fi | [gnome-look](https://www.gnome-look.org/p/2296342) |
249
+ | Kawaii-GRUB | Anime | [gnome-look](https://www.gnome-look.org/p/2218890) |
250
+ | Kayoko-Onikata | Anime / Cat | [gnome-look](https://www.gnome-look.org/p/2350900) |
251
+ | Matrices | Abstract tech | [gnome-look](https://www.gnome-look.org/p/2271298) |
252
+ | Particle | Abstract | [gnome-look](https://www.gnome-look.org/p/2269763) |
253
+ | Zzz-GRUB | Chill / Cat | [gnome-look](https://www.gnome-look.org/p/2354136) |
254
+
255
+ > **Matrices / Particle note:** both repos ship no pre-built theme folder -
256
+ > `backgrounds/`, `common/`, and `config/` are raw assets, and `theme.txt` is
257
+ > only produced at build time by their `generate.sh` (`-t [window|sidebar]
258
+ > -s [1080p|2k|4k]`). The registry entries use a `generate:<args>` SUBDIR so
259
+ > `theamify get` drives that script automatically - see CONTRIBUTING.md for
260
+ > how that mechanism works if you want to add another theme shaped like this.
261
+
262
+ ## Add Your Own Theme
263
+
264
+ ```bash
265
+ theamify add https://github.com/someone/their-grub-theme
266
+ ```
267
+
268
+ Follow the interactive wizard - it asks for name, subdir, description, and tags,
269
+ then appends the entry to `themes.conf`.
270
+
271
+ ## Uninstall
272
+
273
+ ```bash
274
+ sudo ./uninstall.sh
275
+ ```
276
+
277
+ Your active GRUB theme and `/etc/default/grub` settings are preserved.
278
+
279
+ ## Contributing
280
+
281
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for setup, coding conventions, and how
282
+ to submit a theme or a patch.
283
+
284
+ ## License
285
+
286
+ MIT - see [LICENSE](LICENSE). Don Artkins 2026.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import '../src/cli.js';
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "theamify-cli",
3
+ "version": "1.0.1",
4
+ "description": "theamify — GRUB theme manager & interactive browser wizard. Browse, preview, download and apply GRUB boot themes from the terminal.",
5
+ "type": "module",
6
+ "main": "src/cli.js",
7
+ "bin": {
8
+ "theamify": "bin/theamify.js"
9
+ },
10
+ "files": [
11
+ "bin/",
12
+ "src/",
13
+ "vendor/"
14
+ ],
15
+ "engines": {
16
+ "node": ">=18"
17
+ },
18
+ "scripts": {
19
+ "test": "npm run test:syntax && node --test",
20
+ "test:syntax": "bash -n vendor/theamify && bash -n vendor/lib/utils.sh && bash -n vendor/lib/grub.sh && bash -n vendor/lib/themes.sh && bash -n vendor/lib/colors.sh",
21
+ "publish:dry-run": "npm publish --dry-run --access public",
22
+ "release:patch": "npm test && npm version patch -m \"chore(release): %s\" && git push origin main --follow-tags",
23
+ "release:minor": "npm test && npm version minor -m \"chore(release): %s\" && git push origin main --follow-tags",
24
+ "release:major": "npm test && npm version major -m \"chore(release): %s\" && git push origin main --follow-tags"
25
+ },
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "git+https://github.com/DonArtkins/theamify.git"
29
+ },
30
+ "homepage": "https://github.com/DonArtkins/theamify#readme",
31
+ "bugs": {
32
+ "url": "https://github.com/DonArtkins/theamify/issues"
33
+ },
34
+ "keywords": [
35
+ "grub",
36
+ "theme",
37
+ "grub-theme",
38
+ "linux",
39
+ "boot",
40
+ "wizard",
41
+ "cli",
42
+ "theamify"
43
+ ],
44
+ "author": "Don Artkins (https://github.com/DonArtkins)",
45
+ "license": "MIT",
46
+ "dependencies": {
47
+ "@clack/prompts": "^0.7.0",
48
+ "citty": "^0.2.2",
49
+ "execa": "^8.0.1",
50
+ "picocolors": "^1.1.1"
51
+ }
52
+ }
package/src/cli.js ADDED
@@ -0,0 +1,109 @@
1
+ import { defineCommand, runMain } from 'citty';
2
+ import pc from 'picocolors';
3
+ import { createRequire } from 'node:module';
4
+ import { runThemeBrowser } from './wizard/browse.js';
5
+ import { runDoctor, runStatus, runUninstallWizard } from './commands/manage.js';
6
+ import { updateChafa } from './lib/chafa.js';
7
+ import { runEngine } from './core/engine.js';
8
+
9
+ const require = createRequire(import.meta.url);
10
+ const pkg = require('../package.json');
11
+
12
+ /** Commands forwarded straight to the bash engine. */
13
+ const ENGINE_COMMANDS = ['list', 'ls', 'info', 'show', 'get', 'fetch', 'download', 'remove', 'rm', 'uncache', 'add', 'del', 'delete', 'update', 'open', 'browse', 'clean', 'purge-cache', 'help', '-h', '-V', '--version'];
14
+
15
+ /**
16
+ * Forward argv to the bash engine and mirror its exit status onto this process.
17
+ * @param {string[]} args
18
+ */
19
+ async function forwardEngine(args) {
20
+ const res = await runEngine(args);
21
+ if (res && Number.isInteger(res.exitCode) && res.exitCode !== 0) {
22
+ process.exitCode = res.exitCode;
23
+ }
24
+ return res;
25
+ }
26
+
27
+ function printUsage() {
28
+ console.log(`
29
+ ${pc.bold('theamify')} ${pc.dim(`v${pkg.version}`)} — GRUB theme manager
30
+
31
+ ${pc.bold('Usage:')}
32
+ theamify ✨ Interactive theme browser wizard (pick → preview → apply)
33
+ theamify list List all themes with status
34
+ theamify info <name> Show details + terminal preview (needs chafa)
35
+ theamify get <name> Download & cache a theme
36
+ theamify get --all Download every theme
37
+ theamify use <name> Apply theme to GRUB ${pc.dim('[sudo]')}
38
+ theamify update [<name>] Re-download cached themes
39
+ theamify remove <name> Clear local cache (keeps registry entry)
40
+ theamify add <github-url> Add a theme to the registry
41
+ theamify del <name> Remove a theme from the registry
42
+ theamify open <name> Open the theme source page in a browser
43
+ theamify status Show GRUB & dependency status
44
+ theamify doctor Diagnose install, GRUB & dependencies
45
+ theamify uninstall Remove theamify (asks before deleting anything)
46
+ theamify clean Clear the repo clone cache
47
+ `);
48
+ }
49
+
50
+ /**
51
+ * Terminal safety net: when an interactive child (sudo password prompt) is
52
+ * running, the terminal is in canonical mode and Ctrl+C/Z arrive as real
53
+ * signals. Restore the cursor and exit cleanly instead of hanging.
54
+ */
55
+ function terminateAndRestore(code) {
56
+ try {
57
+ if (process.stdout.isTTY) process.stdout.write('\x1b[?25h\n\r');
58
+ } catch { /* best effort */ }
59
+ process.exit(code);
60
+ }
61
+ if (process.platform !== 'win32') process.on('SIGTSTP', () => terminateAndRestore(130)); // Ctrl+Z
62
+ process.on('SIGINT', () => terminateAndRestore(130)); // Ctrl+C
63
+ process.on('SIGTERM', () => terminateAndRestore(143)); // kill/terminate
64
+
65
+ const main = defineCommand({
66
+ meta: {
67
+ name: 'theamify',
68
+ version: pkg.version,
69
+ description: 'GRUB theme manager & interactive browser wizard',
70
+ },
71
+ async run({ args }) {
72
+ const [cmd, ...rest] = args._;
73
+
74
+ // No arguments → the interactive theme browser wizard.
75
+ if (!cmd) return runThemeBrowser();
76
+
77
+ switch (cmd) {
78
+ case 'wizard':
79
+ case 'browse':
80
+ return runThemeBrowser();
81
+ case 'status':
82
+ return runStatus();
83
+ case 'doctor':
84
+ return runDoctor();
85
+ case 'uninstall':
86
+ return runUninstallWizard();
87
+ case 'update':
88
+ // Self-update path: refresh the thumbnail renderer, then forward theme
89
+ // re-downloads to the engine (no name → all cached themes).
90
+ await updateChafa();
91
+ return forwardEngine(['update', ...rest]);
92
+ case 'use':
93
+ case 'apply':
94
+ case 'set':
95
+ return forwardEngine(['use', ...rest]);
96
+ case 'info':
97
+ case 'show':
98
+ return forwardEngine(['info', ...rest]);
99
+ default:
100
+ if (ENGINE_COMMANDS.includes(cmd)) return forwardEngine(args._);
101
+ console.error(pc.red(`Unknown command: ${cmd}`));
102
+ printUsage();
103
+ process.exitCode = 1;
104
+ return undefined;
105
+ }
106
+ },
107
+ });
108
+
109
+ runMain(main);
@@ -0,0 +1,122 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import * as p from '@clack/prompts';
4
+ import pc from 'picocolors';
5
+ import { execa } from 'execa';
6
+ import {
7
+ findInstalledRuntime,
8
+ USER_DIR,
9
+ USER_BIN_LINK,
10
+ resolveEngine,
11
+ } from '../core/engine.js';
12
+ import { parseThemes } from '../lib/conf.js';
13
+ import { uninstallChafa } from '../lib/chafa.js';
14
+
15
+ /** `theamify doctor` — installation, GRUB and dependency health. */
16
+ export async function runDoctor() {
17
+ console.log(pc.bold('\n🩺 theamify Doctor\n'));
18
+
19
+ const found = findInstalledRuntime();
20
+ const engine = await resolveEngine();
21
+ console.log(` Engine : ${pc.cyan(engine)}`);
22
+ console.log(` Runtime : ${found ? pc.cyan(found.dir) : pc.dim('(provisioned on demand)')}`);
23
+
24
+ // Registry / theme count
25
+ try {
26
+ const themes = parseThemes();
27
+ const cached = themes.filter((t) => fs.existsSync(path.join(USER_DIR, 'themes', t.name)));
28
+ console.log(` Registry : ${themes.length} theme(s), ${cached.length} cached`);
29
+ } catch (e) {
30
+ console.log(` Registry : ${pc.red(`unreadable (${e.message})`)}`);
31
+ }
32
+
33
+ // GRUB active theme + dir
34
+ const grubDir = grubDetectDir();
35
+ console.log(` GRUB dir : ${pc.cyan(grubDir)}`);
36
+ const active = activeThemeLabel();
37
+ console.log(` Active : ${active ? pc.green('⭐ ' + active) : pc.dim('none')}`);
38
+
39
+ // Dependencies
40
+ const deps = ['git', 'chafa', 'wget', 'curl'].map((d) => [d, which(d)]);
41
+ console.log(' Deps : ' + deps.map(([d, ok]) => `${d}:${ok ? pc.green('āœ“') : pc.dim('—')}`).join(' '));
42
+ const grub = ['update-grub', 'grub-mkconfig', 'grub2-mkconfig'].filter((c) => which(c));
43
+ console.log(` GRUB cmd : ${grub.length ? grub.join(', ') : pc.red('none found')}`);
44
+ console.log();
45
+ }
46
+
47
+ function which(bin) {
48
+ try { return execa.sync('which', [bin], { reject: false }).stdout.trim(); } catch { return ''; }
49
+ }
50
+
51
+ function grubDetectDir() {
52
+ for (const d of ['/boot/grub/themes', '/boot/grub2/themes', '/usr/share/grub/themes']) {
53
+ if (fs.existsSync(d)) return d;
54
+ }
55
+ return '/boot/grub/themes';
56
+ }
57
+
58
+ /** Name of the currently-active GRUB theme, from /etc/default/grub. */
59
+ function activeThemeLabel() {
60
+ try {
61
+ const cfg = fs.readFileSync('/etc/default/grub', 'utf8');
62
+ const m = cfg.match(/^GRUB_THEME="?([^"\n]+)"?/m);
63
+ if (!m) return '';
64
+ const dir = path.dirname(m[1]);
65
+ return (dir === '.' ? '' : path.basename(dir)) || m[1];
66
+ } catch {
67
+ return '';
68
+ }
69
+ }
70
+
71
+ /** `theamify status` — compact runtime status (wraps engine). */
72
+ export async function runStatus() {
73
+ const engine = await resolveEngine();
74
+ const res = await execa('bash', [engine, 'status'], { stdio: 'inherit', reject: false });
75
+ if (res.exitCode !== 0) process.exit(res.exitCode);
76
+ }
77
+
78
+ /** `theamify uninstall` — interactive, every destructive step confirmed. */
79
+ export async function runUninstallWizard() {
80
+ p.intro(pc.bgRed(pc.black(' theamify Uninstaller ')));
81
+ const found = findInstalledRuntime();
82
+ if (!found) p.log.info('No installed theamify runtime found (nothing to remove).');
83
+
84
+ let removed = false;
85
+ if (found) {
86
+ const confirm = await p.confirm({
87
+ message: `Remove theamify files at ${found.dir}/?`,
88
+ initialValue: true,
89
+ });
90
+ if (!p.isCancel(confirm) && confirm) {
91
+ try {
92
+ const dir = found.dir;
93
+ const owner = fs.statSync(dir).uid;
94
+ if (owner === 0 && process.getuid() !== 0) {
95
+ await execa('sudo', ['rm', '-rf', dir], { stdio: 'inherit' });
96
+ } else {
97
+ fs.rmSync(dir, { recursive: true, force: true });
98
+ }
99
+ removed = true;
100
+ // remove the PATH symlink arm (never touches ~/.bashrc blocks without asking? leave PATH block — it's harmless)
101
+ if (USER_BIN_LINK.startsWith(USER_DIR)) {
102
+ fs.rmSync(USER_BIN_LINK, { force: true });
103
+ }
104
+ } catch {
105
+ p.log.warn(`Could not remove ${found.dir}.`);
106
+ }
107
+ }
108
+ }
109
+
110
+ const keepGrub = await p.confirm({
111
+ message: 'Keep your currently-applied GRUB theme? (recommended)',
112
+ initialValue: true,
113
+ });
114
+ if (p.isCancel(keepGrub)) { p.cancel('Aborted.'); process.exit(0); }
115
+
116
+ // Optionally remove the thumbnail renderer the wizard installed.
117
+ await uninstallChafa();
118
+
119
+ p.outro(pc.green(
120
+ `Uninstalled.${removed ? ' Runtime removed.' : ''}${keepGrub ? ' Active GRUB theme left in place.' : ' To revert GRUB, remove GRUB_THEME= from /etc/default/grub and rebuild.'}`,
121
+ ));
122
+ }