brew-tui 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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 MoLines Designs
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,105 @@
1
+ # Brew-TUI
2
+
3
+ A visual terminal UI for [Homebrew](https://brew.sh) package management.
4
+
5
+ ![License](https://img.shields.io/badge/license-MIT-blue)
6
+ ![Node](https://img.shields.io/badge/node-%3E%3D18-brightgreen)
7
+ ![npm](https://img.shields.io/npm/v/brew-tui)
8
+
9
+ ## Features
10
+
11
+ - **Dashboard** -- overview of installed packages, outdated counts, services, and system info
12
+ - **Installed** -- browse and filter formulae and casks with version info and status badges
13
+ - **Search** -- find and install packages directly from the TUI
14
+ - **Outdated** -- see available upgrades with version comparison arrows, upgrade individually or all at once
15
+ - **Services** -- start, stop, and restart Homebrew services
16
+ - **Doctor** -- run `brew doctor` and see warnings at a glance
17
+ - **Package Info** -- detailed view with dependencies, caveats, and quick install/uninstall
18
+
19
+ ### Pro Features
20
+
21
+ - **Profiles** -- export and import your Homebrew setup across machines
22
+ - **Smart Cleanup** -- find orphaned packages and reclaim disk space
23
+ - **Action History** -- track every install, uninstall, and upgrade
24
+ - **Security Audit** -- scan packages against the OSV vulnerability database
25
+
26
+ ## Install
27
+
28
+ ```bash
29
+ # npm / pnpm / yarn / bun (all use the same npm registry)
30
+ npm install -g brew-tui
31
+ pnpm add -g brew-tui
32
+ yarn global add brew-tui
33
+ bun add -g brew-tui
34
+
35
+ # Homebrew
36
+ brew tap MoLinesGitHub/tap
37
+ brew install brew-tui
38
+
39
+ # GitHub Packages
40
+ npm install -g @MoLinesGitHub/brew-tui --registry https://npm.pkg.github.com
41
+
42
+ # npx (run without installing)
43
+ npx brew-tui
44
+ ```
45
+
46
+ ## Usage
47
+
48
+ ```bash
49
+ brew-tui # Launch the TUI
50
+ brew-tui status # Show license status
51
+ brew-tui activate <key> # Activate Pro license
52
+ brew-tui deactivate # Deactivate Pro license
53
+ ```
54
+
55
+ ### Install BrewBar (Pro)
56
+
57
+ BrewBar is a companion macOS menu bar app that shows outdated package counts, sends notifications, and lets you upgrade packages without opening a terminal. Pro users can install it directly from the CLI:
58
+
59
+ ```bash
60
+ brew-tui install-brewbar # Download & install to /Applications
61
+ brew-tui install-brewbar --force # Reinstall / update
62
+ brew-tui uninstall-brewbar # Remove from /Applications
63
+ ```
64
+
65
+ ### Keyboard Navigation
66
+
67
+ | Key | Action |
68
+ |-----|--------|
69
+ | `1`-`0` | Jump to view |
70
+ | `Tab` / `Shift+Tab` | Cycle views |
71
+ | `j` / `k` | Navigate lists |
72
+ | `Enter` | Select / confirm |
73
+ | `/` | Search / filter |
74
+ | `Escape` | Go back |
75
+ | `L` | Toggle language (en/es) |
76
+ | `q` | Quit |
77
+
78
+ ## Language
79
+
80
+ Brew-TUI supports English and Spanish. The language is detected automatically from your system locale (`LANG` environment variable). You can also:
81
+
82
+ - Pass `--lang=es` or `--lang=en` as a CLI flag
83
+ - Press `L` inside the TUI to toggle between languages
84
+
85
+ ## BrewBar
86
+
87
+ BrewBar is a companion macOS menu bar app (Swift 6 / SwiftUI) that shows outdated package counts, sends notifications, and lets you upgrade packages without opening a terminal.
88
+
89
+ BrewBar lives in the `menubar/` directory and is built separately with [Tuist](https://tuist.io):
90
+
91
+ ```bash
92
+ cd menubar
93
+ tuist generate
94
+ xcodebuild -workspace BrewBar.xcworkspace -scheme BrewBar build
95
+ ```
96
+
97
+ ## Requirements
98
+
99
+ - **Node.js** >= 18
100
+ - **Homebrew** installed on your system
101
+ - **macOS** 14+ (for BrewBar)
102
+
103
+ ## License
104
+
105
+ [MIT](LICENSE) -- MoLines Designs
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import '../build/index.js';
@@ -0,0 +1,64 @@
1
+ import {
2
+ t,
3
+ verifyPro
4
+ } from "./chunk-P6PTN4HR.js";
5
+
6
+ // src/lib/brewbar-installer.ts
7
+ import { rm, access } from "fs/promises";
8
+ import { createWriteStream } from "fs";
9
+ import { pipeline } from "stream/promises";
10
+ import { execFile } from "child_process";
11
+ import { promisify } from "util";
12
+ var execFileAsync = promisify(execFile);
13
+ var BREWBAR_APP_PATH = "/Applications/BrewBar.app";
14
+ var DOWNLOAD_URL = "https://github.com/MoLinesGitHub/Brew-TUI/releases/latest/download/BrewBar.app.zip";
15
+ var TMP_ZIP = "/tmp/BrewBar.app.zip";
16
+ async function isBrewBarInstalled() {
17
+ try {
18
+ await access(BREWBAR_APP_PATH);
19
+ return true;
20
+ } catch {
21
+ return false;
22
+ }
23
+ }
24
+ async function installBrewBar(force = false) {
25
+ if (process.platform !== "darwin") {
26
+ throw new Error(t("cli_brewbarMacOnly"));
27
+ }
28
+ if (!verifyPro()) {
29
+ throw new Error(t("cli_brewbarProRequired"));
30
+ }
31
+ if (!force && await isBrewBarInstalled()) {
32
+ throw new Error(t("cli_brewbarAlreadyInstalled"));
33
+ }
34
+ console.log(t("cli_brewbarInstalling"));
35
+ const res = await fetch(DOWNLOAD_URL);
36
+ if (!res.ok || !res.body) {
37
+ throw new Error(t("cli_brewbarDownloadFailed", { error: `HTTP ${res.status}` }));
38
+ }
39
+ const fileStream = createWriteStream(TMP_ZIP);
40
+ await pipeline(res.body, fileStream);
41
+ if (force && await isBrewBarInstalled()) {
42
+ await rm(BREWBAR_APP_PATH, { recursive: true, force: true });
43
+ }
44
+ try {
45
+ await execFileAsync("ditto", ["-xk", TMP_ZIP, "/Applications/"]);
46
+ } catch (err) {
47
+ throw new Error(t("cli_brewbarDownloadFailed", { error: err instanceof Error ? err.message : String(err) }));
48
+ } finally {
49
+ await rm(TMP_ZIP, { force: true }).catch(() => {
50
+ });
51
+ }
52
+ }
53
+ async function uninstallBrewBar() {
54
+ if (!await isBrewBarInstalled()) {
55
+ throw new Error(t("cli_brewbarNotInstalled"));
56
+ }
57
+ await rm(BREWBAR_APP_PATH, { recursive: true, force: true });
58
+ }
59
+ export {
60
+ installBrewBar,
61
+ isBrewBarInstalled,
62
+ uninstallBrewBar
63
+ };
64
+ //# sourceMappingURL=brewbar-installer-CPCOE3MI.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/brewbar-installer.ts"],"sourcesContent":["import { rm, access } from 'node:fs/promises';\nimport { createWriteStream } from 'node:fs';\nimport { pipeline } from 'node:stream/promises';\nimport { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { t } from '../i18n/index.js';\nimport { verifyPro } from './license/pro-guard.js';\n\nconst execFileAsync = promisify(execFile);\nconst BREWBAR_APP_PATH = '/Applications/BrewBar.app';\nconst DOWNLOAD_URL = 'https://github.com/MoLinesGitHub/Brew-TUI/releases/latest/download/BrewBar.app.zip';\nconst TMP_ZIP = '/tmp/BrewBar.app.zip';\n\nexport async function isBrewBarInstalled(): Promise<boolean> {\n try {\n await access(BREWBAR_APP_PATH);\n return true;\n } catch {\n return false;\n }\n}\n\nexport async function installBrewBar(force = false): Promise<void> {\n // macOS only\n if (process.platform !== 'darwin') {\n throw new Error(t('cli_brewbarMacOnly'));\n }\n\n // Pro check\n if (!verifyPro()) {\n throw new Error(t('cli_brewbarProRequired'));\n }\n\n // Already installed check\n if (!force && await isBrewBarInstalled()) {\n throw new Error(t('cli_brewbarAlreadyInstalled'));\n }\n\n console.log(t('cli_brewbarInstalling'));\n\n // Download zip\n const res = await fetch(DOWNLOAD_URL);\n if (!res.ok || !res.body) {\n throw new Error(t('cli_brewbarDownloadFailed', { error: `HTTP ${res.status}` }));\n }\n\n // Write to tmp file\n const fileStream = createWriteStream(TMP_ZIP);\n await pipeline(res.body as any, fileStream);\n\n // Remove old app if force reinstall\n if (force && await isBrewBarInstalled()) {\n await rm(BREWBAR_APP_PATH, { recursive: true, force: true });\n }\n\n // Unzip to /Applications\n try {\n await execFileAsync('ditto', ['-xk', TMP_ZIP, '/Applications/']);\n } catch (err) {\n throw new Error(t('cli_brewbarDownloadFailed', { error: err instanceof Error ? err.message : String(err) }));\n } finally {\n // Clean up tmp zip\n await rm(TMP_ZIP, { force: true }).catch(() => {});\n }\n}\n\nexport async function uninstallBrewBar(): Promise<void> {\n if (!await isBrewBarInstalled()) {\n throw new Error(t('cli_brewbarNotInstalled'));\n }\n\n await rm(BREWBAR_APP_PATH, { recursive: true, force: true });\n}\n"],"mappings":";;;;;;AAAA,SAAS,IAAI,cAAc;AAC3B,SAAS,yBAAyB;AAClC,SAAS,gBAAgB;AACzB,SAAS,gBAAgB;AACzB,SAAS,iBAAiB;AAI1B,IAAM,gBAAgB,UAAU,QAAQ;AACxC,IAAM,mBAAmB;AACzB,IAAM,eAAe;AACrB,IAAM,UAAU;AAEhB,eAAsB,qBAAuC;AAC3D,MAAI;AACF,UAAM,OAAO,gBAAgB;AAC7B,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAsB,eAAe,QAAQ,OAAsB;AAEjE,MAAI,QAAQ,aAAa,UAAU;AACjC,UAAM,IAAI,MAAM,EAAE,oBAAoB,CAAC;AAAA,EACzC;AAGA,MAAI,CAAC,UAAU,GAAG;AAChB,UAAM,IAAI,MAAM,EAAE,wBAAwB,CAAC;AAAA,EAC7C;AAGA,MAAI,CAAC,SAAS,MAAM,mBAAmB,GAAG;AACxC,UAAM,IAAI,MAAM,EAAE,6BAA6B,CAAC;AAAA,EAClD;AAEA,UAAQ,IAAI,EAAE,uBAAuB,CAAC;AAGtC,QAAM,MAAM,MAAM,MAAM,YAAY;AACpC,MAAI,CAAC,IAAI,MAAM,CAAC,IAAI,MAAM;AACxB,UAAM,IAAI,MAAM,EAAE,6BAA6B,EAAE,OAAO,QAAQ,IAAI,MAAM,GAAG,CAAC,CAAC;AAAA,EACjF;AAGA,QAAM,aAAa,kBAAkB,OAAO;AAC5C,QAAM,SAAS,IAAI,MAAa,UAAU;AAG1C,MAAI,SAAS,MAAM,mBAAmB,GAAG;AACvC,UAAM,GAAG,kBAAkB,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,EAC7D;AAGA,MAAI;AACF,UAAM,cAAc,SAAS,CAAC,OAAO,SAAS,gBAAgB,CAAC;AAAA,EACjE,SAAS,KAAK;AACZ,UAAM,IAAI,MAAM,EAAE,6BAA6B,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE,CAAC,CAAC;AAAA,EAC7G,UAAE;AAEA,UAAM,GAAG,SAAS,EAAE,OAAO,KAAK,CAAC,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EACnD;AACF;AAEA,eAAsB,mBAAkC;AACtD,MAAI,CAAC,MAAM,mBAAmB,GAAG;AAC/B,UAAM,IAAI,MAAM,EAAE,yBAAyB,CAAC;AAAA,EAC9C;AAEA,QAAM,GAAG,kBAAkB,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAC7D;","names":[]}
@@ -0,0 +1,55 @@
1
+ import {
2
+ HISTORY_PATH,
3
+ ensureDataDirs,
4
+ requirePro
5
+ } from "./chunk-P6PTN4HR.js";
6
+
7
+ // src/lib/history/history-logger.ts
8
+ import { readFile, writeFile, rename } from "fs/promises";
9
+ var MAX_ENTRIES = 1e3;
10
+ async function loadHistory() {
11
+ requirePro();
12
+ try {
13
+ const raw = await readFile(HISTORY_PATH, "utf-8");
14
+ const file = JSON.parse(raw);
15
+ const entries = file.entries;
16
+ return Array.isArray(entries) ? entries : [];
17
+ } catch {
18
+ return [];
19
+ }
20
+ }
21
+ async function saveHistory(entries) {
22
+ await ensureDataDirs();
23
+ const file = { version: 1, entries };
24
+ const tmp = HISTORY_PATH + ".tmp";
25
+ await writeFile(tmp, JSON.stringify(file, null, 2), "utf-8");
26
+ await rename(tmp, HISTORY_PATH);
27
+ }
28
+ async function appendEntry(action, packageName, success, error = null) {
29
+ requirePro();
30
+ const entries = await loadHistory();
31
+ const entry = {
32
+ id: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
33
+ action,
34
+ packageName,
35
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
36
+ success,
37
+ error
38
+ };
39
+ entries.unshift(entry);
40
+ if (entries.length > MAX_ENTRIES) {
41
+ entries.length = MAX_ENTRIES;
42
+ }
43
+ await saveHistory(entries);
44
+ }
45
+ async function clearHistory() {
46
+ requirePro();
47
+ await saveHistory([]);
48
+ }
49
+
50
+ export {
51
+ loadHistory,
52
+ appendEntry,
53
+ clearHistory
54
+ };
55
+ //# sourceMappingURL=chunk-3BK3B53S.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/history/history-logger.ts"],"sourcesContent":["import { readFile, writeFile, rename } from 'node:fs/promises';\nimport { HISTORY_PATH, ensureDataDirs } from '../data-dir.js';\nimport { requirePro } from '../license/pro-guard.js';\nimport type { HistoryEntry, HistoryFile, HistoryAction } from './types.js';\n\nconst MAX_ENTRIES = 1000;\n\nexport async function loadHistory(): Promise<HistoryEntry[]> {\n requirePro();\n\n try {\n const raw = await readFile(HISTORY_PATH, 'utf-8');\n const file = JSON.parse(raw) as HistoryFile;\n const entries = file.entries;\n return Array.isArray(entries) ? entries : [];\n } catch {\n return [];\n }\n}\n\nasync function saveHistory(entries: HistoryEntry[]): Promise<void> {\n await ensureDataDirs();\n const file: HistoryFile = { version: 1, entries };\n const tmp = HISTORY_PATH + '.tmp';\n await writeFile(tmp, JSON.stringify(file, null, 2), 'utf-8');\n await rename(tmp, HISTORY_PATH);\n}\n\nexport async function appendEntry(\n action: HistoryAction,\n packageName: string | null,\n success: boolean,\n error: string | null = null,\n): Promise<void> {\n requirePro();\n const entries = await loadHistory();\n\n const entry: HistoryEntry = {\n id: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,\n action,\n packageName,\n timestamp: new Date().toISOString(),\n success,\n error,\n };\n\n entries.unshift(entry);\n\n if (entries.length > MAX_ENTRIES) {\n entries.length = MAX_ENTRIES;\n }\n\n await saveHistory(entries);\n}\n\nexport async function clearHistory(): Promise<void> {\n requirePro();\n await saveHistory([]);\n}\n"],"mappings":";;;;;;;AAAA,SAAS,UAAU,WAAW,cAAc;AAK5C,IAAM,cAAc;AAEpB,eAAsB,cAAuC;AAC3D,aAAW;AAEX,MAAI;AACF,UAAM,MAAM,MAAM,SAAS,cAAc,OAAO;AAChD,UAAM,OAAO,KAAK,MAAM,GAAG;AAC3B,UAAM,UAAU,KAAK;AACrB,WAAO,MAAM,QAAQ,OAAO,IAAI,UAAU,CAAC;AAAA,EAC7C,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAEA,eAAe,YAAY,SAAwC;AACjE,QAAM,eAAe;AACrB,QAAM,OAAoB,EAAE,SAAS,GAAG,QAAQ;AAChD,QAAM,MAAM,eAAe;AAC3B,QAAM,UAAU,KAAK,KAAK,UAAU,MAAM,MAAM,CAAC,GAAG,OAAO;AAC3D,QAAM,OAAO,KAAK,YAAY;AAChC;AAEA,eAAsB,YACpB,QACA,aACA,SACA,QAAuB,MACR;AACf,aAAW;AACX,QAAM,UAAU,MAAM,YAAY;AAElC,QAAM,QAAsB;AAAA,IAC1B,IAAI,GAAG,KAAK,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,GAAG,CAAC,CAAC;AAAA,IAC3D;AAAA,IACA;AAAA,IACA,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,IAClC;AAAA,IACA;AAAA,EACF;AAEA,UAAQ,QAAQ,KAAK;AAErB,MAAI,QAAQ,SAAS,aAAa;AAChC,YAAQ,SAAS;AAAA,EACnB;AAEA,QAAM,YAAY,OAAO;AAC3B;AAEA,eAAsB,eAA8B;AAClD,aAAW;AACX,QAAM,YAAY,CAAC,CAAC;AACtB;","names":[]}