brew-tui 0.6.0 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/build/{brewbar-installer-BAHS6EEZ.js → brewbar-installer-WJZKSELD.js} +3 -3
- package/build/brewbar-installer-WJZKSELD.js.map +1 -0
- package/build/{brewfile-manager-3SERRYNC.js → brewfile-manager-OITSKEHY.js} +4 -4
- package/build/{chunk-U2DRWB7A.js → chunk-5MYWF5D7.js} +2 -2
- package/build/{chunk-MSXH66I2.js → chunk-6NA4INJS.js} +31 -3
- package/build/chunk-6NA4INJS.js.map +1 -0
- package/build/chunk-EHIBIFCB.js +54 -0
- package/build/chunk-EHIBIFCB.js.map +1 -0
- package/build/{chunk-LXF72RCD.js → chunk-KSIAKLE2.js} +3 -3
- package/build/{chunk-4I344KQX.js → chunk-PVSE6XO7.js} +42 -2
- package/build/chunk-PVSE6XO7.js.map +1 -0
- package/build/chunk-QPXROTAP.js +679 -0
- package/build/chunk-QPXROTAP.js.map +1 -0
- package/build/{chunk-KVCVIRWI.js → chunk-Z2VN4VYQ.js} +2 -2
- package/build/compliance-checker-7NMFKWTI.js +12 -0
- package/build/{history-logger-PBDOLKNJ.js → history-logger-ZGYRAFON.js} +3 -3
- package/build/index.js +449 -678
- package/build/index.js.map +1 -1
- package/build/{snapshot-RAPGMAJF.js → snapshot-YWIOFQ5H.js} +7 -3
- package/build/{sync-engine-CAFK4LHA.js → sync-engine-DIYXV66P.js} +7 -5
- package/package.json +4 -4
- package/build/brewbar-installer-BAHS6EEZ.js.map +0 -1
- package/build/chunk-4I344KQX.js.map +0 -1
- package/build/chunk-AIAZQJKL.js +0 -299
- package/build/chunk-AIAZQJKL.js.map +0 -1
- package/build/chunk-MSXH66I2.js.map +0 -1
- package/build/chunk-UWS4A4F5.js +0 -25
- package/build/chunk-UWS4A4F5.js.map +0 -1
- package/build/compliance-checker-X7P623UF.js +0 -12
- /package/build/{brewfile-manager-3SERRYNC.js.map → brewfile-manager-OITSKEHY.js.map} +0 -0
- /package/build/{chunk-U2DRWB7A.js.map → chunk-5MYWF5D7.js.map} +0 -0
- /package/build/{chunk-LXF72RCD.js.map → chunk-KSIAKLE2.js.map} +0 -0
- /package/build/{chunk-KVCVIRWI.js.map → chunk-Z2VN4VYQ.js.map} +0 -0
- /package/build/{compliance-checker-X7P623UF.js.map → compliance-checker-7NMFKWTI.js.map} +0 -0
- /package/build/{history-logger-PBDOLKNJ.js.map → history-logger-ZGYRAFON.js.map} +0 -0
- /package/build/{snapshot-RAPGMAJF.js.map → snapshot-YWIOFQ5H.js.map} +0 -0
- /package/build/{sync-engine-CAFK4LHA.js.map → sync-engine-DIYXV66P.js.map} +0 -0
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](https://www.npmjs.com/package/brew-tui)
|
|
6
6
|
[](https://nodejs.org/)
|
|
7
7
|
[](LICENSE)
|
|
8
|
-
[](https://github.com/
|
|
8
|
+
[](https://github.com/MoLinesDesigns/homebrew-tap)
|
|
9
9
|
[]()
|
|
10
10
|
|
|
11
11
|
A keyboard-driven terminal UI for Homebrew, with a native macOS menu bar companion that watches updates in the background. No daemons, no middleware — both tools call `brew` directly.
|
|
@@ -13,7 +13,7 @@ A keyboard-driven terminal UI for Homebrew, with a native macOS menu bar compani
|
|
|
13
13
|

|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
brew tap
|
|
16
|
+
brew tap MoLinesDesigns/tap
|
|
17
17
|
brew install brew-tui # then just type: brew-tui
|
|
18
18
|
```
|
|
19
19
|
|
|
@@ -36,7 +36,7 @@ You don't memorize `brew outdated && brew upgrade && brew services list && brew
|
|
|
36
36
|
|
|
37
37
|
```bash
|
|
38
38
|
# Homebrew (recommended)
|
|
39
|
-
brew tap
|
|
39
|
+
brew tap MoLinesDesigns/tap
|
|
40
40
|
brew install brew-tui
|
|
41
41
|
|
|
42
42
|
# npm
|
|
@@ -152,7 +152,7 @@ brew-tui install-brewbar --force # Reinstall / update
|
|
|
152
152
|
brew-tui uninstall-brewbar # Remove
|
|
153
153
|
|
|
154
154
|
# Via Homebrew Cask
|
|
155
|
-
brew install --cask
|
|
155
|
+
brew install --cask MoLinesDesigns/tap/brewbar
|
|
156
156
|
```
|
|
157
157
|
|
|
158
158
|
### Build from Source
|
|
@@ -231,7 +231,7 @@ menubar/ # BrewBar (Swift 6 / SwiftUI / Tuist)
|
|
|
231
231
|
## Contributing
|
|
232
232
|
|
|
233
233
|
```bash
|
|
234
|
-
git clone https://github.com/
|
|
234
|
+
git clone https://github.com/MoLinesDesigns/Brew-TUI.git
|
|
235
235
|
cd Brew-TUI
|
|
236
236
|
npm install
|
|
237
237
|
npm run dev # Run with tsx (requires interactive TTY)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
fetchWithTimeout,
|
|
3
3
|
t
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-6NA4INJS.js";
|
|
5
5
|
import "./chunk-KDHEUNRI.js";
|
|
6
6
|
|
|
7
7
|
// src/lib/brewbar-installer.ts
|
|
@@ -15,7 +15,7 @@ import { execFile } from "child_process";
|
|
|
15
15
|
import { promisify } from "util";
|
|
16
16
|
var execFileAsync = promisify(execFile);
|
|
17
17
|
var BREWBAR_APP_PATH = "/Applications/BrewBar.app";
|
|
18
|
-
var DOWNLOAD_URL = "https://github.com/
|
|
18
|
+
var DOWNLOAD_URL = "https://github.com/MoLinesDesigns/Brew-TUI/releases/latest/download/BrewBar.app.zip";
|
|
19
19
|
var MAX_SIZE = 200 * 1024 * 1024;
|
|
20
20
|
async function isBrewBarInstalled() {
|
|
21
21
|
try {
|
|
@@ -125,4 +125,4 @@ export {
|
|
|
125
125
|
launchBrewBar,
|
|
126
126
|
uninstallBrewBar
|
|
127
127
|
};
|
|
128
|
-
//# sourceMappingURL=brewbar-installer-
|
|
128
|
+
//# sourceMappingURL=brewbar-installer-WJZKSELD.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/brewbar-installer.ts"],"sourcesContent":["import { rm, access, readFile } from 'node:fs/promises';\nimport { createWriteStream } from 'node:fs';\nimport { createHash, randomUUID } from 'node:crypto';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport { pipeline } from 'node:stream/promises';\nimport { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { t } from '../i18n/index.js';\nimport { fetchWithTimeout } from './fetch-timeout.js';\n\nconst execFileAsync = promisify(execFile);\nconst BREWBAR_APP_PATH = '/Applications/BrewBar.app';\nconst DOWNLOAD_URL = 'https://github.com/MoLinesDesigns/Brew-TUI/releases/latest/download/BrewBar.app.zip';\nconst MAX_SIZE = 200 * 1024 * 1024; // 200 MB\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(isPro: boolean, 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 (!isPro) {\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 // EP-013: Use unique temp path\n const TMP_ZIP = join(tmpdir(), 'BrewBar-' + randomUUID() + '.zip');\n\n // Download zip (120s timeout for large binary)\n const res = await fetchWithTimeout(DOWNLOAD_URL, {}, 120_000);\n if (!res.ok || !res.body) {\n throw new Error(t('cli_brewbarDownloadFailed', { error: `HTTP ${res.status}` }));\n }\n\n // Reject downloads larger than 200 MB (from Content-Length header)\n const contentLength = Number(res.headers.get('content-length') ?? '0');\n if (contentLength > MAX_SIZE) {\n throw new Error(t('cli_brewbarDownloadFailed', { error: 'Download exceeds 200 MB size limit' }));\n }\n\n // EP-005: Track downloaded bytes during the stream\n let downloadedBytes = 0;\n\n // Write to tmp file with byte counting\n const fileStream = createWriteStream(TMP_ZIP);\n const transformedBody = new ReadableStream({\n async start(controller) {\n const bodyReader = (res.body as ReadableStream<Uint8Array>).getReader();\n try {\n while (true) {\n const { done, value } = await bodyReader.read();\n if (done) break;\n downloadedBytes += value.length;\n if (downloadedBytes > MAX_SIZE) {\n controller.error(new Error('Download exceeds 200 MB limit'));\n return;\n }\n controller.enqueue(value);\n }\n controller.close();\n } catch (err) {\n controller.error(err);\n }\n },\n });\n await pipeline(transformedBody as unknown as NodeJS.ReadableStream, fileStream);\n\n // SEG-001: SHA-256 integrity check with proper error handling\n let expectedHash: string | null = null;\n try {\n const checksumRes = await fetchWithTimeout(`${DOWNLOAD_URL}.sha256`, {}, 15_000);\n if (checksumRes.ok) {\n const text = await checksumRes.text();\n // EP-009: Validate split result is defined\n const hash = text.trim().split(/\\s+/)[0];\n // EP-010: Validate hash format\n if (hash && /^[0-9a-f]{64}$/i.test(hash)) {\n expectedHash = hash.toLowerCase();\n }\n }\n } catch {\n /* checksum file not available */\n }\n\n if (expectedHash) {\n const fileBuffer = await readFile(TMP_ZIP);\n const actual = createHash('sha256').update(fileBuffer).digest('hex');\n if (actual !== expectedHash) {\n await rm(TMP_ZIP, { force: true }).catch(() => {});\n throw new Error(t('cli_brewbarDownloadFailed', { error: 'SHA-256 mismatch: binary may have been tampered with' }));\n }\n } else {\n // NUEVO-003: Treat missing checksum as fatal — don't install unverified binaries\n await rm(TMP_ZIP, { force: true }).catch(() => {});\n throw new Error(t('cli_brewbarDownloadFailed', { error: 'SHA-256 checksum unavailable — cannot verify download integrity' }));\n }\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) }), { cause: err });\n } finally {\n // Clean up tmp zip\n await rm(TMP_ZIP, { force: true }).catch(() => {});\n }\n}\n\n/// Launches BrewBar detached from the parent process so it survives terminal close.\n/// `open -g -a` runs the app in the background without bringing it to foreground.\nexport async function launchBrewBar(): Promise<void> {\n if (process.platform !== 'darwin') return;\n if (!await isBrewBarInstalled()) return;\n try {\n await execFileAsync('open', ['-g', '-a', BREWBAR_APP_PATH]);\n } catch {\n // Non-fatal: BrewBar may already be running, or LaunchServices may need a moment.\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":"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","names":[]}
|
|
@@ -5,9 +5,9 @@ import {
|
|
|
5
5
|
loadBrewfile,
|
|
6
6
|
reconcile,
|
|
7
7
|
saveBrewfile
|
|
8
|
-
} from "./chunk-
|
|
9
|
-
import "./chunk-
|
|
10
|
-
import "./chunk-
|
|
8
|
+
} from "./chunk-KSIAKLE2.js";
|
|
9
|
+
import "./chunk-PVSE6XO7.js";
|
|
10
|
+
import "./chunk-EHIBIFCB.js";
|
|
11
11
|
import "./chunk-KDHEUNRI.js";
|
|
12
12
|
export {
|
|
13
13
|
BREWFILE_PATH,
|
|
@@ -17,4 +17,4 @@ export {
|
|
|
17
17
|
reconcile,
|
|
18
18
|
saveBrewfile
|
|
19
19
|
};
|
|
20
|
-
//# sourceMappingURL=brewfile-manager-
|
|
20
|
+
//# sourceMappingURL=brewfile-manager-OITSKEHY.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
captureSnapshot
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-PVSE6XO7.js";
|
|
4
4
|
|
|
5
5
|
// src/lib/compliance/compliance-checker.ts
|
|
6
6
|
import { hostname } from "os";
|
|
@@ -120,4 +120,4 @@ export {
|
|
|
120
120
|
versionAtLeast,
|
|
121
121
|
checkCompliance
|
|
122
122
|
};
|
|
123
|
-
//# sourceMappingURL=chunk-
|
|
123
|
+
//# sourceMappingURL=chunk-5MYWF5D7.js.map
|
|
@@ -68,6 +68,8 @@ var en = {
|
|
|
68
68
|
hint_add: "add",
|
|
69
69
|
hint_reconcile: "reconcile",
|
|
70
70
|
hint_export: "export",
|
|
71
|
+
hint_apply: "apply",
|
|
72
|
+
hint_revalidate: "revalidate",
|
|
71
73
|
hint_select: "select",
|
|
72
74
|
hint_sync: "sync now",
|
|
73
75
|
hint_conflict: "resolve",
|
|
@@ -91,6 +93,10 @@ var en = {
|
|
|
91
93
|
// ── Confirm dialog ──
|
|
92
94
|
confirm_yes: "[Y]es",
|
|
93
95
|
confirm_no: "[N]o",
|
|
96
|
+
confirm_brewfile_reconcile: "Reconcile Brewfile? Will install {{missing}} missing and reinstall {{wrongVer}} mismatched packages.",
|
|
97
|
+
confirm_sync_now: "Sync now with iCloud? This pushes local state and pulls remote changes.",
|
|
98
|
+
confirm_sync_apply: "Apply {{count}} conflict resolution(s)? Selected versions will be installed.",
|
|
99
|
+
confirm_compliance_remediate: "Remediate {{count}} actionable violation(s)? Will install missing packages and align mismatched versions.",
|
|
94
100
|
// ── Error ──
|
|
95
101
|
error_prefix: "Error: ",
|
|
96
102
|
// ── Common ──
|
|
@@ -278,6 +284,7 @@ var en = {
|
|
|
278
284
|
account_licenseExpired: "Your license has expired. Renew to continue using Pro features.",
|
|
279
285
|
account_deactivating: "Deactivating...",
|
|
280
286
|
account_loading: "Loading license status...",
|
|
287
|
+
account_revalidating: "Revalidating with server...",
|
|
281
288
|
account_promoTitle: "Redeem Promo Code",
|
|
282
289
|
account_promoLabel: "Code:",
|
|
283
290
|
account_promoValidating: "Validating promo code...",
|
|
@@ -460,6 +467,8 @@ var en = {
|
|
|
460
467
|
brewfile_formulae_count: "{{count}} formulae",
|
|
461
468
|
brewfile_casks_count: "{{count}} casks",
|
|
462
469
|
brewfile_strict_mode: "Strict mode",
|
|
470
|
+
brewfile_in_sync: "\u2713 System is in sync with Brewfile",
|
|
471
|
+
brewfile_computing_drift: "Computing drift...",
|
|
463
472
|
// ── Compliance ──
|
|
464
473
|
view_compliance: "Compliance",
|
|
465
474
|
compliance_title: "Team Compliance",
|
|
@@ -483,7 +492,12 @@ var en = {
|
|
|
483
492
|
compliance_policy_by: "Maintained by: {{maintainer}}",
|
|
484
493
|
// ── Upgrade Prompt — Compliance ──
|
|
485
494
|
upgrade_compliance: "Team Compliance",
|
|
486
|
-
upgrade_complianceDesc: "Enforce package policies across your team. Define required, forbidden, and version-pinned packages and automatically remediate deviations."
|
|
495
|
+
upgrade_complianceDesc: "Enforce package policies across your team. Define required, forbidden, and version-pinned packages and automatically remediate deviations.",
|
|
496
|
+
// ── Upgrade Prompt — Rollback / Brewfile ──
|
|
497
|
+
upgrade_rollback: "Smart Rollback",
|
|
498
|
+
upgrade_rollbackDesc: "Restore your Homebrew state from any past snapshot. Brew-TUI captures a snapshot before every install and upgrade so you can roll back a bad update with one keystroke.",
|
|
499
|
+
upgrade_brewfile: "Declarative Brewfile",
|
|
500
|
+
upgrade_brewfileDesc: "Pin your toolchain in a versioned Brewfile and reconcile any machine to it. Drift score, missing/extra detection and one-keystroke alignment."
|
|
487
501
|
};
|
|
488
502
|
var en_default = en;
|
|
489
503
|
|
|
@@ -550,6 +564,8 @@ var es = {
|
|
|
550
564
|
hint_add: "a\xF1adir",
|
|
551
565
|
hint_reconcile: "reconciliar",
|
|
552
566
|
hint_export: "exportar",
|
|
567
|
+
hint_apply: "aplicar",
|
|
568
|
+
hint_revalidate: "revalidar",
|
|
553
569
|
hint_select: "seleccionar",
|
|
554
570
|
hint_sync: "sincronizar",
|
|
555
571
|
hint_conflict: "resolver",
|
|
@@ -573,6 +589,10 @@ var es = {
|
|
|
573
589
|
// ── Confirm dialog ──
|
|
574
590
|
confirm_yes: "[S]\xED",
|
|
575
591
|
confirm_no: "[N]o",
|
|
592
|
+
confirm_brewfile_reconcile: "\xBFReconciliar Brewfile? Instalar\xE1 {{missing}} paquetes faltantes y reinstalar\xE1 {{wrongVer}} con versi\xF3n incorrecta.",
|
|
593
|
+
confirm_sync_now: "\xBFSincronizar ahora con iCloud? Se subir\xE1 el estado local y se descargar\xE1n los cambios remotos.",
|
|
594
|
+
confirm_sync_apply: "\xBFAplicar {{count}} resoluci\xF3n(es) de conflicto? Se instalar\xE1n las versiones seleccionadas.",
|
|
595
|
+
confirm_compliance_remediate: "\xBFRemediar {{count}} violaci\xF3n(es) accionable(s)? Se instalar\xE1n los paquetes faltantes y se alinear\xE1n las versiones desviadas.",
|
|
576
596
|
// ── Error ──
|
|
577
597
|
error_prefix: "Error: ",
|
|
578
598
|
// ── Common ──
|
|
@@ -759,6 +779,7 @@ var es = {
|
|
|
759
779
|
account_licenseExpired: "Tu licencia ha expirado. Renueva para seguir usando las funciones Pro.",
|
|
760
780
|
account_deactivating: "Desactivando...",
|
|
761
781
|
account_loading: "Cargando estado de la licencia...",
|
|
782
|
+
account_revalidating: "Revalidando con el servidor...",
|
|
762
783
|
account_promoTitle: "Canjear C\xF3digo Promocional",
|
|
763
784
|
account_promoLabel: "C\xF3digo:",
|
|
764
785
|
account_promoValidating: "Validando c\xF3digo promocional...",
|
|
@@ -942,6 +963,8 @@ var es = {
|
|
|
942
963
|
brewfile_formulae_count: "{{count}} formulae",
|
|
943
964
|
brewfile_casks_count: "{{count}} casks",
|
|
944
965
|
brewfile_strict_mode: "Modo estricto",
|
|
966
|
+
brewfile_in_sync: "\u2713 El sistema coincide con el Brewfile",
|
|
967
|
+
brewfile_computing_drift: "Calculando deriva...",
|
|
945
968
|
// ── Compliance ──
|
|
946
969
|
view_compliance: "Compliance",
|
|
947
970
|
compliance_title: "Conformidad de Equipo",
|
|
@@ -965,7 +988,12 @@ var es = {
|
|
|
965
988
|
compliance_policy_by: "Mantenida por: {{maintainer}}",
|
|
966
989
|
// ── Upgrade Prompt — Compliance ──
|
|
967
990
|
upgrade_compliance: "Conformidad de Equipo",
|
|
968
|
-
upgrade_complianceDesc: "Aplica pol\xEDticas de paquetes en todo tu equipo. Define paquetes requeridos, prohibidos y con versi\xF3n fijada, y remedia desviaciones autom\xE1ticamente."
|
|
991
|
+
upgrade_complianceDesc: "Aplica pol\xEDticas de paquetes en todo tu equipo. Define paquetes requeridos, prohibidos y con versi\xF3n fijada, y remedia desviaciones autom\xE1ticamente.",
|
|
992
|
+
// ── Upgrade Prompt — Rollback / Brewfile ──
|
|
993
|
+
upgrade_rollback: "Rollback Inteligente",
|
|
994
|
+
upgrade_rollbackDesc: "Restaura tu estado de Homebrew desde cualquier snapshot pasado. Brew-TUI captura un snapshot antes de cada install/upgrade para que revertir una actualizaci\xF3n fallida sea una pulsaci\xF3n.",
|
|
995
|
+
upgrade_brewfile: "Brewfile Declarativo",
|
|
996
|
+
upgrade_brewfileDesc: "Fija tu toolchain en un Brewfile versionado y reconcilia cualquier m\xE1quina contra \xE9l. Score de deriva, detecci\xF3n de faltantes/sobrantes y alineaci\xF3n con una pulsaci\xF3n."
|
|
969
997
|
};
|
|
970
998
|
var es_default = es;
|
|
971
999
|
|
|
@@ -1053,4 +1081,4 @@ export {
|
|
|
1053
1081
|
fetchWithTimeout,
|
|
1054
1082
|
fetchWithRetry
|
|
1055
1083
|
};
|
|
1056
|
-
//# sourceMappingURL=chunk-
|
|
1084
|
+
//# sourceMappingURL=chunk-6NA4INJS.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/i18n/index.ts","../src/i18n/en.ts","../src/i18n/es.ts","../src/lib/fetch-timeout.ts"],"sourcesContent":["import { create } from 'zustand';\nimport en from './en.js';\nimport es from './es.js';\nimport type { Translations, TranslationKey } from './en.js';\n\nexport type Locale = 'en' | 'es';\n\nconst locales: Record<Locale, Translations> = { en, es };\n\nfunction isLocale(s: string): s is Locale {\n return s === 'en' || s === 'es';\n}\n\nfunction detectLocale(): Locale {\n const flag = process.argv.find((a) => a.startsWith('--lang='));\n if (flag) {\n const code = flag.split('=')[1];\n if (code && isLocale(code)) return code;\n }\n const env = process.env.LANG ?? process.env.LC_ALL ?? process.env.LC_MESSAGES ?? '';\n const prefix = env.split(/[_.]/)[0] ?? '';\n if (isLocale(prefix)) return prefix;\n return 'en';\n}\n\n// ── Locale store ──\ninterface LocaleState {\n locale: Locale;\n setLocale: (locale: Locale) => void;\n}\n\nexport const useLocaleStore = create<LocaleState>((set) => ({\n locale: detectLocale(),\n setLocale: (locale) => set({ locale }),\n}));\n\nexport function getLocale(): Locale {\n return useLocaleStore.getState().locale;\n}\n\n// ── Translation function ──\nexport function t(key: TranslationKey, values?: Record<string, string | number>): string {\n const locale = useLocaleStore.getState().locale;\n let text: string = locales[locale][key] ?? locales['en'][key] ?? key;\n if (values) {\n for (const [k, v] of Object.entries(values)) {\n text = text.replaceAll(`{{${k}}}`, String(v));\n }\n }\n return text;\n}\n\n// ── Plural helper ──\nexport function tp(baseKey: string, count: number, values?: Record<string, string | number>): string {\n const suffix = count === 1 ? '_one' : '_other';\n return t(`${baseKey}${suffix}` as TranslationKey, { count, ...values });\n}\n\nexport type { TranslationKey, Translations };\n","const en = {\n // ── App chrome ──\n app_title: 'Brew-TUI',\n pro_badge: 'PRO',\n app_version: 'Brew-TUI v{{version}}',\n\n // ── View labels (header tab bar) ──\n view_dashboard: 'Dashboard',\n view_installed: 'Installed',\n view_search: 'Search',\n view_outdated: 'Outdated',\n view_packageInfo: 'Pkg Info',\n view_services: 'Services',\n view_doctor: 'Doctor',\n view_profiles: 'Profiles',\n view_smartCleanup: 'Cleanup',\n view_history: 'History',\n view_rollback: 'Rollback',\n view_brewfile: 'Brewfile',\n view_sync: 'Sync',\n view_securityAudit: 'Security',\n view_account: 'Account',\n\n // ── Keyboard hint actions ──\n hint_navigate: 'navigate',\n hint_next: 'next',\n hint_quit: 'quit',\n hint_filter: 'filter',\n hint_info: 'info',\n hint_toggle: 'toggle',\n hint_typeToSearch: 'type to search',\n hint_install: 'install',\n hint_uninstall: 'uninstall',\n hint_upgrade: 'upgrade',\n hint_upgradeAll: 'upgrade all',\n hint_back: 'back',\n hint_start: 'start',\n hint_stop: 'stop',\n hint_restart: 'restart',\n hint_refresh: 'refresh',\n hint_new: 'new',\n hint_details: 'details',\n hint_import: 'import',\n hint_delete: 'delete',\n hint_clean: 'clean',\n hint_all: 'all',\n hint_search: 'search',\n hint_clear: 'clear',\n hint_scan: 'scan',\n hint_expand: 'expand',\n hint_cancel: 'cancel',\n hint_force: 'force uninstall (ignore deps)',\n hint_rescan: 'rescan',\n hint_deactivate: 'deactivate',\n hint_promo: 'promo code',\n hint_importProfile: 'import this profile',\n hint_lang: 'lang',\n hint_replay: 'replay',\n hint_edit: 'edit',\n hint_pin: 'pin/unpin',\n hint_rollback_confirm: 'rollback',\n hint_add: 'add',\n hint_reconcile: 'reconcile',\n hint_export: 'export',\n hint_apply: 'apply',\n hint_revalidate: 'revalidate',\n hint_select: 'select',\n hint_sync: 'sync now',\n hint_conflict: 'resolve',\n hint_rollback: 'rollback',\n hint_check_compliance: 'check compliance',\n hint_switchTab: 'switch tab',\n hint_useLocal: 'use local',\n\n // ── Loading / progress ──\n loading_default: 'Loading...',\n loading_fetchingBrew: 'Fetching Homebrew data...',\n loading_installed: 'Loading installed packages...',\n loading_outdated: 'Checking for outdated packages...',\n loading_services: 'Loading services...',\n loading_doctor: 'Running brew doctor... (this may take a moment)',\n loading_profiles: 'Loading profiles...',\n loading_cleanup: 'Analyzing packages... (checking disk usage)',\n loading_history: 'Loading history...',\n loading_security: 'Scanning packages against OSV vulnerability database...',\n loading_searching: 'Searching...',\n loading_package: 'Loading {{name}}...',\n\n // ── Confirm dialog ──\n confirm_yes: '[Y]es',\n confirm_no: '[N]o',\n confirm_brewfile_reconcile: 'Reconcile Brewfile? Will install {{missing}} missing and reinstall {{wrongVer}} mismatched packages.',\n confirm_sync_now: 'Sync now with iCloud? This pushes local state and pulls remote changes.',\n confirm_sync_apply: 'Apply {{count}} conflict resolution(s)? Selected versions will be installed.',\n confirm_compliance_remediate: 'Remediate {{count}} actionable violation(s)? Will install missing packages and align mismatched versions.',\n\n // ── Error ──\n error_prefix: 'Error: ',\n\n // ── Common ──\n common_andMore: '...and {{count}} more',\n common_exit: '(exit {{code}})',\n common_yes: 'yes',\n common_no: 'no',\n\n // ── Relative time ──\n time_justNow: 'just now',\n time_minutesAgo: '{{n}}m ago',\n time_hoursAgo: '{{n}}h ago',\n time_daysAgo: '{{n}}d ago',\n time_monthsAgo: '{{n}}mo ago',\n\n // ── Badges ──\n badge_outdated: 'outdated',\n badge_pinned: 'pinned',\n badge_kegOnly: 'keg-only',\n badge_dep: 'dep',\n badge_installed: 'installed',\n badge_deprecated: 'deprecated',\n badge_ok: 'ok',\n badge_fail: 'fail',\n badge_error: 'error',\n\n // ── Dashboard ──\n dashboard_pro_status: 'Pro Status',\n dashboard_security: 'Security',\n dashboard_cves: '{{count}} CVEs',\n dashboard_no_cves: 'No CVEs',\n dashboard_brewfile: 'Brewfile',\n dashboard_sync: 'Sync',\n dashboard_sync_never: 'Never synced',\n dashboard_sync_ago: '{{time}} ago',\n dashboard_compliance: 'Compliance',\n dashboard_compliance_violations: '{{count}} violations',\n dashboard_compliance_ok: 'OK',\n dashboard_overview: 'Overview',\n dashboard_formulae: 'Formulae',\n dashboard_casks: 'Casks',\n dashboard_outdated: 'Outdated',\n dashboard_services: 'Services',\n dashboard_systemInfo: 'System Info',\n dashboard_homebrew: 'Homebrew:',\n dashboard_prefix: 'Prefix:',\n dashboard_updated: 'Updated:',\n dashboard_outdatedPackages: 'Outdated Packages',\n dashboard_serviceErrors: 'Service Errors',\n dashboard_partialData: 'Some Homebrew sections failed to load:',\n dashboard_statError: 'ERR',\n\n // ── Installed ──\n installed_formulaeCount: 'Formulae ({{count}})',\n installed_casksCount: 'Casks ({{count}})',\n installed_filterDisplay: 'Filter: \"{{query}}\" ({{count}} matches)',\n installed_noPackages: 'No packages found',\n installed_confirmUninstall: 'Uninstall {{name}}?',\n\n // ── Search ──\n search_placeholder: 'Search Homebrew packages... (enter to search)',\n search_resultsFor: 'Results for',\n search_escToClear: '(esc to clear)',\n search_installing: 'Installing package...',\n search_installComplete: 'Installation complete!',\n search_confirmInstall: 'Install {{name}}?',\n search_formulaeHeader: '=== Formulae ({{count}})',\n search_casksHeader: '=== Casks ({{count}})',\n search_noResults: 'No results found',\n search_minChars: 'Type at least 2 characters to search.',\n\n // ── Outdated ──\n outdated_title: 'Outdated Packages ({{count}})',\n outdated_upgrading: 'Upgrading...',\n outdated_upgradeComplete: 'Upgrade complete!',\n outdated_pressRefresh: '(press r to refresh)',\n outdated_upToDate: 'Everything is up to date!',\n outdated_confirmAll: 'Upgrade all {{count}} packages?',\n outdated_confirmSingle: 'Upgrade {{name}}?',\n outdated_pinned: '[pinned]',\n\n // ── Package Info ──\n pkgInfo_noPackage: 'No package selected. Go to Installed and press Enter on a package.',\n pkgInfo_notFound: 'Package not found',\n pkgInfo_installing: 'Installing {{name}}...',\n pkgInfo_uninstalling: 'Uninstalling {{name}}...',\n pkgInfo_upgrading: 'Upgrading {{name}}...',\n pkgInfo_done: 'Done!',\n pkgInfo_confirmInstall: 'install {{name}}?',\n pkgInfo_confirmUninstall: 'uninstall {{name}}?',\n pkgInfo_confirmUpgrade: 'upgrade {{name}}?',\n pkgInfo_details: 'Details',\n pkgInfo_homepage: 'Homepage:',\n pkgInfo_license: 'License:',\n pkgInfo_tap: 'Tap:',\n pkgInfo_stable: 'Stable:',\n pkgInfo_installed: 'Installed:',\n pkgInfo_bottle: 'Bottle:',\n pkgInfo_onRequest: 'On request:',\n pkgInfo_noDependency: 'no (dependency)',\n pkgInfo_dependencies: 'Dependencies ({{count}})',\n pkgInfo_caveats: 'Caveats',\n\n // ── Services ──\n services_title: 'Homebrew Services',\n services_titleCount: 'Homebrew Services ({{count}})',\n services_noServices: 'No services found',\n services_name: 'Name',\n services_status: 'Status',\n services_user: 'User',\n services_processing: 'Processing...',\n services_confirmStop: 'Stop service {{name}}?',\n services_confirmRestart: 'Restart service {{name}}?',\n\n // ── Doctor ──\n doctor_title: 'Homebrew Doctor',\n doctor_clean: 'Your system is ready to brew.',\n doctor_warningsNotCaptured: 'Doctor finished with warnings but none were captured.',\n\n // ── Profiles ──\n profiles_title: 'Package Profiles ({{count}})',\n profiles_importTitle: 'Importing profile...',\n profiles_importComplete: 'Import complete. Press any key.',\n profiles_importPartial: 'Import finished with errors. Check the log above.',\n profiles_createName: 'Create Profile \\u2014 Name:',\n profiles_namePlaceholder: 'e.g. work, personal, project-x',\n profiles_createDesc: 'Create Profile \"{{name}}\" \\u2014 Description:',\n profiles_descPlaceholder: 'Brief description of this setup',\n profiles_created: 'Created: {{date}}',\n profiles_formulaeCount: 'Formulae ({{count}})',\n profiles_casksCount: 'Casks ({{count}})',\n profiles_confirmDelete: 'Delete profile \"{{name}}\"?',\n profiles_noProfiles: 'No profiles saved yet.',\n profiles_press: 'Press',\n profiles_exportHint: 'to export your current setup as a profile.',\n profiles_editName: 'Edit Profile \\u2014 Name:',\n profiles_editDesc: 'Edit Profile \"{{name}}\" \\u2014 Description:',\n\n // ── Smart Cleanup ──\n cleanup_title: 'Smart Cleanup',\n cleanup_cleaning: 'Cleaning up...',\n cleanup_complete: 'Cleanup complete! Press r to re-analyze.',\n cleanup_orphans: 'Orphans',\n cleanup_reclaimable: 'Reclaimable',\n cleanup_selected: 'Selected',\n cleanup_confirmUninstall: 'Uninstall {{count}} packages?',\n cleanup_confirmForce: 'Some packages have dependencies. Force uninstall {{count}} packages? (ignores dependencies)',\n cleanup_depError: 'Some packages could not be removed due to dependencies.',\n cleanup_systemClean: 'No orphaned packages found. Your system is clean!',\n\n // ── History ──\n history_title: 'Action History ({{count}})',\n history_filterLabel: 'filter: {{filter}}',\n history_searchPlaceholder: 'Search packages...',\n history_confirmClear: 'Clear all {{count}} history entries?',\n history_noEntries: 'No history entries',\n history_noEntriesFor: 'No history entries for \"{{filter}}\"',\n history_all: '(all)',\n history_actionInstall: 'install',\n history_actionUninstall: 'uninstall',\n history_actionUpgrade: 'upgrade',\n history_actionUpgradeAll: 'upgrade-all',\n history_confirmReplay: 'Re-run: {{action}} {{name}}?',\n history_replayAll: 'Re-run: upgrade all packages?',\n\n // ── Security Audit ──\n security_rollback_hint: 'R: open Rollback for version restoration',\n security_title: 'Security Audit',\n security_scanned: 'Scanned',\n security_vulnerable: 'Vulnerable',\n security_critical: 'Critical',\n security_high: 'High',\n security_medium: 'Medium',\n security_noVulns: 'No known vulnerabilities found in your installed packages!',\n security_fixedIn: 'Fixed in: {{version}}',\n security_confirmUpgrade: 'Upgrade {{name}} to fix vulnerabilities?',\n\n // ── Account ──\n account_title: 'Account & License',\n account_confirmDeactivate: 'Deactivate your Pro license on this machine?',\n account_statusLabel: 'Status:',\n account_pro: '[Pro]',\n account_free: '[Free]',\n account_expired: '[Expired]',\n account_validating: '[Validating...]',\n account_emailLabel: 'Email:',\n account_nameLabel: 'Name:',\n account_planLabel: 'Plan:',\n account_monthlyPrice: '9.95\\u20AC/month',\n account_yearlyPrice: '82\\u20AC/year',\n account_keyLabel: 'Key:',\n account_expiresLabel: 'Expires:',\n account_activatedLabel: 'Activated:',\n account_upgradeTitle: 'Upgrade to Brew-TUI Pro',\n account_unlockDesc: 'Unlock Profiles, Smart Cleanup, History, Security Audit, and BrewBar (macOS menu bar companion).',\n account_pricing: '9.95\\u20AC/month or 82\\u20AC/year (save 31%)',\n account_runActivate: 'Run:',\n account_activateCmd: 'brew-tui activate <key>',\n account_licenseExpired: 'Your license has expired. Renew to continue using Pro features.',\n account_deactivating: 'Deactivating...',\n account_loading: 'Loading license status...',\n account_revalidating: 'Revalidating with server...',\n account_promoTitle: 'Redeem Promo Code',\n account_promoLabel: 'Code:',\n account_promoValidating: 'Validating promo code...',\n account_promoSuccess: 'Promo code redeemed! Pro access until {{expires}}.',\n account_promoInvalid: 'Invalid or expired promo code.',\n account_promoError: 'Could not validate promo code. Check your connection.',\n account_promoEsc: 'esc: cancel',\n account_promoHint: 'p: redeem promo code',\n\n // ── Upgrade Prompt ──\n upgrade_proFeature: '{{title}} \\u2014 Pro Feature',\n upgrade_profiles: 'Package Profiles',\n upgrade_profilesDesc: 'Export and import your Homebrew setup across machines. Save named profiles for work, personal, or project-specific configurations.',\n upgrade_cleanup: 'Smart Cleanup',\n upgrade_cleanupDesc: 'Find orphaned packages, analyze disk usage per package, and reclaim disk space with one-click intelligent cleanup.',\n upgrade_history: 'Action History',\n upgrade_historyDesc: 'Track every install, uninstall, and upgrade with timestamps. Search and filter your package management history.',\n upgrade_security: 'Security Audit',\n upgrade_securityDesc: 'Scan installed packages against known vulnerabilities (CVEs). See severity levels, affected versions, and available fixes.',\n upgrade_pricing: '9.95\\u20AC/month or 82\\u20AC/year (save 31%)',\n upgrade_teamFeature: '{{title}} \\u2014 Team tier',\n upgrade_teamPricing: '8\\u20AC/seat/month or 81.60\\u20AC/seat/year (save 15%) \\u2014 from 3 seats',\n upgrade_buyAt: 'Buy at:',\n upgrade_buyUrl: 'https://buy.polar.sh/polar_cl_yQsiUeDelyyEQznbWffD1j77JAyP24ra7iEVQ22PA4h',\n upgrade_buyUrlTeam: 'https://buy.polar.sh/polar_cl_CO6xqSzKgFiQJwXnhZYGqisOP04Wspi0KKZSn38NjFZ?quantity=3',\n upgrade_activateWith: 'Then activate with:',\n upgrade_activateCmd: 'brew-tui activate <your-license-key>',\n upgrade_proLabel: 'Brew-TUI Pro \\u2014 9.95\\u20AC/month or 82\\u20AC/year \\u2014 Includes BrewBar for macOS',\n upgrade_teamLabel: 'Brew-TUI Team \\u2014 8\\u20AC/seat/month \\u2014 Includes everything in Pro plus Compliance',\n\n // ── Progress Log ──\n progress_noOutput: 'No output yet',\n\n // ── Search Input ──\n searchInput_placeholder: 'Type to filter...',\n\n // ── Profile Manager ──\n profileMgr_tapping: 'Tapping {{name}}...',\n profileMgr_installing: 'Installing {{name}}...',\n profileMgr_installingCask: 'Installing cask {{name}}...',\n profileMgr_importDone: 'Done! Installed {{count}} packages.',\n\n // ── CLI ──\n cli_usageActivate: 'Usage: brew-tui activate <license-key>',\n cli_activated: '\\u2714 Pro activated for {{email}}',\n cli_plan: ' Plan: {{plan}}',\n cli_expires: ' Expires: {{date}}',\n cli_activationFailed: '\\u2718 Activation failed: {{error}}',\n cli_noLicense: 'No active license found.',\n cli_deactivated: '\\u2714 License deactivated.',\n cli_planFree: 'Plan: Free',\n cli_planPro: 'Plan: Pro',\n cli_planExpired: 'Plan: Expired',\n cli_confirmDeactivate: 'Deactivate your Pro license on this machine? (y/N): ',\n cli_deactivateCancelled: 'Deactivation cancelled.',\n cli_upgradeHint: 'Run `brew-tui activate <key>` to upgrade to Pro.',\n cli_revalidateHint: 'Run `brew-tui revalidate` to refresh your current license.',\n cli_email: 'Email: {{email}}',\n cli_status: 'Status: {{status}}',\n cli_revalidated: '\\u2714 License revalidated.',\n cli_revalidateGrace: '\\u26A0 Could not reach the server. Your current license remains usable within the offline grace period.',\n cli_revalidateFailed: '\\u2718 License revalidation failed. Renew your subscription or activate a valid key.',\n cli_rateLimited: 'Too many activation attempts. Try again in {{minutes}} minutes.',\n cli_cooldown: 'Please wait before trying again.',\n cli_brewbarInstalling: 'Downloading BrewBar...',\n cli_brewbarInstalled: '\\u2714 BrewBar installed to /Applications/BrewBar.app',\n cli_brewbarAlreadyInstalled: 'BrewBar is already installed. Use --force to reinstall.',\n cli_brewbarUninstalled: '\\u2714 BrewBar removed from /Applications.',\n cli_brewbarNotInstalled: 'BrewBar is not installed.',\n cli_brewbarProRequired: '\\u2718 BrewBar requires a Pro license.\\n Run: brew-tui activate <key>',\n cli_brewbarRevalidateRequired: '\\u2718 BrewBar requires a valid Pro license.\\n Run: brew-tui revalidate',\n cli_brewbarMacOnly: '\\u2718 BrewBar is only available on macOS.',\n cli_brewbarDownloadFailed: '\\u2718 Failed to download BrewBar: {{error}}',\n cli_brewbarAutoFailed: '\\u26A0 BrewBar auto-launch failed: {{error}}',\n cli_deactivateRemoteFailed: '\\u26A0 Warning: Could not reach the server to deactivate remotely. The license was removed locally but may still count as active.',\n\n // ── License degradation (Layer 15) ──\n license_offlineWarning: 'Your license has not been validated for {{days}} days. Please connect to the internet.',\n\n // ── Plurals ──\n plural_vulns_one: '({{count}} vuln)',\n plural_vulns_other: '({{count}} vulns)',\n plural_warnings_one: '{{count}} warning',\n plural_warnings_other: '{{count}} warnings',\n\n // ── Scroll indicators ──\n scroll_moreAbove: '\\u2191 {{count}} more',\n scroll_moreBelow: '\\u2193 {{count}} more',\n\n // ── SCR-001: Cleanup warning ──\n cleanup_warning_system_tools: 'Warning: detected orphans may include dependencies of tools not managed by Homebrew. Review the list before proceeding.',\n\n // ── SCR-002: Installed column headers ──\n installed_col_package: 'Package',\n installed_col_version: 'Version',\n installed_col_status: 'Status',\n\n // ── SCR-003: Search failed ──\n search_failed: 'Search failed',\n\n // ── SCR-007: Deactivate failed ──\n deactivate_failed: 'Deactivation failed',\n\n // ── ACC-005: Version labels ──\n version_installed: 'installed:',\n version_available: 'available:',\n\n // ── SCR-006: Upgrade-all replay warning ──\n upgrade_all_warning: 'Note: this will upgrade all currently outdated packages, which may differ from the original set.',\n\n // ── SEG-007: Delete account ──\n delete_account_confirm: 'Delete all Brew-TUI data (~/.brew-tui)? This removes your license, profiles, and history. This cannot be undone.',\n delete_account_success: 'All Brew-TUI data has been removed.',\n\n // ── SCR-012: Upgrade All packages list ──\n outdated_upgradeAllList: 'Packages to upgrade: {{list}}',\n\n // ── SCR-005: Profile import summary ──\n profiles_importSummary: 'This profile contains {{formulae}} formulae and {{casks}} casks. Continue?',\n\n // ── SCR-017: Network error ��─\n security_networkError: 'Could not reach OSV.dev vulnerability database. Check your internet connection.',\n\n // ── ARQ-004: Dashboard last updated ──\n dashboard_lastUpdated: 'Last updated: {{time}}',\n\n // ── SCR-014: Services last error ──\n services_lastError: 'Last error: {{error}}',\n\n // ── SCR-010: Generic network error ──\n error_network: 'Network error: unable to reach the server.',\n\n // ── ARQ-005: Security cache ──\n security_cachedResults: 'Showing cached results ({{time}} ago). Press r to rescan.',\n\n // ── Impact Analysis ──\n impact_brewfile_hint: '\\u{1F4A1} Add to Brewfile to pin this version before upgrading',\n impact_analyzing: 'Analyzing upgrade impact...',\n impact_high: 'HIGH RISK',\n impact_medium: 'MEDIUM RISK',\n impact_low: 'LOW RISK',\n impact_affects: 'affects {{count}} installed packages',\n impact_usedBy: 'Used by: {{packages}}',\n impact_hint: 'Select package to see upgrade impact',\n impact_reason_critical_package: 'Critical system package',\n impact_reason_major_bump: 'Major version change',\n impact_reason_many_deps: '{{count}} packages depend on this',\n\n // ── Rollback ──\n rollback_title: 'Rollback \\u2014 Restore Previous State',\n rollback_no_snapshots: 'No snapshots available. Snapshots are captured automatically after each operation.',\n rollback_select_snapshot: 'Select a snapshot to restore',\n rollback_snapshot_label: '{{label}} \\u2014 {{date}}',\n rollback_snapshot_auto: 'Auto',\n rollback_diff_empty: 'No changes detected between this snapshot and current state',\n rollback_confirm: 'Roll back {{count}} package(s) to this state?',\n rollback_strategy_bottle: 'from bottle cache',\n rollback_strategy_versioned: 'from versioned formula',\n rollback_strategy_pin: 'pin only (version not restorable)',\n rollback_strategy_unavailable: 'cannot restore',\n rollback_executing: 'Rolling back...',\n rollback_success: 'Rollback completed',\n rollback_error: 'Rollback failed: {{error}}',\n rollback_item_downgrade: '{{name}}: {{from}} \\u2192 {{to}}',\n rollback_item_remove: 'Remove: {{name}}',\n rollback_item_install: 'Install: {{name}} {{version}}',\n rollback_warning_cask: 'Casks will be pinned only (version restoration not supported)',\n rollback_capturing: 'Capturing current snapshot...',\n\n // ── Sync ──\n sync_title: 'Cross-machine Sync',\n sync_disabled: 'Sync is not configured. Press s to set up iCloud sync.',\n sync_status_ok: 'In sync',\n sync_status_drift: '{{count}} change(s) from other machines',\n sync_status_conflict: '{{count}} conflict(s) need resolution',\n sync_last_sync: 'Last sync: {{date}}',\n sync_machine: 'This machine: {{name}}',\n sync_other_machines: 'Other machines: {{names}}',\n sync_syncing: 'Syncing...',\n sync_success: 'Sync complete',\n sync_error: 'Sync failed: {{error}}',\n sync_conflict_title: 'Conflict: {{package}}',\n sync_conflict_local: 'This machine: {{version}}',\n sync_conflict_remote: '{{machine}}: {{version}}',\n sync_conflict_use_local: 'Keep local',\n sync_conflict_use_remote: 'Use remote',\n sync_setup_icloud: 'Setting up iCloud sync...',\n sync_no_icloud: 'iCloud Drive not found. Ensure iCloud Drive is enabled in System Settings.',\n\n // ── Upgrade Prompt — Sync ──\n upgrade_sync: 'Cross-machine Sync',\n upgrade_syncDesc: 'Keep your Homebrew setup in sync across multiple Macs via iCloud Drive. Automatically merge installations and resolve conflicts.',\n\n // ── Brewfile ──\n brewfile_title: 'Declarative Brewfile',\n brewfile_compliant: 'compliant',\n brewfile_no_brewfile: 'No Brewfile found. Press n to create one from your current installation.',\n brewfile_create_name: 'Brewfile name (Enter to confirm):',\n brewfile_created: 'Brewfile created: {{name}}',\n brewfile_drift_missing: '{{count}} packages missing',\n brewfile_drift_extra: '{{count}} extra packages',\n brewfile_drift_wrong: '{{count}} wrong versions',\n brewfile_reconciling: 'Reconciling...',\n brewfile_reconcile_success: 'Reconciliation complete',\n brewfile_reconcile_error: 'Reconciliation failed: {{error}}',\n brewfile_exported: 'Exported to {{path}}',\n brewfile_formulae_count: '{{count}} formulae',\n brewfile_casks_count: '{{count}} casks',\n brewfile_strict_mode: 'Strict mode',\n brewfile_in_sync: '✓ System is in sync with Brewfile',\n brewfile_computing_drift: 'Computing drift...',\n\n // ── Compliance ──\n view_compliance: 'Compliance',\n compliance_title: 'Team Compliance',\n compliance_no_policy: 'No policy loaded. Press i to import a policy file.',\n compliance_score: '{{score}}% compliant',\n compliance_violations: '{{count}} violation(s)',\n compliance_ok: 'Fully compliant',\n compliance_import_prompt: 'Policy file path (Enter to confirm):',\n compliance_import_error: 'Failed to load policy: {{error}}',\n compliance_violation_missing: 'Missing: {{name}} (required)',\n compliance_violation_forbidden: 'Forbidden: {{name}} — {{reason}}',\n compliance_violation_version: 'Wrong version: {{name}} (required {{required}}, installed {{installed}})',\n compliance_violation_extra: 'Extra package: {{name}}',\n compliance_remediating: 'Remediating...',\n compliance_remediate_success: 'Remediation complete',\n compliance_remediate_error: 'Remediation failed: {{error}}',\n compliance_export_done: 'Report exported to {{path}}',\n compliance_press_r_hint: 'Press r to run compliance check.',\n compliance_machine: 'Machine: {{name}}',\n compliance_policy_name: 'Policy: {{name}}',\n compliance_policy_by: 'Maintained by: {{maintainer}}',\n\n // ── Upgrade Prompt — Compliance ──\n upgrade_compliance: 'Team Compliance',\n upgrade_complianceDesc: 'Enforce package policies across your team. Define required, forbidden, and version-pinned packages and automatically remediate deviations.',\n\n // ── Upgrade Prompt — Rollback / Brewfile ──\n upgrade_rollback: 'Smart Rollback',\n upgrade_rollbackDesc: 'Restore your Homebrew state from any past snapshot. Brew-TUI captures a snapshot before every install and upgrade so you can roll back a bad update with one keystroke.',\n upgrade_brewfile: 'Declarative Brewfile',\n upgrade_brewfileDesc: 'Pin your toolchain in a versioned Brewfile and reconcile any machine to it. Drift score, missing/extra detection and one-keystroke alignment.',\n};\n\nexport default en;\nexport type Translations = { [K in keyof typeof en]: string };\nexport type TranslationKey = keyof typeof en;\n","import type { Translations } from './en.js';\n\nconst es: Translations = {\n // ── App chrome ──\n app_title: 'Brew-TUI',\n pro_badge: 'PRO',\n app_version: 'Brew-TUI v{{version}}',\n\n // ── View labels (header tab bar) ──\n view_dashboard: 'Inicio',\n view_installed: 'Instalados',\n view_search: 'Buscar',\n view_outdated: 'Desactual.',\n view_packageInfo: 'Info Paq.',\n view_services: 'Servicios',\n view_doctor: 'Doctor',\n view_profiles: 'Perfiles',\n view_smartCleanup: 'Limpieza',\n view_history: 'Historial',\n view_rollback: 'Rollback',\n view_brewfile: 'Brewfile',\n view_sync: 'Sync',\n view_securityAudit: 'Seguridad',\n view_account: 'Cuenta',\n\n // ── Keyboard hint actions ──\n hint_navigate: 'navegar',\n hint_next: 'siguiente',\n hint_quit: 'salir',\n hint_filter: 'filtrar',\n hint_info: 'info',\n hint_toggle: 'cambiar',\n hint_typeToSearch: 'escribe para buscar',\n hint_install: 'instalar',\n hint_uninstall: 'desinstalar',\n hint_upgrade: 'actualizar',\n hint_upgradeAll: 'actualizar todo',\n hint_back: 'volver',\n hint_start: 'iniciar',\n hint_stop: 'detener',\n hint_restart: 'reiniciar',\n hint_refresh: 'refrescar',\n hint_new: 'nuevo',\n hint_details: 'detalles',\n hint_import: 'importar',\n hint_delete: 'eliminar',\n hint_clean: 'limpiar',\n hint_all: 'todo',\n hint_search: 'buscar',\n hint_clear: 'borrar',\n hint_scan: 'escanear',\n hint_expand: 'expandir',\n hint_cancel: 'cancelar',\n hint_force: 'forzar (ignorar deps)',\n hint_rescan: 're-escanear',\n hint_deactivate: 'desactivar',\n hint_promo: 'c\\u00F3digo promo',\n hint_importProfile: 'importar este perfil',\n hint_lang: 'idioma',\n hint_replay: 'repetir',\n hint_edit: 'editar',\n hint_pin: 'fijar/desfijar',\n hint_rollback_confirm: 'revertir',\n hint_add: 'a\\u00F1adir',\n hint_reconcile: 'reconciliar',\n hint_export: 'exportar',\n hint_apply: 'aplicar',\n hint_revalidate: 'revalidar',\n hint_select: 'seleccionar',\n hint_sync: 'sincronizar',\n hint_conflict: 'resolver',\n hint_rollback: 'revertir',\n hint_check_compliance: 'verificar conformidad',\n hint_switchTab: 'cambiar pestaña',\n hint_useLocal: 'usar local',\n\n // ── Loading / progress ──\n loading_default: 'Cargando...',\n loading_fetchingBrew: 'Obteniendo datos de Homebrew...',\n loading_installed: 'Cargando paquetes instalados...',\n loading_outdated: 'Buscando paquetes desactualizados...',\n loading_services: 'Cargando servicios...',\n loading_doctor: 'Ejecutando brew doctor... (puede tardar un momento)',\n loading_profiles: 'Cargando perfiles...',\n loading_cleanup: 'Analizando paquetes... (verificando uso de disco)',\n loading_history: 'Cargando historial...',\n loading_security: 'Escaneando paquetes en la base de datos de vulnerabilidades OSV...',\n loading_searching: 'Buscando...',\n loading_package: 'Cargando {{name}}...',\n\n // ── Confirm dialog ──\n confirm_yes: '[S]\\u00ED',\n confirm_no: '[N]o',\n confirm_brewfile_reconcile: '¿Reconciliar Brewfile? Instalará {{missing}} paquetes faltantes y reinstalará {{wrongVer}} con versión incorrecta.',\n confirm_sync_now: '¿Sincronizar ahora con iCloud? Se subirá el estado local y se descargarán los cambios remotos.',\n confirm_sync_apply: '¿Aplicar {{count}} resolución(es) de conflicto? Se instalarán las versiones seleccionadas.',\n confirm_compliance_remediate: '¿Remediar {{count}} violación(es) accionable(s)? Se instalarán los paquetes faltantes y se alinearán las versiones desviadas.',\n\n // ── Error ──\n error_prefix: 'Error: ',\n\n // ── Common ──\n common_andMore: '...y {{count}} m\\u00E1s',\n common_exit: '(salida {{code}})',\n common_yes: 's\\u00ED',\n common_no: 'no',\n\n // ── Relative time ──\n time_justNow: 'ahora',\n time_minutesAgo: 'hace {{n}}m',\n time_hoursAgo: 'hace {{n}}h',\n time_daysAgo: 'hace {{n}}d',\n time_monthsAgo: 'hace {{n}}me',\n\n // ── Badges ──\n badge_outdated: 'desactualizado',\n badge_pinned: 'fijado',\n badge_kegOnly: 'keg-only',\n badge_dep: 'dep',\n badge_installed: 'instalado',\n badge_deprecated: 'obsoleto',\n badge_ok: 'ok',\n badge_fail: 'fallo',\n badge_error: 'error',\n\n // ── Dashboard ──\n dashboard_pro_status: 'Estado Pro',\n dashboard_security: 'Seguridad',\n dashboard_cves: '{{count}} CVEs',\n dashboard_no_cves: 'Sin CVEs',\n dashboard_brewfile: 'Brewfile',\n dashboard_sync: 'Sync',\n dashboard_sync_never: 'Nunca sincronizado',\n dashboard_sync_ago: 'hace {{time}}',\n dashboard_compliance: 'Conformidad',\n dashboard_compliance_violations: '{{count}} violaciones',\n dashboard_compliance_ok: 'OK',\n dashboard_overview: 'Resumen',\n dashboard_formulae: 'Formulae',\n dashboard_casks: 'Casks',\n dashboard_outdated: 'Desactualizados',\n dashboard_services: 'Servicios',\n dashboard_systemInfo: 'Info del Sistema',\n dashboard_homebrew: 'Homebrew:',\n dashboard_prefix: 'Prefijo:',\n dashboard_updated: 'Actualizado:',\n dashboard_outdatedPackages: 'Paquetes Desactualizados',\n dashboard_serviceErrors: 'Errores de Servicios',\n dashboard_partialData: 'Algunas secciones de Homebrew no pudieron cargarse:',\n dashboard_statError: 'ERR',\n\n // ── Installed ──\n installed_formulaeCount: 'Formulae ({{count}})',\n installed_casksCount: 'Casks ({{count}})',\n installed_filterDisplay: 'Filtro: \"{{query}}\" ({{count}} coincidencias)',\n installed_noPackages: 'No se encontraron paquetes',\n installed_confirmUninstall: '\\u00BFDesinstalar {{name}}?',\n\n // ── Search ──\n search_placeholder: 'Buscar paquetes Homebrew... (enter para buscar)',\n search_resultsFor: 'Resultados para',\n search_escToClear: '(esc para limpiar)',\n search_installing: 'Instalando paquete...',\n search_installComplete: '\\u00A1Instalaci\\u00F3n completa!',\n search_confirmInstall: '\\u00BFInstalar {{name}}?',\n search_formulaeHeader: '=== Formulae ({{count}})',\n search_casksHeader: '=== Casks ({{count}})',\n search_noResults: 'Sin resultados',\n search_minChars: 'Escribe al menos 2 caracteres para buscar.',\n\n // ── Outdated ──\n outdated_title: 'Paquetes Desactualizados ({{count}})',\n outdated_upgrading: 'Actualizando...',\n outdated_upgradeComplete: '\\u00A1Actualizaci\\u00F3n completa!',\n outdated_pressRefresh: '(presiona r para refrescar)',\n outdated_upToDate: '\\u00A1Todo est\\u00E1 al d\\u00EDa!',\n outdated_confirmAll: '\\u00BFActualizar los {{count}} paquetes?',\n outdated_confirmSingle: '\\u00BFActualizar {{name}}?',\n outdated_pinned: '[fijado]',\n\n // ── Package Info ──\n pkgInfo_noPackage: 'Ning\\u00FAn paquete seleccionado. Ve a Instalados y presiona Enter en un paquete.',\n pkgInfo_notFound: 'Paquete no encontrado',\n pkgInfo_installing: 'Instalando {{name}}...',\n pkgInfo_uninstalling: 'Desinstalando {{name}}...',\n pkgInfo_upgrading: 'Actualizando {{name}}...',\n pkgInfo_done: '\\u00A1Listo!',\n pkgInfo_confirmInstall: '\\u00BFinstalar {{name}}?',\n pkgInfo_confirmUninstall: '\\u00BFdesinstalar {{name}}?',\n pkgInfo_confirmUpgrade: '\\u00BFactualizar {{name}}?',\n pkgInfo_details: 'Detalles',\n pkgInfo_homepage: 'Web:',\n pkgInfo_license: 'Licencia:',\n pkgInfo_tap: 'Tap:',\n pkgInfo_stable: 'Estable:',\n pkgInfo_installed: 'Instalado:',\n pkgInfo_bottle: 'Bottle:',\n pkgInfo_onRequest: 'Por solicitud:',\n pkgInfo_noDependency: 'no (dependencia)',\n pkgInfo_dependencies: 'Dependencias ({{count}})',\n pkgInfo_caveats: 'Advertencias',\n\n // ── Services ──\n services_title: 'Servicios Homebrew',\n services_titleCount: 'Servicios Homebrew ({{count}})',\n services_noServices: 'No se encontraron servicios',\n services_name: 'Nombre',\n services_status: 'Estado',\n services_user: 'Usuario',\n services_processing: 'Procesando...',\n services_confirmStop: '\\u00BFDetener servicio {{name}}?',\n services_confirmRestart: '\\u00BFReiniciar servicio {{name}}?',\n\n // ── Doctor ──\n doctor_title: 'Homebrew Doctor',\n doctor_clean: 'Tu sistema est\\u00E1 listo para brew.',\n doctor_warningsNotCaptured: 'Doctor termin\\u00F3 con advertencias pero no se capturaron.',\n\n // ── Profiles ──\n profiles_title: 'Perfiles de Paquetes ({{count}})',\n profiles_importTitle: 'Importando perfil...',\n profiles_importComplete: 'Importaci\\u00F3n completa. Presiona cualquier tecla.',\n profiles_importPartial: 'Importaci\\u00F3n finalizada con errores. Revisa el registro arriba.',\n profiles_createName: 'Crear Perfil \\u2014 Nombre:',\n profiles_namePlaceholder: 'ej. trabajo, personal, proyecto-x',\n profiles_createDesc: 'Crear Perfil \"{{name}}\" \\u2014 Descripci\\u00F3n:',\n profiles_descPlaceholder: 'Breve descripci\\u00F3n de esta configuraci\\u00F3n',\n profiles_created: 'Creado: {{date}}',\n profiles_formulaeCount: 'Formulae ({{count}})',\n profiles_casksCount: 'Casks ({{count}})',\n profiles_confirmDelete: '\\u00BFEliminar perfil \"{{name}}\"?',\n profiles_noProfiles: 'A\\u00FAn no hay perfiles guardados.',\n profiles_press: 'Presiona',\n profiles_exportHint: 'para exportar tu configuraci\\u00F3n actual como perfil.',\n profiles_editName: 'Editar Perfil \\u2014 Nombre:',\n profiles_editDesc: 'Editar Perfil \"{{name}}\" \\u2014 Descripci\\u00F3n:',\n\n // ── Smart Cleanup ──\n cleanup_title: 'Limpieza Inteligente',\n cleanup_cleaning: 'Limpiando...',\n cleanup_complete: '\\u00A1Limpieza completa! Presiona r para re-analizar.',\n cleanup_orphans: 'Hu\\u00E9rfanos',\n cleanup_reclaimable: 'Recuperable',\n cleanup_selected: 'Seleccionados',\n cleanup_confirmUninstall: '\\u00BFDesinstalar {{count}} paquetes?',\n cleanup_confirmForce: 'Algunos paquetes tienen dependencias. \\u00BFForzar desinstalaci\\u00F3n de {{count}} paquetes? (ignora dependencias)',\n cleanup_depError: 'Algunos paquetes no pudieron eliminarse por dependencias.',\n cleanup_systemClean: '\\u00A1No se encontraron paquetes hu\\u00E9rfanos. Tu sistema est\\u00E1 limpio!',\n\n // ── History ──\n history_title: 'Historial de Acciones ({{count}})',\n history_filterLabel: 'filtro: {{filter}}',\n history_searchPlaceholder: 'Buscar paquetes...',\n history_confirmClear: '\\u00BFBorrar las {{count}} entradas del historial?',\n history_noEntries: 'Sin entradas en el historial',\n history_noEntriesFor: 'Sin entradas en el historial para \"{{filter}}\"',\n history_all: '(todos)',\n history_actionInstall: 'instalar',\n history_actionUninstall: 'desinstalar',\n history_actionUpgrade: 'actualizar',\n history_actionUpgradeAll: 'actualizar-todo',\n history_confirmReplay: '\\u00BFRe-ejecutar: {{action}} {{name}}?',\n history_replayAll: '\\u00BFRe-ejecutar: actualizar todos los paquetes?',\n\n // ── Security Audit ──\n security_title: 'Auditor\\u00EDa de Seguridad',\n security_scanned: 'Escaneados',\n security_vulnerable: 'Vulnerables',\n security_critical: 'Cr\\u00EDticos',\n security_high: 'Altos',\n security_medium: 'Medios',\n security_noVulns: '\\u00A1No se encontraron vulnerabilidades conocidas en tus paquetes instalados!',\n security_fixedIn: 'Corregido en: {{version}}',\n security_confirmUpgrade: '\\u00BFActualizar {{name}} para corregir vulnerabilidades?',\n\n // ── Account ──\n account_title: 'Cuenta y Licencia',\n account_confirmDeactivate: '\\u00BFDesactivar tu licencia Pro en esta m\\u00E1quina?',\n account_statusLabel: 'Estado:',\n account_pro: '[Pro]',\n account_free: '[Gratis]',\n account_expired: '[Expirada]',\n account_validating: '[Validando...]',\n account_emailLabel: 'Email:',\n account_nameLabel: 'Nombre:',\n account_planLabel: 'Plan:',\n account_monthlyPrice: '9,95\\u20AC/mes',\n account_yearlyPrice: '82\\u20AC/a\\u00F1o',\n account_keyLabel: 'Clave:',\n account_expiresLabel: 'Expira:',\n account_activatedLabel: 'Activado:',\n account_upgradeTitle: 'Actualiza a Brew-TUI Pro',\n account_unlockDesc: 'Desbloquea Perfiles, Limpieza Inteligente, Historial, Auditor\\u00EDa de Seguridad y BrewBar (barra de men\\u00FA macOS).',\n account_pricing: '9,95\\u20AC/mes o 82\\u20AC/a\\u00F1o (ahorra 31%)',\n account_runActivate: 'Ejecuta:',\n account_activateCmd: 'brew-tui activate <clave>',\n account_licenseExpired: 'Tu licencia ha expirado. Renueva para seguir usando las funciones Pro.',\n account_deactivating: 'Desactivando...',\n account_loading: 'Cargando estado de la licencia...',\n account_revalidating: 'Revalidando con el servidor...',\n account_promoTitle: 'Canjear C\\u00F3digo Promocional',\n account_promoLabel: 'C\\u00F3digo:',\n account_promoValidating: 'Validando c\\u00F3digo promocional...',\n account_promoSuccess: '\\u00A1C\\u00F3digo canjeado! Acceso Pro hasta {{expires}}.',\n account_promoInvalid: 'C\\u00F3digo inv\\u00E1lido o expirado.',\n account_promoError: 'No se pudo validar el c\\u00F3digo. Comprueba tu conexi\\u00F3n.',\n account_promoEsc: 'esc: cancelar',\n account_promoHint: 'p: canjear c\\u00F3digo promocional',\n\n // ── Upgrade Prompt ──\n upgrade_proFeature: '{{title}} \\u2014 Funci\\u00F3n Pro',\n upgrade_profiles: 'Perfiles de Paquetes',\n upgrade_profilesDesc: 'Exporta e importa tu configuraci\\u00F3n de Homebrew entre m\\u00E1quinas. Guarda perfiles con nombre para trabajo, personal o proyectos espec\\u00EDficos.',\n upgrade_cleanup: 'Limpieza Inteligente',\n upgrade_cleanupDesc: 'Encuentra paquetes hu\\u00E9rfanos, analiza uso de disco por paquete y recupera espacio con limpieza inteligente de un clic.',\n upgrade_history: 'Historial de Acciones',\n upgrade_historyDesc: 'Rastrea cada instalaci\\u00F3n, desinstalaci\\u00F3n y actualizaci\\u00F3n con marcas de tiempo. Busca y filtra tu historial de gesti\\u00F3n de paquetes.',\n upgrade_security: 'Auditor\\u00EDa de Seguridad',\n upgrade_securityDesc: 'Escanea paquetes instalados contra vulnerabilidades conocidas (CVEs). Ve niveles de severidad, versiones afectadas y correcciones disponibles.',\n upgrade_pricing: '9,95\\u20AC/mes o 82\\u20AC/a\\u00F1o (ahorra 31%)',\n upgrade_teamFeature: '{{title}} \\u2014 Tier Team',\n upgrade_teamPricing: '8\\u20AC/seat/mes o 81,60\\u20AC/seat/a\\u00F1o (ahorra 15%) \\u2014 desde 3 seats',\n upgrade_buyAt: 'Compra en:',\n upgrade_buyUrl: 'https://buy.polar.sh/polar_cl_yQsiUeDelyyEQznbWffD1j77JAyP24ra7iEVQ22PA4h',\n upgrade_buyUrlTeam: 'https://buy.polar.sh/polar_cl_CO6xqSzKgFiQJwXnhZYGqisOP04Wspi0KKZSn38NjFZ?quantity=3',\n upgrade_activateWith: 'Luego activa con:',\n upgrade_activateCmd: 'brew-tui activate <tu-clave-de-licencia>',\n upgrade_proLabel: 'Brew-TUI Pro \\u2014 9,95\\u20AC/mes o 82\\u20AC/a\\u00F1o \\u2014 Incluye BrewBar para macOS',\n upgrade_teamLabel: 'Brew-TUI Team \\u2014 8\\u20AC/seat/mes \\u2014 Incluye todo Pro m\\u00E1s Compliance',\n\n // ── Progress Log ──\n progress_noOutput: 'Sin salida a\\u00FAn',\n\n // ── Search Input ──\n searchInput_placeholder: 'Escribe para filtrar...',\n\n // ── Profile Manager ──\n profileMgr_tapping: 'A\\u00F1adiendo tap {{name}}...',\n profileMgr_installing: 'Instalando {{name}}...',\n profileMgr_installingCask: 'Instalando cask {{name}}...',\n profileMgr_importDone: '\\u00A1Listo! {{count}} paquetes instalados.',\n\n // ── CLI ──\n cli_usageActivate: 'Uso: brew-tui activate <clave-de-licencia>',\n cli_activated: '\\u2714 Pro activado para {{email}}',\n cli_plan: ' Plan: {{plan}}',\n cli_expires: ' Expira: {{date}}',\n cli_activationFailed: '\\u2718 Activaci\\u00F3n fallida: {{error}}',\n cli_noLicense: 'No se encontr\\u00F3 licencia activa.',\n cli_deactivated: '\\u2714 Licencia desactivada.',\n cli_planFree: 'Plan: Gratis',\n cli_planPro: 'Plan: Pro',\n cli_planExpired: 'Plan: Expirada',\n cli_confirmDeactivate: '\\u00BFDesactivar tu licencia Pro en esta m\\u00E1quina? (s/N): ',\n cli_deactivateCancelled: 'Desactivaci\\u00F3n cancelada.',\n cli_upgradeHint: 'Ejecuta `brew-tui activate <clave>` para actualizar a Pro.',\n cli_revalidateHint: 'Ejecuta `brew-tui revalidate` para refrescar tu licencia actual.',\n cli_email: 'Email: {{email}}',\n cli_status: 'Estado: {{status}}',\n cli_revalidated: '\\u2714 Licencia revalidada.',\n cli_revalidateGrace: '\\u26A0 No se pudo contactar al servidor. Tu licencia actual sigue siendo usable dentro del periodo de gracia offline.',\n cli_revalidateFailed: '\\u2718 La revalidaci\\u00F3n de la licencia fall\\u00F3. Renueva tu suscripci\\u00F3n o activa una clave v\\u00E1lida.',\n cli_rateLimited: 'Demasiados intentos de activaci\\u00F3n. Int\\u00E9ntalo en {{minutes}} minutos.',\n cli_cooldown: 'Por favor espera antes de intentar de nuevo.',\n cli_brewbarInstalling: 'Descargando BrewBar...',\n cli_brewbarInstalled: '\\u2714 BrewBar instalado en /Applications/BrewBar.app',\n cli_brewbarAlreadyInstalled: 'BrewBar ya est\\u00E1 instalado. Usa --force para reinstalar.',\n cli_brewbarUninstalled: '\\u2714 BrewBar eliminado de /Applications.',\n cli_brewbarNotInstalled: 'BrewBar no est\\u00E1 instalado.',\n cli_brewbarProRequired: '\\u2718 BrewBar requiere una licencia Pro.\\n Ejecuta: brew-tui activate <clave>',\n cli_brewbarRevalidateRequired: '\\u2718 BrewBar requiere una licencia Pro v\\u00E1lida.\\n Ejecuta: brew-tui revalidate',\n cli_brewbarMacOnly: '\\u2718 BrewBar solo est\\u00E1 disponible en macOS.',\n cli_brewbarDownloadFailed: '\\u2718 Error al descargar BrewBar: {{error}}',\n cli_brewbarAutoFailed: '\\u26A0 No se pudo lanzar BrewBar automáticamente: {{error}}',\n cli_deactivateRemoteFailed: '\\u26A0 Advertencia: No se pudo contactar al servidor para desactivar remotamente. La licencia se elimin\\u00F3 localmente pero puede seguir contando como activa.',\n\n // ── License degradation (Layer 15) ──\n license_offlineWarning: 'Tu licencia no se ha validado en {{days}} d\\u00EDas. Por favor con\\u00E9ctate a internet.',\n\n // ── Plurals ──\n plural_vulns_one: '({{count}} vuln)',\n plural_vulns_other: '({{count}} vulns)',\n plural_warnings_one: '{{count}} advertencia',\n plural_warnings_other: '{{count}} advertencias',\n\n // ── Scroll indicators ──\n scroll_moreAbove: '\\u2191 {{count}} m\\u00E1s',\n scroll_moreBelow: '\\u2193 {{count}} m\\u00E1s',\n\n // ── SCR-001: Cleanup warning ──\n cleanup_warning_system_tools: 'Advertencia: los hu\\u00E9rfanos detectados pueden incluir dependencias de herramientas no gestionadas por Homebrew. Revisa la lista antes de continuar.',\n\n // ── SCR-002: Installed column headers ──\n installed_col_package: 'Paquete',\n installed_col_version: 'Versi\\u00F3n',\n installed_col_status: 'Estado',\n\n // ── SCR-003: Search failed ──\n search_failed: 'B\\u00FAsqueda fallida',\n\n // ── SCR-007: Deactivate failed ──\n deactivate_failed: 'Error al desactivar',\n\n // ── ACC-005: Version labels ──\n version_installed: 'instalado:',\n version_available: 'disponible:',\n\n // ── SCR-006: Upgrade-all replay warning ──\n upgrade_all_warning: 'Nota: esto actualizar\\u00E1 todos los paquetes desactualizados actualmente, que pueden diferir del conjunto original.',\n\n // ── SEG-007: Delete account ──\n delete_account_confirm: '\\u00BFEliminar todos los datos de Brew-TUI (~/.brew-tui)? Esto elimina tu licencia, perfiles e historial. Esta acci\\u00F3n no se puede deshacer.',\n delete_account_success: 'Todos los datos de Brew-TUI han sido eliminados.',\n\n // ── SCR-012: Upgrade All packages list ──\n outdated_upgradeAllList: 'Paquetes a actualizar: {{list}}',\n\n // ── SCR-005: Profile import summary ──\n profiles_importSummary: 'Este perfil contiene {{formulae}} formulae y {{casks}} casks. \\u00BFContinuar?',\n\n // ── SCR-017: Network error ──\n security_networkError: 'No se pudo conectar con la base de datos de vulnerabilidades OSV.dev. Verifica tu conexi\\u00F3n a internet.',\n\n // ── ARQ-004: Dashboard last updated ──\n dashboard_lastUpdated: '\\u00DAltima actualizaci\\u00F3n: {{time}}',\n\n // ── SCR-014: Services last error ──\n services_lastError: '\\u00DAltimo error: {{error}}',\n\n // ── SCR-010: Generic network error ──\n error_network: 'Error de red: no se puede conectar con el servidor.',\n\n // ── ARQ-005: Security cache ──\n security_rollback_hint: 'R: abrir Rollback para restaurar versiones',\n security_cachedResults: 'Mostrando resultados en cach\\u00E9 (hace {{time}}). Presiona r para re-escanear.',\n\n // ── Impact Analysis ──\n impact_brewfile_hint: '\\u{1F4A1} Añade al Brewfile para fijar esta versión antes de actualizar',\n impact_analyzing: 'Analizando impacto de actualizaci\\u00F3n...',\n impact_high: 'RIESGO ALTO',\n impact_medium: 'RIESGO MEDIO',\n impact_low: 'RIESGO BAJO',\n impact_affects: 'afecta {{count}} paquetes instalados',\n impact_usedBy: 'Usado por: {{packages}}',\n impact_hint: 'Selecciona un paquete para ver el impacto',\n impact_reason_critical_package: 'Paquete cr\\u00EDtico del sistema',\n impact_reason_major_bump: 'Cambio de versi\\u00F3n mayor',\n impact_reason_many_deps: '{{count}} paquetes dependen de este',\n\n // ── Rollback ──\n rollback_title: 'Rollback \\u2014 Restaurar Estado Anterior',\n rollback_no_snapshots: 'No hay snapshots disponibles. Los snapshots se capturan autom\\u00E1ticamente tras cada operaci\\u00F3n.',\n rollback_select_snapshot: 'Selecciona un snapshot para restaurar',\n rollback_snapshot_label: '{{label}} \\u2014 {{date}}',\n rollback_snapshot_auto: 'Auto',\n rollback_diff_empty: 'No se detectaron cambios entre este snapshot y el estado actual',\n rollback_confirm: '\\u00BFRevertir {{count}} paquete(s) a este estado?',\n rollback_strategy_bottle: 'desde cach\\u00E9 de bottle',\n rollback_strategy_versioned: 'desde formula versionada',\n rollback_strategy_pin: 'solo fijar (versi\\u00F3n no restaurable)',\n rollback_strategy_unavailable: 'no se puede restaurar',\n rollback_executing: 'Revirtiendo...',\n rollback_success: 'Rollback completado',\n rollback_error: 'Rollback fallido: {{error}}',\n rollback_item_downgrade: '{{name}}: {{from}} \\u2192 {{to}}',\n rollback_item_remove: 'Eliminar: {{name}}',\n rollback_item_install: 'Instalar: {{name}} {{version}}',\n rollback_warning_cask: 'Los Casks solo se fijar\\u00E1n (restauraci\\u00F3n de versi\\u00F3n no disponible)',\n rollback_capturing: 'Capturando snapshot actual...',\n\n // ── Sync ──\n sync_title: 'Sincronización entre máquinas',\n sync_disabled: 'Sync no está configurado. Pulsa s para configurar iCloud sync.',\n sync_status_ok: 'Sincronizado',\n sync_status_drift: '{{count}} cambio(s) desde otras máquinas',\n sync_status_conflict: '{{count}} conflicto(s) requieren resolución',\n sync_last_sync: 'Última sync: {{date}}',\n sync_machine: 'Esta máquina: {{name}}',\n sync_other_machines: 'Otras máquinas: {{names}}',\n sync_syncing: 'Sincronizando...',\n sync_success: 'Sincronización completa',\n sync_error: 'Sync fallida: {{error}}',\n sync_conflict_title: 'Conflicto: {{package}}',\n sync_conflict_local: 'Esta máquina: {{version}}',\n sync_conflict_remote: '{{machine}}: {{version}}',\n sync_conflict_use_local: 'Mantener local',\n sync_conflict_use_remote: 'Usar remoto',\n sync_setup_icloud: 'Configurando iCloud sync...',\n sync_no_icloud: 'iCloud Drive no encontrado. Asegúrate de que iCloud Drive está activado en Ajustes del Sistema.',\n\n // ── Upgrade Prompt — Sync ──\n upgrade_sync: 'Sincronización entre máquinas',\n upgrade_syncDesc: 'Mantén tu configuración de Homebrew sincronizada entre varios Macs mediante iCloud Drive. Fusiona instalaciones automáticamente y resuelve conflictos.',\n\n // ── Brewfile ──\n brewfile_title: 'Brewfile Declarativo',\n brewfile_compliant: 'en conformidad',\n brewfile_no_brewfile: 'No se encontró Brewfile. Pulsa n para crear uno desde tu instalación actual.',\n brewfile_create_name: 'Nombre del Brewfile (Enter para confirmar):',\n brewfile_created: 'Brewfile creado: {{name}}',\n brewfile_drift_missing: '{{count}} paquetes faltantes',\n brewfile_drift_extra: '{{count}} paquetes extra',\n brewfile_drift_wrong: '{{count}} versiones incorrectas',\n brewfile_reconciling: 'Reconciliando...',\n brewfile_reconcile_success: 'Reconciliación completa',\n brewfile_reconcile_error: 'Reconciliación fallida: {{error}}',\n brewfile_exported: 'Exportado a {{path}}',\n brewfile_formulae_count: '{{count}} formulae',\n brewfile_casks_count: '{{count}} casks',\n brewfile_strict_mode: 'Modo estricto',\n brewfile_in_sync: '✓ El sistema coincide con el Brewfile',\n brewfile_computing_drift: 'Calculando deriva...',\n\n // ── Compliance ──\n view_compliance: 'Compliance',\n compliance_title: 'Conformidad de Equipo',\n compliance_no_policy: 'No hay política cargada. Pulsa i para importar un archivo de política.',\n compliance_score: '{{score}}% en conformidad',\n compliance_violations: '{{count}} violación(es)',\n compliance_ok: 'Totalmente en conformidad',\n compliance_import_prompt: 'Ruta del archivo de política (Enter para confirmar):',\n compliance_import_error: 'Error al cargar la política: {{error}}',\n compliance_violation_missing: 'Faltante: {{name}} (requerido)',\n compliance_violation_forbidden: 'Prohibido: {{name}} — {{reason}}',\n compliance_violation_version: 'Versión incorrecta: {{name}} (requerida {{required}}, instalada {{installed}})',\n compliance_violation_extra: 'Paquete extra: {{name}}',\n compliance_remediating: 'Remediando...',\n compliance_remediate_success: 'Remediación completa',\n compliance_remediate_error: 'Remediación fallida: {{error}}',\n compliance_export_done: 'Informe exportado a {{path}}',\n compliance_press_r_hint: 'Pulsa r para ejecutar la comprobación de cumplimiento.',\n compliance_machine: 'Máquina: {{name}}',\n compliance_policy_name: 'Política: {{name}}',\n compliance_policy_by: 'Mantenida por: {{maintainer}}',\n\n // ── Upgrade Prompt — Compliance ──\n upgrade_compliance: 'Conformidad de Equipo',\n upgrade_complianceDesc: 'Aplica políticas de paquetes en todo tu equipo. Define paquetes requeridos, prohibidos y con versión fijada, y remedia desviaciones automáticamente.',\n\n // ── Upgrade Prompt — Rollback / Brewfile ──\n upgrade_rollback: 'Rollback Inteligente',\n upgrade_rollbackDesc: 'Restaura tu estado de Homebrew desde cualquier snapshot pasado. Brew-TUI captura un snapshot antes de cada install/upgrade para que revertir una actualización fallida sea una pulsación.',\n upgrade_brewfile: 'Brewfile Declarativo',\n upgrade_brewfileDesc: 'Fija tu toolchain en un Brewfile versionado y reconcilia cualquier máquina contra él. Score de deriva, detección de faltantes/sobrantes y alineación con una pulsación.',\n};\n\nexport default es;\n","import { logger } from '../utils/logger.js';\n\nexport function fetchWithTimeout(url: string, options: RequestInit = {}, timeoutMs = 15_000): Promise<Response> {\n return fetch(url, { ...options, signal: AbortSignal.timeout(timeoutMs) });\n}\n\n/**\n * Wrap an async function with debug-level latency logging.\n */\nexport function timed<T>(label: string, fn: () => Promise<T>): Promise<T> {\n const start = Date.now();\n return fn().finally(() => logger.debug(`${label} took ${Date.now() - start}ms`));\n}\n\ninterface RetryOptions {\n attempts?: number;\n baseDelayMs?: number;\n maxDelayMs?: number;\n retryOn?: (response: Response) => boolean;\n}\n\nconst DEFAULT_RETRY: Required<Omit<RetryOptions, 'retryOn'>> & Pick<RetryOptions, 'retryOn'> = {\n attempts: 3,\n baseDelayMs: 500,\n maxDelayMs: 4_000,\n retryOn: (res) => res.status >= 500 && res.status < 600,\n};\n\nfunction isTransientNetworkError(err: unknown): boolean {\n const msg = err instanceof Error ? err.message : String(err);\n return /fetch failed|ECONNREFUSED|ENOTFOUND|ETIMEDOUT|network|timeout|abort|EAI_AGAIN/i.test(msg);\n}\n\n/**\n * fetchWithTimeout + retry with exponential backoff. Retries only on transient\n * network errors and on responses matching `retryOn` (default: 5xx). 4xx\n * responses are returned to the caller without retry.\n */\nexport async function fetchWithRetry(\n url: string,\n options: RequestInit = {},\n timeoutMs = 15_000,\n retry: RetryOptions = {},\n): Promise<Response> {\n const cfg = { ...DEFAULT_RETRY, ...retry };\n let lastError: unknown;\n\n for (let attempt = 1; attempt <= cfg.attempts; attempt++) {\n try {\n const res = await fetchWithTimeout(url, options, timeoutMs);\n if (attempt < cfg.attempts && cfg.retryOn?.(res)) {\n const delay = Math.min(cfg.baseDelayMs * Math.pow(2, attempt - 1), cfg.maxDelayMs);\n logger.warn(`fetchWithRetry: ${url} returned ${res.status}, retry ${attempt}/${cfg.attempts - 1} in ${delay}ms`);\n await new Promise((r) => setTimeout(r, delay));\n continue;\n }\n return res;\n } catch (err) {\n lastError = err;\n if (attempt >= cfg.attempts || !isTransientNetworkError(err)) throw err;\n const delay = Math.min(cfg.baseDelayMs * Math.pow(2, attempt - 1), cfg.maxDelayMs);\n logger.warn(`fetchWithRetry: ${url} threw transient error, retry ${attempt}/${cfg.attempts - 1} in ${delay}ms`, { error: String(err) });\n await new Promise((r) => setTimeout(r, delay));\n }\n }\n\n throw lastError instanceof Error ? lastError : new Error(String(lastError));\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// src/lib/data-dir.ts
|
|
2
|
+
import { homedir } from "os";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
5
|
+
import { randomUUID } from "crypto";
|
|
6
|
+
var DATA_DIR = join(homedir(), ".brew-tui");
|
|
7
|
+
var PROFILES_DIR = join(DATA_DIR, "profiles");
|
|
8
|
+
var LICENSE_PATH = join(DATA_DIR, "license.json");
|
|
9
|
+
var HISTORY_PATH = join(DATA_DIR, "history.json");
|
|
10
|
+
var SNAPSHOTS_DIR = join(DATA_DIR, "snapshots");
|
|
11
|
+
var CVE_CACHE_PATH = join(DATA_DIR, "cve-cache.json");
|
|
12
|
+
var MACHINE_ID_PATH = join(DATA_DIR, "machine-id");
|
|
13
|
+
async function ensureDataDirs() {
|
|
14
|
+
await mkdir(DATA_DIR, { recursive: true, mode: 448 });
|
|
15
|
+
await mkdir(PROFILES_DIR, { recursive: true, mode: 448 });
|
|
16
|
+
await mkdir(SNAPSHOTS_DIR, { recursive: true, mode: 448 });
|
|
17
|
+
}
|
|
18
|
+
var cachedMachineId = null;
|
|
19
|
+
var pendingResolution = null;
|
|
20
|
+
async function getMachineId() {
|
|
21
|
+
if (cachedMachineId) return cachedMachineId;
|
|
22
|
+
if (pendingResolution) return pendingResolution;
|
|
23
|
+
pendingResolution = (async () => {
|
|
24
|
+
try {
|
|
25
|
+
const id2 = (await readFile(MACHINE_ID_PATH, "utf-8")).trim();
|
|
26
|
+
if (id2) {
|
|
27
|
+
cachedMachineId = id2;
|
|
28
|
+
return id2;
|
|
29
|
+
}
|
|
30
|
+
} catch {
|
|
31
|
+
}
|
|
32
|
+
const id = randomUUID();
|
|
33
|
+
await mkdir(DATA_DIR, { recursive: true, mode: 448 });
|
|
34
|
+
await writeFile(MACHINE_ID_PATH, id, { encoding: "utf-8", mode: 384 });
|
|
35
|
+
cachedMachineId = id;
|
|
36
|
+
return id;
|
|
37
|
+
})();
|
|
38
|
+
try {
|
|
39
|
+
return await pendingResolution;
|
|
40
|
+
} finally {
|
|
41
|
+
pendingResolution = null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export {
|
|
46
|
+
DATA_DIR,
|
|
47
|
+
PROFILES_DIR,
|
|
48
|
+
LICENSE_PATH,
|
|
49
|
+
HISTORY_PATH,
|
|
50
|
+
SNAPSHOTS_DIR,
|
|
51
|
+
ensureDataDirs,
|
|
52
|
+
getMachineId
|
|
53
|
+
};
|
|
54
|
+
//# sourceMappingURL=chunk-EHIBIFCB.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/data-dir.ts"],"sourcesContent":["import { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { randomUUID } from 'node:crypto';\n\nexport const DATA_DIR = join(homedir(), '.brew-tui');\nexport const PROFILES_DIR = join(DATA_DIR, 'profiles');\nexport const LICENSE_PATH = join(DATA_DIR, 'license.json');\nexport const HISTORY_PATH = join(DATA_DIR, 'history.json');\nexport const SNAPSHOTS_DIR = join(DATA_DIR, 'snapshots');\nexport const CVE_CACHE_PATH = join(DATA_DIR, 'cve-cache.json');\nexport const MACHINE_ID_PATH = join(DATA_DIR, 'machine-id');\n\nexport async function ensureDataDirs(): Promise<void> {\n await mkdir(DATA_DIR, { recursive: true, mode: 0o700 });\n await mkdir(PROFILES_DIR, { recursive: true, mode: 0o700 });\n await mkdir(SNAPSHOTS_DIR, { recursive: true, mode: 0o700 });\n}\n\n// In-memory cache + serializer to prevent multiple concurrent first-time\n// creations from racing and producing different UUIDs on first launch.\nlet cachedMachineId: string | null = null;\nlet pendingResolution: Promise<string> | null = null;\n\nexport async function getMachineId(): Promise<string> {\n if (cachedMachineId) return cachedMachineId;\n if (pendingResolution) return pendingResolution;\n\n pendingResolution = (async () => {\n try {\n const id = (await readFile(MACHINE_ID_PATH, 'utf-8')).trim();\n if (id) {\n cachedMachineId = id;\n return id;\n }\n } catch { /* file does not exist yet */ }\n\n const id = randomUUID();\n await mkdir(DATA_DIR, { recursive: true, mode: 0o700 });\n await writeFile(MACHINE_ID_PATH, id, { encoding: 'utf-8', mode: 0o600 });\n cachedMachineId = id;\n return id;\n })();\n\n try {\n return await pendingResolution;\n } finally {\n pendingResolution = null;\n }\n}\n"],"mappings":";AAAA,SAAS,eAAe;AACxB,SAAS,YAAY;AACrB,SAAS,OAAO,UAAU,iBAAiB;AAC3C,SAAS,kBAAkB;AAEpB,IAAM,WAAW,KAAK,QAAQ,GAAG,WAAW;AAC5C,IAAM,eAAe,KAAK,UAAU,UAAU;AAC9C,IAAM,eAAe,KAAK,UAAU,cAAc;AAClD,IAAM,eAAe,KAAK,UAAU,cAAc;AAClD,IAAM,gBAAgB,KAAK,UAAU,WAAW;AAChD,IAAM,iBAAiB,KAAK,UAAU,gBAAgB;AACtD,IAAM,kBAAkB,KAAK,UAAU,YAAY;AAE1D,eAAsB,iBAAgC;AACpD,QAAM,MAAM,UAAU,EAAE,WAAW,MAAM,MAAM,IAAM,CAAC;AACtD,QAAM,MAAM,cAAc,EAAE,WAAW,MAAM,MAAM,IAAM,CAAC;AAC1D,QAAM,MAAM,eAAe,EAAE,WAAW,MAAM,MAAM,IAAM,CAAC;AAC7D;AAIA,IAAI,kBAAiC;AACrC,IAAI,oBAA4C;AAEhD,eAAsB,eAAgC;AACpD,MAAI,gBAAiB,QAAO;AAC5B,MAAI,kBAAmB,QAAO;AAE9B,uBAAqB,YAAY;AAC/B,QAAI;AACF,YAAMA,OAAM,MAAM,SAAS,iBAAiB,OAAO,GAAG,KAAK;AAC3D,UAAIA,KAAI;AACN,0BAAkBA;AAClB,eAAOA;AAAA,MACT;AAAA,IACF,QAAQ;AAAA,IAAgC;AAExC,UAAM,KAAK,WAAW;AACtB,UAAM,MAAM,UAAU,EAAE,WAAW,MAAM,MAAM,IAAM,CAAC;AACtD,UAAM,UAAU,iBAAiB,IAAI,EAAE,UAAU,SAAS,MAAM,IAAM,CAAC;AACvE,sBAAkB;AAClB,WAAO;AAAA,EACT,GAAG;AAEH,MAAI;AACF,WAAO,MAAM;AAAA,EACf,UAAE;AACA,wBAAoB;AAAA,EACtB;AACF;","names":["id"]}
|
|
@@ -2,11 +2,11 @@ import {
|
|
|
2
2
|
captureSnapshot,
|
|
3
3
|
saveSnapshot,
|
|
4
4
|
streamBrew
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-PVSE6XO7.js";
|
|
6
6
|
import {
|
|
7
7
|
DATA_DIR,
|
|
8
8
|
ensureDataDirs
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-EHIBIFCB.js";
|
|
10
10
|
import {
|
|
11
11
|
logger
|
|
12
12
|
} from "./chunk-KDHEUNRI.js";
|
|
@@ -464,4 +464,4 @@ export {
|
|
|
464
464
|
computeDrift,
|
|
465
465
|
reconcile
|
|
466
466
|
};
|
|
467
|
-
//# sourceMappingURL=chunk-
|
|
467
|
+
//# sourceMappingURL=chunk-KSIAKLE2.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
SNAPSHOTS_DIR,
|
|
3
3
|
ensureDataDirs
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-EHIBIFCB.js";
|
|
5
5
|
import {
|
|
6
6
|
logger
|
|
7
7
|
} from "./chunk-KDHEUNRI.js";
|
|
@@ -151,6 +151,7 @@ function sanitizeLabel(label) {
|
|
|
151
151
|
function timestampToFilename(iso) {
|
|
152
152
|
return iso.replace(/[:.]/g, "-");
|
|
153
153
|
}
|
|
154
|
+
var SNAPSHOT_RETENTION_LIMIT = 20;
|
|
154
155
|
async function saveSnapshot(s, label) {
|
|
155
156
|
await ensureDataDirs();
|
|
156
157
|
const effectiveLabel = label ? sanitizeLabel(label) : "auto";
|
|
@@ -160,6 +161,43 @@ async function saveSnapshot(s, label) {
|
|
|
160
161
|
const payload = label ? { ...s, label } : s;
|
|
161
162
|
await writeFile(tmpPath, JSON.stringify(payload, null, 2), { encoding: "utf-8", mode: 384 });
|
|
162
163
|
await rename(tmpPath, filePath);
|
|
164
|
+
await pruneSnapshots();
|
|
165
|
+
}
|
|
166
|
+
async function pruneSnapshots(maxCount = SNAPSHOT_RETENTION_LIMIT) {
|
|
167
|
+
let entries;
|
|
168
|
+
try {
|
|
169
|
+
entries = await readdir(SNAPSHOTS_DIR);
|
|
170
|
+
} catch {
|
|
171
|
+
return 0;
|
|
172
|
+
}
|
|
173
|
+
const candidates = [];
|
|
174
|
+
for (const filename of entries.filter((f) => f.endsWith(".json"))) {
|
|
175
|
+
if (!filename.endsWith("-auto.json")) continue;
|
|
176
|
+
try {
|
|
177
|
+
const raw = await readFile(join(SNAPSHOTS_DIR, filename), "utf-8");
|
|
178
|
+
const parsed = JSON.parse(raw);
|
|
179
|
+
if (isValidSnapshot(parsed)) {
|
|
180
|
+
candidates.push({ filename, capturedAt: parsed.capturedAt });
|
|
181
|
+
}
|
|
182
|
+
} catch {
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (candidates.length <= maxCount) return 0;
|
|
186
|
+
candidates.sort((a, b) => b.capturedAt.localeCompare(a.capturedAt));
|
|
187
|
+
const toDelete = candidates.slice(maxCount);
|
|
188
|
+
let removed = 0;
|
|
189
|
+
for (const entry of toDelete) {
|
|
190
|
+
try {
|
|
191
|
+
await unlink(join(SNAPSHOTS_DIR, entry.filename));
|
|
192
|
+
removed++;
|
|
193
|
+
} catch (err) {
|
|
194
|
+
logger.warn("Failed to prune snapshot", { filename: entry.filename, error: String(err) });
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (removed > 0) {
|
|
198
|
+
logger.info("Pruned snapshots", { removed, retained: maxCount });
|
|
199
|
+
}
|
|
200
|
+
return removed;
|
|
163
201
|
}
|
|
164
202
|
async function loadSnapshots() {
|
|
165
203
|
let entries;
|
|
@@ -213,9 +251,11 @@ export {
|
|
|
213
251
|
execBrew,
|
|
214
252
|
streamBrew,
|
|
215
253
|
captureSnapshot,
|
|
254
|
+
SNAPSHOT_RETENTION_LIMIT,
|
|
216
255
|
saveSnapshot,
|
|
256
|
+
pruneSnapshots,
|
|
217
257
|
loadSnapshots,
|
|
218
258
|
deleteSnapshot,
|
|
219
259
|
getLatestSnapshot
|
|
220
260
|
};
|
|
221
|
-
//# sourceMappingURL=chunk-
|
|
261
|
+
//# sourceMappingURL=chunk-PVSE6XO7.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/state-snapshot/snapshot.ts","../src/lib/brew-cli.ts"],"sourcesContent":["import { readFile, writeFile, rename, readdir, unlink } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { execBrew } from '../brew-cli.js';\nimport { SNAPSHOTS_DIR, ensureDataDirs } from '../data-dir.js';\nimport { logger } from '../../utils/logger.js';\n\nexport interface BrewSnapshot {\n capturedAt: string;\n label?: string;\n formulae: Array<{ name: string; version: string; pinned: boolean }>;\n casks: Array<{ name: string; version: string }>;\n taps: string[];\n}\n\nfunction isValidSnapshot(v: unknown): v is BrewSnapshot {\n if (!v || typeof v !== 'object') return false;\n const s = v as Record<string, unknown>;\n return (\n typeof s['capturedAt'] === 'string' &&\n Array.isArray(s['formulae']) &&\n Array.isArray(s['casks']) &&\n Array.isArray(s['taps'])\n );\n}\n\n/** Parse `brew list --versions --formula` or `brew list --cask --versions`.\n * Each line: `name version1 version2...` — last version is the current install. */\nfunction parseVersionsList(output: string): Array<{ name: string; version: string }> {\n return output\n .split('\\n')\n .map((line) => line.trim())\n .filter(Boolean)\n .map((line) => {\n const parts = line.split(/\\s+/);\n const name = parts[0] ?? '';\n const version = parts[parts.length - 1] ?? '';\n return { name, version };\n })\n .filter((e) => e.name !== '');\n}\n\n/** Parse `brew tap` — one tap per line. */\nfunction parseTapsList(output: string): string[] {\n return output\n .split('\\n')\n .map((line) => line.trim())\n .filter(Boolean);\n}\n\n/** Parse `brew list --pinned` — one formula per line. */\nfunction parsePinnedList(output: string): Set<string> {\n return new Set(\n output\n .split('\\n')\n .map((line) => line.trim())\n .filter(Boolean),\n );\n}\n\nexport async function captureSnapshot(): Promise<BrewSnapshot> {\n const [formulaeRaw, casksRaw, tapsRaw, pinnedRaw] = await Promise.all([\n execBrew(['list', '--versions', '--formula']),\n execBrew(['list', '--cask', '--versions']),\n execBrew(['tap']),\n execBrew(['list', '--pinned']),\n ]);\n\n const pinned = parsePinnedList(pinnedRaw);\n const formulae = parseVersionsList(formulaeRaw).map((f) => ({\n ...f,\n pinned: pinned.has(f.name),\n }));\n const casks = parseVersionsList(casksRaw);\n const taps = parseTapsList(tapsRaw);\n\n return {\n capturedAt: new Date().toISOString(),\n formulae,\n casks,\n taps,\n };\n}\n\n/** Sanitize a label for safe use in a filename. */\nfunction sanitizeLabel(label: string): string {\n const clean = label.replace(/[^A-Za-z0-9_-]/g, '_');\n return clean.length > 0 ? clean : 'auto';\n}\n\n/** Convert an ISO 8601 timestamp to a filename-safe string by replacing `:` and `.` with `-`. */\nfunction timestampToFilename(iso: string): string {\n return iso.replace(/[:.]/g, '-');\n}\n\n/** Cap on retained snapshots. Older auto snapshots are pruned to keep\n * the rollback feature from filling ~/.brew-tui/snapshots/ indefinitely.\n * 20 entries fits roughly 2-3 weeks of typical usage. */\nexport const SNAPSHOT_RETENTION_LIMIT = 20;\n\nexport async function saveSnapshot(s: BrewSnapshot, label?: string): Promise<void> {\n await ensureDataDirs();\n\n const effectiveLabel = label ? sanitizeLabel(label) : 'auto';\n const filename = `${timestampToFilename(s.capturedAt)}-${effectiveLabel}.json`;\n const filePath = join(SNAPSHOTS_DIR, filename);\n const tmpPath = filePath + '.tmp';\n\n const payload: BrewSnapshot = label ? { ...s, label } : s;\n\n await writeFile(tmpPath, JSON.stringify(payload, null, 2), { encoding: 'utf-8', mode: 0o600 });\n await rename(tmpPath, filePath);\n\n await pruneSnapshots();\n}\n\n/** Delete the oldest auto-labelled snapshots beyond SNAPSHOT_RETENTION_LIMIT.\n * Labelled snapshots (those the user explicitly named) are preserved — they\n * represent intentional checkpoints. */\nexport async function pruneSnapshots(maxCount = SNAPSHOT_RETENTION_LIMIT): Promise<number> {\n let entries: string[];\n try {\n entries = await readdir(SNAPSHOTS_DIR);\n } catch {\n return 0;\n }\n\n const candidates: Array<{ filename: string; capturedAt: string }> = [];\n for (const filename of entries.filter((f) => f.endsWith('.json'))) {\n // Auto snapshots are named ...-auto.json. User-labelled ones get a slug\n // and stay regardless of count.\n if (!filename.endsWith('-auto.json')) continue;\n try {\n const raw = await readFile(join(SNAPSHOTS_DIR, filename), 'utf-8');\n const parsed: unknown = JSON.parse(raw);\n if (isValidSnapshot(parsed)) {\n candidates.push({ filename, capturedAt: parsed.capturedAt });\n }\n } catch { /* skip unreadable */ }\n }\n\n if (candidates.length <= maxCount) return 0;\n\n candidates.sort((a, b) => b.capturedAt.localeCompare(a.capturedAt));\n const toDelete = candidates.slice(maxCount);\n let removed = 0;\n for (const entry of toDelete) {\n try {\n await unlink(join(SNAPSHOTS_DIR, entry.filename));\n removed++;\n } catch (err) {\n logger.warn('Failed to prune snapshot', { filename: entry.filename, error: String(err) });\n }\n }\n if (removed > 0) {\n logger.info('Pruned snapshots', { removed, retained: maxCount });\n }\n return removed;\n}\n\nexport async function loadSnapshots(): Promise<BrewSnapshot[]> {\n let entries: string[];\n try {\n entries = await readdir(SNAPSHOTS_DIR);\n } catch {\n return [];\n }\n\n const jsonFiles = entries.filter((f) => f.endsWith('.json'));\n const snapshots: BrewSnapshot[] = [];\n\n for (const filename of jsonFiles) {\n try {\n const raw = await readFile(join(SNAPSHOTS_DIR, filename), 'utf-8');\n const parsed: unknown = JSON.parse(raw);\n if (!isValidSnapshot(parsed)) {\n logger.warn('Skipping corrupt snapshot file', { filename });\n continue;\n }\n snapshots.push(parsed);\n } catch {\n logger.warn('Failed to read snapshot file', { filename });\n }\n }\n\n return snapshots.sort((a, b) => b.capturedAt.localeCompare(a.capturedAt));\n}\n\nexport async function deleteSnapshot(capturedAt: string): Promise<void> {\n let entries: string[];\n try {\n entries = await readdir(SNAPSHOTS_DIR);\n } catch {\n return;\n }\n\n for (const filename of entries.filter((f) => f.endsWith('.json'))) {\n try {\n const raw = await readFile(join(SNAPSHOTS_DIR, filename), 'utf-8');\n const parsed: unknown = JSON.parse(raw);\n if (isValidSnapshot(parsed) && parsed.capturedAt === capturedAt) {\n await unlink(join(SNAPSHOTS_DIR, filename));\n return;\n }\n } catch {\n // Skip unreadable files\n }\n }\n}\n\nexport async function getLatestSnapshot(): Promise<BrewSnapshot | null> {\n const all = await loadSnapshots();\n return all[0] ?? null;\n}\n","import { spawn } from 'node:child_process';\n\nconst DEFAULT_TIMEOUT_MS = 30_000; // 30 seconds for instant commands\nconst STREAM_IDLE_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes idle timeout for streaming\n\nexport async function execBrew(args: string[], timeoutMs = DEFAULT_TIMEOUT_MS): Promise<string> {\n return new Promise((resolve, reject) => {\n const proc = spawn('brew', args, { env: { ...process.env, HOMEBREW_NO_AUTO_UPDATE: '1' } });\n let stdout = '';\n let stderr = '';\n let killed = false;\n\n // EP-012: Timeout with AbortController pattern\n const timer = setTimeout(() => {\n killed = true;\n proc.kill();\n reject(new Error(`brew ${args.join(' ')} timed out after ${timeoutMs}ms`));\n }, timeoutMs);\n\n proc.stdout.on('data', (d: Buffer) => { stdout += d.toString(); });\n proc.stderr.on('data', (d: Buffer) => { stderr += d.toString(); });\n proc.on('close', (code) => {\n clearTimeout(timer);\n if (killed) return;\n if (code === 0) {\n resolve(stdout);\n } else {\n reject(new Error(stderr.trim() || `brew ${args.join(' ')} exited with code ${code}`));\n }\n });\n proc.on('error', (err) => {\n clearTimeout(timer);\n if (killed) return;\n reject(new Error(`Failed to run brew: ${err.message}`));\n });\n });\n}\n\nexport async function* streamBrew(args: string[]): AsyncGenerator<string> {\n const proc = spawn('brew', args, {\n env: { ...process.env, HOMEBREW_NO_AUTO_UPDATE: '1' },\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n\n let buffer = '';\n const lines: string[] = [];\n let done = false;\n let exitError: string | null = null;\n let lastOutputAt = Date.now();\n\n const push = (chunk: Buffer) => {\n lastOutputAt = Date.now();\n buffer += chunk.toString();\n const parts = buffer.split('\\n');\n buffer = parts.pop() ?? '';\n for (const line of parts) {\n if (line.trim()) lines.push(line);\n }\n };\n\n proc.stdout.on('data', push);\n proc.stderr.on('data', push);\n\n proc.on('close', (code) => {\n if (buffer.trim()) lines.push(buffer.trim());\n done = true;\n if (code !== 0) {\n exitError = `brew ${args.join(' ')} exited with code ${code}`;\n }\n });\n\n proc.on('error', (err) => {\n done = true;\n exitError = err.message;\n });\n\n try {\n while (!done || lines.length > 0) {\n if (lines.length > 0) {\n yield lines.shift()!;\n } else if (!done) {\n // EP-012: Kill process if idle for too long\n if (Date.now() - lastOutputAt > STREAM_IDLE_TIMEOUT_MS) {\n proc.kill();\n throw new Error(`brew ${args.join(' ')} timed out: no output for ${STREAM_IDLE_TIMEOUT_MS / 1000}s`);\n }\n await new Promise((r) => setTimeout(r, 100));\n }\n }\n } finally {\n if (!done) {\n proc.kill();\n }\n }\n\n // Throw after all lines have been yielded so the consumer sees\n // brew's stderr output in the stream before the error surfaces.\n if (exitError) {\n throw new Error(exitError);\n }\n}\n"],"mappings":"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","names":[]}
|