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 +21 -0
- package/README.md +286 -0
- package/bin/theamify.js +2 -0
- package/package.json +52 -0
- package/src/cli.js +109 -0
- package/src/commands/manage.js +122 -0
- package/src/core/engine.js +100 -0
- package/src/lib/chafa.js +143 -0
- package/src/lib/conf.js +55 -0
- package/src/lib/tools.js +76 -0
- package/src/wizard/browse.js +165 -0
- package/vendor/config/themes.conf +36 -0
- package/vendor/lib/colors.sh +151 -0
- package/vendor/lib/grub.sh +179 -0
- package/vendor/lib/themes.sh +283 -0
- package/vendor/lib/utils.sh +135 -0
- package/vendor/theamify +565 -0
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.
|
package/bin/theamify.js
ADDED
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
|
+
}
|