brew-tui 1.2.1 → 1.2.3

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.
Files changed (37) hide show
  1. package/build/{brewbar-installer-GWJ76J6G.js → brewbar-installer-2OHLRM27.js} +37 -3
  2. package/build/brewbar-installer-2OHLRM27.js.map +1 -0
  3. package/build/{brewfile-manager-G7Q4IOG3.js → brewfile-manager-CPVXIVZC.js} +4 -3
  4. package/build/{chunk-KN4GCMIE.js → chunk-5PJWI4XS.js} +20 -1
  5. package/build/chunk-5PJWI4XS.js.map +1 -0
  6. package/build/{chunk-BRXZG7ZL.js → chunk-CMIC4N74.js} +11 -3
  7. package/build/chunk-CMIC4N74.js.map +1 -0
  8. package/build/{chunk-KDKXGXN2.js → chunk-EDQPT5EF.js} +15 -8
  9. package/build/chunk-EDQPT5EF.js.map +1 -0
  10. package/build/{chunk-J6HCX7RG.js → chunk-JNEIP2LJ.js} +2 -2
  11. package/build/chunk-NRRQECXA.js +63 -0
  12. package/build/chunk-NRRQECXA.js.map +1 -0
  13. package/build/{chunk-WDRT6G63.js → chunk-WX7MPVPH.js} +6 -46
  14. package/build/chunk-WX7MPVPH.js.map +1 -0
  15. package/build/{chunk-QX5DEW3S.js → chunk-XI743B6D.js} +408 -2
  16. package/build/chunk-XI743B6D.js.map +1 -0
  17. package/build/{compliance-checker-IXZHIMQG.js → compliance-checker-3FDEX4OI.js} +3 -3
  18. package/build/index.js +115 -400
  19. package/build/index.js.map +1 -1
  20. package/build/{policy-io-EECGRKNA.js → policy-io-P5YIH6C7.js} +2 -2
  21. package/build/{snapshot-ZOJETCED.js → snapshot-RQ444U5L.js} +2 -2
  22. package/build/{sync-engine-76YMONYH.js → sync-engine-Q4B2PPQS.js} +5 -4
  23. package/build/{version-check-AIYMRDFF.js → version-check-NS6RPUSU.js} +2 -2
  24. package/package.json +3 -2
  25. package/build/brewbar-installer-GWJ76J6G.js.map +0 -1
  26. package/build/chunk-BRXZG7ZL.js.map +0 -1
  27. package/build/chunk-KDKXGXN2.js.map +0 -1
  28. package/build/chunk-KN4GCMIE.js.map +0 -1
  29. package/build/chunk-QX5DEW3S.js.map +0 -1
  30. package/build/chunk-WDRT6G63.js.map +0 -1
  31. /package/build/{brewfile-manager-G7Q4IOG3.js.map → brewfile-manager-CPVXIVZC.js.map} +0 -0
  32. /package/build/{chunk-J6HCX7RG.js.map → chunk-JNEIP2LJ.js.map} +0 -0
  33. /package/build/{compliance-checker-IXZHIMQG.js.map → compliance-checker-3FDEX4OI.js.map} +0 -0
  34. /package/build/{policy-io-EECGRKNA.js.map → policy-io-P5YIH6C7.js.map} +0 -0
  35. /package/build/{snapshot-ZOJETCED.js.map → snapshot-RQ444U5L.js.map} +0 -0
  36. /package/build/{sync-engine-76YMONYH.js.map → sync-engine-Q4B2PPQS.js.map} +0 -0
  37. /package/build/{version-check-AIYMRDFF.js.map → version-check-NS6RPUSU.js.map} +0 -0
package/build/index.js CHANGED
@@ -1,11 +1,30 @@
1
1
  import {
2
+ brewUpdate,
3
+ casksToListItems,
2
4
  computeDrift,
3
5
  createDefaultBrewfile,
4
6
  diffSnapshots,
7
+ formulaeFromCask,
8
+ formulaeToListItems,
9
+ getCaskInfo,
10
+ getConfig,
11
+ getDoctor,
12
+ getFormulaInfo,
13
+ getInstalled,
14
+ getLeaves,
15
+ getOutdated,
16
+ getServices,
17
+ getUpgradeImpact,
5
18
  loadBrewfile,
19
+ pinPackage,
6
20
  reconcile,
7
- saveBrewfile
8
- } from "./chunk-QX5DEW3S.js";
21
+ saveBrewfile,
22
+ search,
23
+ serviceAction,
24
+ uninstallPackage,
25
+ unpinPackage,
26
+ validatePackageName
27
+ } from "./chunk-XI743B6D.js";
9
28
  import {
10
29
  activate,
11
30
  applyConflictResolutions,
@@ -19,21 +38,21 @@ import {
19
38
  readSyncEnvelope,
20
39
  revalidate,
21
40
  sync
22
- } from "./chunk-KDKXGXN2.js";
41
+ } from "./chunk-EDQPT5EF.js";
23
42
  import {
24
43
  checkCompliance
25
- } from "./chunk-J6HCX7RG.js";
44
+ } from "./chunk-JNEIP2LJ.js";
26
45
  import {
27
46
  captureSnapshot,
28
47
  execBrew,
29
48
  loadSnapshots,
30
49
  saveSnapshot,
31
50
  streamBrew
32
- } from "./chunk-BRXZG7ZL.js";
51
+ } from "./chunk-CMIC4N74.js";
33
52
  import {
34
53
  exportReport,
35
54
  loadPolicy
36
- } from "./chunk-KN4GCMIE.js";
55
+ } from "./chunk-5PJWI4XS.js";
37
56
  import {
38
57
  appendEntry,
39
58
  clearHistory,
@@ -49,13 +68,14 @@ import {
49
68
  writeLastAction
50
69
  } from "./chunk-IGDHDXUH.js";
51
70
  import {
52
- fetchWithRetry,
53
- fetchWithTimeout,
71
+ fetchWithRetry
72
+ } from "./chunk-NRRQECXA.js";
73
+ import {
54
74
  getLocale,
55
75
  t,
56
76
  tp,
57
77
  useLocaleStore
58
- } from "./chunk-WDRT6G63.js";
78
+ } from "./chunk-WX7MPVPH.js";
59
79
  import {
60
80
  logger
61
81
  } from "./chunk-KDHEUNRI.js";
@@ -343,18 +363,23 @@ var GRADIENTS = {
343
363
  import { useEffect, useState } from "react";
344
364
  import { Text as Text2 } from "ink";
345
365
  import { jsx as jsx2 } from "react/jsx-runtime";
366
+ function shouldDisableBlink() {
367
+ return Boolean(process.env.NO_COLOR) || process.env.REDUCE_MOTION === "1" || process.env.ACCESSIBILITY_REDUCE_MOTION === "1";
368
+ }
346
369
  function BlinkingText({
347
370
  color,
348
371
  intervalMs = 600,
349
372
  bold = true,
350
373
  children
351
374
  }) {
375
+ const blinkDisabled = shouldDisableBlink();
352
376
  const [bright, setBright] = useState(true);
353
377
  useEffect(() => {
378
+ if (blinkDisabled) return;
354
379
  const id = setInterval(() => setBright((b) => !b), intervalMs);
355
380
  return () => clearInterval(id);
356
- }, [intervalMs]);
357
- return /* @__PURE__ */ jsx2(Text2, { color, bold, dimColor: !bright, children });
381
+ }, [intervalMs, blinkDisabled]);
382
+ return /* @__PURE__ */ jsx2(Text2, { color, bold, dimColor: !bright && !blinkDisabled, children });
358
383
  }
359
384
 
360
385
  // src/utils/spacing.ts
@@ -1017,372 +1042,6 @@ function useVisibleRows({
1017
1042
 
1018
1043
  // src/stores/brew-store.ts
1019
1044
  import { create as create4 } from "zustand";
1020
-
1021
- // src/lib/brew-api.ts
1022
- import { spawn } from "child_process";
1023
-
1024
- // src/lib/parsers/json-parser.ts
1025
- function safeParse(raw, context) {
1026
- try {
1027
- const result = JSON.parse(raw);
1028
- if (result === null || result === void 0) {
1029
- throw new Error(`${context} returned null or empty response`);
1030
- }
1031
- return result;
1032
- } catch (err) {
1033
- throw new Error(`Failed to parse ${context} JSON: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
1034
- }
1035
- }
1036
- function parseInstalledJson(raw) {
1037
- const data = safeParse(raw, "brew info --installed");
1038
- return {
1039
- formulae: Array.isArray(data.formulae) ? data.formulae : [],
1040
- casks: Array.isArray(data.casks) ? data.casks : []
1041
- };
1042
- }
1043
- function parseOutdatedJson(raw) {
1044
- const data = safeParse(raw, "brew outdated");
1045
- return {
1046
- formulae: Array.isArray(data.formulae) ? data.formulae : [],
1047
- casks: Array.isArray(data.casks) ? data.casks : []
1048
- };
1049
- }
1050
- function parseServicesJson(raw) {
1051
- const data = safeParse(raw, "brew services list");
1052
- if (!Array.isArray(data)) return [];
1053
- return data.map((s) => ({
1054
- name: s.name,
1055
- status: s.status ?? "none",
1056
- user: s.user ?? null,
1057
- file: s.file ?? null,
1058
- exit_code: s.exit_code ?? null
1059
- }));
1060
- }
1061
- function parseFormulaInfoJson(raw) {
1062
- const data = safeParse(raw, "brew info");
1063
- return data.formulae?.[0] ?? null;
1064
- }
1065
-
1066
- // src/lib/parsers/text-parser.ts
1067
- function parseSearchResults(raw) {
1068
- const lines = raw.split("\n").map((l) => l.trim()).filter(Boolean);
1069
- const formulae = [];
1070
- const casks = [];
1071
- let section = "formulae";
1072
- for (const line of lines) {
1073
- if (line.startsWith("==> Formulae")) {
1074
- section = "formulae";
1075
- continue;
1076
- }
1077
- if (line.startsWith("==> Casks")) {
1078
- section = "casks";
1079
- continue;
1080
- }
1081
- if (line.startsWith("==>")) continue;
1082
- if (section === "formulae") formulae.push(line);
1083
- else casks.push(line);
1084
- }
1085
- return { formulae, casks };
1086
- }
1087
- function parseDoctorOutput(raw) {
1088
- const cleaned = raw.trim();
1089
- if (cleaned.includes("Your system is ready to brew")) {
1090
- return { warnings: [], isClean: true };
1091
- }
1092
- const warnings = [];
1093
- let current = "";
1094
- for (const line of cleaned.split("\n")) {
1095
- if (line.startsWith("Warning:")) {
1096
- if (current) warnings.push(current.trim());
1097
- current = line;
1098
- } else if (current) {
1099
- current += "\n" + line;
1100
- }
1101
- }
1102
- if (current) warnings.push(current.trim());
1103
- return { warnings, isClean: false };
1104
- }
1105
- function parseBrewConfig(raw) {
1106
- const lines = raw.split("\n");
1107
- const get = (key) => {
1108
- const line = lines.find((l) => l.startsWith(key));
1109
- return line?.split(":").slice(1).join(":").trim() ?? "";
1110
- };
1111
- return {
1112
- HOMEBREW_VERSION: get("HOMEBREW_VERSION"),
1113
- HOMEBREW_PREFIX: get("HOMEBREW_PREFIX"),
1114
- coreUpdated: get("Core tap last commit") || get("Core tap JSON")
1115
- };
1116
- }
1117
- function parseLeavesOutput(raw) {
1118
- return raw.split("\n").map((l) => l.trim()).filter(Boolean);
1119
- }
1120
-
1121
- // src/lib/impact/impact-analyzer.ts
1122
- var HIGH_RISK_PACKAGES = /* @__PURE__ */ new Set([
1123
- "openssl",
1124
- "openssl@3",
1125
- "openssl@1.1",
1126
- "python",
1127
- "python@3",
1128
- "python@3.11",
1129
- "python@3.12",
1130
- "python@3.13",
1131
- "node",
1132
- "node@18",
1133
- "node@20",
1134
- "ruby",
1135
- "ruby@3",
1136
- "sqlite",
1137
- "sqlite3",
1138
- "libpq",
1139
- "postgresql",
1140
- "postgresql@16",
1141
- "glibc",
1142
- "gcc",
1143
- "llvm"
1144
- ]);
1145
- function isMajorVersionBump(from, to) {
1146
- const fromMajor = parseInt(from.split(".")[0] ?? "0", 10);
1147
- const toMajor = parseInt(to.split(".")[0] ?? "0", 10);
1148
- return !isNaN(fromMajor) && !isNaN(toMajor) && toMajor > fromMajor;
1149
- }
1150
- function calculateRisk(name, reverseDeps, fromVersion, toVersion) {
1151
- const reasons = [];
1152
- if (HIGH_RISK_PACKAGES.has(name)) {
1153
- reasons.push(t("impact_reason_critical_package"));
1154
- return { risk: "high", reasons };
1155
- }
1156
- if (reverseDeps.length > 10) {
1157
- reasons.push(t("impact_reason_many_deps", { count: reverseDeps.length }));
1158
- return { risk: "high", reasons };
1159
- }
1160
- let factorCount = 0;
1161
- if (reverseDeps.length >= 3) {
1162
- factorCount++;
1163
- reasons.push(t("impact_reason_many_deps", { count: reverseDeps.length }));
1164
- }
1165
- if (isMajorVersionBump(fromVersion, toVersion)) {
1166
- factorCount++;
1167
- reasons.push(t("impact_reason_major_bump"));
1168
- }
1169
- const risk = factorCount >= 2 ? "high" : factorCount === 1 ? "medium" : "low";
1170
- return { risk, reasons };
1171
- }
1172
- async function analyzeUpgradeImpact(packageName, fromVersion, toVersion, packageType) {
1173
- if (packageType === "cask") {
1174
- return {
1175
- packageName,
1176
- fromVersion,
1177
- toVersion,
1178
- packageType,
1179
- directDeps: [],
1180
- reverseDeps: [],
1181
- risk: "low",
1182
- riskReasons: []
1183
- };
1184
- }
1185
- let directDeps = [];
1186
- let reverseDeps = [];
1187
- try {
1188
- const depsOutput = await execBrew(["deps", "--1", packageName]);
1189
- directDeps = depsOutput.split("\n").filter((l) => l.trim() !== "");
1190
- } catch (err) {
1191
- logger.warn(`impact-analyzer: deps failed for ${packageName}: ${err instanceof Error ? err.message : String(err)}`);
1192
- }
1193
- try {
1194
- const usesOutput = await execBrew(["uses", "--installed", packageName]);
1195
- reverseDeps = usesOutput.split("\n").filter((l) => l.trim() !== "");
1196
- } catch (err) {
1197
- logger.warn(`impact-analyzer: uses failed for ${packageName}: ${err instanceof Error ? err.message : String(err)}`);
1198
- }
1199
- const { risk, reasons } = calculateRisk(packageName, reverseDeps, fromVersion, toVersion);
1200
- return {
1201
- packageName,
1202
- fromVersion,
1203
- toVersion,
1204
- packageType,
1205
- directDeps,
1206
- reverseDeps,
1207
- risk,
1208
- riskReasons: reasons
1209
- };
1210
- }
1211
-
1212
- // src/lib/brew-api.ts
1213
- var PKG_PATTERN = /^[\w@./+-]+$/;
1214
- function validatePackageName(name) {
1215
- if (!PKG_PATTERN.test(name)) throw new Error("Invalid package name: " + name);
1216
- }
1217
- async function brewUpdate() {
1218
- return new Promise((resolve, reject) => {
1219
- const proc = spawn("brew", ["update"], { stdio: "ignore" });
1220
- let settled = false;
1221
- const timeout = setTimeout(() => {
1222
- if (settled) return;
1223
- settled = true;
1224
- proc.kill("SIGTERM");
1225
- reject(new Error("brew update timed out after 120s"));
1226
- }, 12e4);
1227
- proc.on("close", (code) => {
1228
- if (settled) return;
1229
- settled = true;
1230
- clearTimeout(timeout);
1231
- if (code === 0) resolve();
1232
- else reject(new Error(`brew update exited with code ${code}`));
1233
- });
1234
- proc.on("error", (err) => {
1235
- if (settled) return;
1236
- settled = true;
1237
- clearTimeout(timeout);
1238
- reject(err);
1239
- });
1240
- });
1241
- }
1242
- async function getInstalled() {
1243
- const raw = await execBrew(["info", "--json=v2", "--installed"]);
1244
- return parseInstalledJson(raw);
1245
- }
1246
- async function getOutdated() {
1247
- const raw = await execBrew(["outdated", "--json=v2"]);
1248
- return parseOutdatedJson(raw);
1249
- }
1250
- async function getServices() {
1251
- const raw = await execBrew(["services", "list", "--json"]);
1252
- return parseServicesJson(raw);
1253
- }
1254
- async function getFormulaInfo(name) {
1255
- validatePackageName(name);
1256
- const raw = await execBrew(["info", "--json=v2", name]);
1257
- return parseFormulaInfoJson(raw);
1258
- }
1259
- async function getCaskInfo(name) {
1260
- validatePackageName(name);
1261
- try {
1262
- const raw = await execBrew(["info", "--json=v2", "--cask", name]);
1263
- const data = JSON.parse(raw);
1264
- return data.casks?.[0] ?? null;
1265
- } catch {
1266
- return null;
1267
- }
1268
- }
1269
- function formulaeFromCask(cask) {
1270
- return {
1271
- name: cask.token,
1272
- full_name: cask.full_token,
1273
- tap: "",
1274
- desc: cask.desc,
1275
- license: "",
1276
- homepage: cask.homepage,
1277
- versions: { stable: cask.version, head: null, bottle: false },
1278
- dependencies: [],
1279
- build_dependencies: [],
1280
- installed: cask.installed ? [{
1281
- version: cask.installed,
1282
- used_options: [],
1283
- built_as_bottle: false,
1284
- poured_from_bottle: false,
1285
- time: cask.installed_time ?? 0,
1286
- runtime_dependencies: [],
1287
- installed_as_dependency: false,
1288
- installed_on_request: true
1289
- }] : [],
1290
- linked_keg: null,
1291
- pinned: false,
1292
- outdated: cask.outdated,
1293
- deprecated: false,
1294
- keg_only: false,
1295
- caveats: null
1296
- };
1297
- }
1298
- async function search(term) {
1299
- const safeTerm = term.replace(/^-+/, "");
1300
- if (!safeTerm) return { formulae: [], casks: [] };
1301
- const raw = await execBrew(["search", safeTerm]);
1302
- return parseSearchResults(raw);
1303
- }
1304
- async function getDoctor() {
1305
- try {
1306
- const raw = await execBrew(["doctor"]);
1307
- return parseDoctorOutput(raw);
1308
- } catch (err) {
1309
- const msg = err instanceof Error ? err.message : String(err);
1310
- return parseDoctorOutput(msg);
1311
- }
1312
- }
1313
- async function getConfig() {
1314
- const raw = await execBrew(["config"]);
1315
- return parseBrewConfig(raw);
1316
- }
1317
- async function getLeaves() {
1318
- const raw = await execBrew(["leaves"]);
1319
- return parseLeavesOutput(raw);
1320
- }
1321
- async function uninstallPackage(name) {
1322
- validatePackageName(name);
1323
- return execBrew(["uninstall", name]);
1324
- }
1325
- async function serviceAction(name, action) {
1326
- validatePackageName(name);
1327
- return execBrew(["services", action, name]);
1328
- }
1329
- async function pinPackage(name) {
1330
- validatePackageName(name);
1331
- return execBrew(["pin", name]);
1332
- }
1333
- async function unpinPackage(name) {
1334
- validatePackageName(name);
1335
- return execBrew(["unpin", name]);
1336
- }
1337
- function formulaeToListItems(formulae) {
1338
- return formulae.map((f) => {
1339
- const installed = f.installed[0];
1340
- return {
1341
- name: f.name,
1342
- version: installed?.version ?? f.versions.stable,
1343
- desc: f.desc,
1344
- type: "formula",
1345
- outdated: f.outdated,
1346
- pinned: f.pinned,
1347
- kegOnly: f.keg_only,
1348
- installedAsDependency: installed?.installed_as_dependency ?? false,
1349
- installedTime: installed?.time ?? null
1350
- };
1351
- });
1352
- }
1353
- var impactCache = /* @__PURE__ */ new Map();
1354
- var IMPACT_CACHE_LIMIT = 64;
1355
- function impactKey(name, from, to, type) {
1356
- return `${type}::${name}::${from}::${to}`;
1357
- }
1358
- async function getUpgradeImpact(packageName, fromVersion, toVersion, packageType) {
1359
- validatePackageName(packageName);
1360
- const key = impactKey(packageName, fromVersion, toVersion, packageType);
1361
- const cached2 = impactCache.get(key);
1362
- if (cached2) return cached2;
1363
- const result = await analyzeUpgradeImpact(packageName, fromVersion, toVersion, packageType);
1364
- if (impactCache.size >= IMPACT_CACHE_LIMIT) {
1365
- const firstKey = impactCache.keys().next().value;
1366
- if (firstKey !== void 0) impactCache.delete(firstKey);
1367
- }
1368
- impactCache.set(key, result);
1369
- return result;
1370
- }
1371
- function casksToListItems(casks) {
1372
- return casks.map((c) => ({
1373
- name: c.token,
1374
- version: c.installed ?? c.version,
1375
- desc: c.desc,
1376
- type: "cask",
1377
- outdated: c.outdated,
1378
- pinned: false,
1379
- kegOnly: false,
1380
- installedAsDependency: false,
1381
- installedTime: c.installed_time ?? null
1382
- }));
1383
- }
1384
-
1385
- // src/stores/brew-store.ts
1386
1045
  var BREW_UPDATE_COOLDOWN_MS = 5 * 60 * 1e3;
1387
1046
  var fetchAllInFlight = null;
1388
1047
  var brewUpdateInFlight = null;
@@ -2348,7 +2007,7 @@ function useBrewStream() {
2348
2007
  }
2349
2008
  const MUTATING_COMMANDS = /* @__PURE__ */ new Set(["install", "uninstall", "upgrade", "pin", "unpin", "tap", "untap"]);
2350
2009
  if (!cancelRef.current && MUTATING_COMMANDS.has(args[0] ?? "")) {
2351
- void import("./snapshot-ZOJETCED.js").then(({ captureSnapshot: captureSnapshot2, saveSnapshot: saveSnapshot2 }) => {
2010
+ void import("./snapshot-RQ444U5L.js").then(({ captureSnapshot: captureSnapshot2, saveSnapshot: saveSnapshot2 }) => {
2352
2011
  captureSnapshot2().then((s) => saveSnapshot2(s)).catch((err) => logger.warn("snapshot: capture/save failed", { error: String(err) }));
2353
2012
  });
2354
2013
  }
@@ -2639,6 +2298,13 @@ function InstalledView() {
2639
2298
  setConfirmUninstall(null);
2640
2299
  void stream.run(["uninstall", name]).then(() => {
2641
2300
  fetchInstalled();
2301
+ void writeLastAction({
2302
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2303
+ action: "uninstall",
2304
+ packages: [name],
2305
+ remainingOutdated: 0,
2306
+ source: "brew-tui"
2307
+ });
2642
2308
  });
2643
2309
  },
2644
2310
  onCancel: () => setConfirmUninstall(null)
@@ -2708,6 +2374,7 @@ function SearchView() {
2708
2374
  const selectPackage = useNavigationStore((s) => s.selectPackage);
2709
2375
  const fetchInstalled = useBrewStore((s) => s.fetchInstalled);
2710
2376
  const hasRefreshed = useRef4(false);
2377
+ const pendingInstallRef = useRef4(null);
2711
2378
  const resultRows = useVisibleRows({
2712
2379
  reservedRows: searchError ? 8 : 6,
2713
2380
  fallbackReservedRows: searchError ? 18 : 16,
@@ -2750,6 +2417,17 @@ function SearchView() {
2750
2417
  if (!stream.isRunning && !stream.error && stream.lines.length > 0 && !hasRefreshed.current) {
2751
2418
  hasRefreshed.current = true;
2752
2419
  void fetchInstalled();
2420
+ const installed = pendingInstallRef.current;
2421
+ if (installed) {
2422
+ pendingInstallRef.current = null;
2423
+ void writeLastAction({
2424
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2425
+ action: "install",
2426
+ packages: [installed],
2427
+ remainingOutdated: 0,
2428
+ source: "brew-tui"
2429
+ });
2430
+ }
2753
2431
  }
2754
2432
  }, [stream.isRunning, stream.error]);
2755
2433
  const allResults = results ? [
@@ -2860,6 +2538,7 @@ function SearchView() {
2860
2538
  onConfirm: () => {
2861
2539
  const name = confirmInstall;
2862
2540
  hasRefreshed.current = false;
2541
+ pendingInstallRef.current = name;
2863
2542
  setConfirmInstall(null);
2864
2543
  void stream.run(["install", name]);
2865
2544
  },
@@ -2928,7 +2607,10 @@ function ImpactPanel({ impact }) {
2928
2607
  ] });
2929
2608
  }
2930
2609
  function OutdatedView() {
2931
- const { outdated, loading, errors, fetchOutdated } = useBrewStore();
2610
+ const outdated = useBrewStore((s) => s.outdated);
2611
+ const loading = useBrewStore((s) => s.loading);
2612
+ const errors = useBrewStore((s) => s.errors);
2613
+ const fetchOutdated = useBrewStore((s) => s.fetchOutdated);
2932
2614
  const stream = useBrewStream();
2933
2615
  const [cursor, setCursor] = useState7(0);
2934
2616
  const [confirmAction, setConfirmAction] = useState7(null);
@@ -3364,7 +3046,11 @@ function humaniseServiceError(message) {
3364
3046
  return message;
3365
3047
  }
3366
3048
  function ServicesView() {
3367
- const { services, loading, errors, fetchServices, serviceAction: serviceAction2 } = useBrewStore();
3049
+ const services = useBrewStore((s) => s.services);
3050
+ const loading = useBrewStore((s) => s.loading);
3051
+ const errors = useBrewStore((s) => s.errors);
3052
+ const fetchServices = useBrewStore((s) => s.fetchServices);
3053
+ const serviceAction2 = useBrewStore((s) => s.serviceAction);
3368
3054
  const [cursor, setCursor] = useState9(0);
3369
3055
  const [actionInProgress, setActionInProgress] = useState9(false);
3370
3056
  const [confirmAction, setConfirmAction] = useState9(null);
@@ -3499,7 +3185,11 @@ import { useEffect as useEffect13, useRef as useRef8, useState as useState10 } f
3499
3185
  import { Box as Box20, Text as Text23 } from "ink";
3500
3186
  import { jsx as jsx24, jsxs as jsxs21 } from "react/jsx-runtime";
3501
3187
  function DoctorView() {
3502
- const { doctorWarnings, doctorClean, loading, errors, fetchDoctor } = useBrewStore();
3188
+ const doctorWarnings = useBrewStore((s) => s.doctorWarnings);
3189
+ const doctorClean = useBrewStore((s) => s.doctorClean);
3190
+ const loading = useBrewStore((s) => s.loading);
3191
+ const errors = useBrewStore((s) => s.errors);
3192
+ const fetchDoctor = useBrewStore((s) => s.fetchDoctor);
3503
3193
  const [cursor, setCursor] = useState10(0);
3504
3194
  const visibleWarnings = useVisibleRows({
3505
3195
  reservedRows: 6,
@@ -3674,7 +3364,7 @@ async function updateProfile(isPro, oldName, newName, newDescription) {
3674
3364
  await saveProfile(isPro, updated);
3675
3365
  }
3676
3366
  var TAP_PATTERN = /^[a-z0-9][-a-z0-9]*\/[a-z0-9][-a-z0-9]*$/;
3677
- var PKG_PATTERN2 = /^[a-z0-9][-a-z0-9_.@+]*$/;
3367
+ var PKG_PATTERN = /^[a-z0-9][-a-z0-9_.@+]*$/;
3678
3368
  async function* importProfile(isPro, profile) {
3679
3369
  proCheck(isPro);
3680
3370
  const installed = await getInstalled();
@@ -3693,7 +3383,7 @@ async function* importProfile(isPro, profile) {
3693
3383
  }
3694
3384
  const missingFormulae = profile.formulae.filter((f) => !installedFormulae.has(f));
3695
3385
  for (const name of missingFormulae) {
3696
- if (!PKG_PATTERN2.test(name)) {
3386
+ if (!PKG_PATTERN.test(name)) {
3697
3387
  yield `Skipping invalid formula name: ${name}`;
3698
3388
  continue;
3699
3389
  }
@@ -3704,7 +3394,7 @@ async function* importProfile(isPro, profile) {
3704
3394
  }
3705
3395
  const missingCasks = profile.casks.filter((c) => !installedCasks.has(c));
3706
3396
  for (const name of missingCasks) {
3707
- if (!PKG_PATTERN2.test(name)) {
3397
+ if (!PKG_PATTERN.test(name)) {
3708
3398
  yield `Skipping invalid cask name: ${name}`;
3709
3399
  continue;
3710
3400
  }
@@ -4772,7 +4462,7 @@ async function redeemPromoCode(code) {
4772
4462
  let serverType;
4773
4463
  const idempotencyKey = randomUUID();
4774
4464
  try {
4775
- const res = await fetchWithTimeout(`${PROMO_API_URL}/redeem`, {
4465
+ const res = await fetchWithRetry(`${PROMO_API_URL}/redeem`, {
4776
4466
  method: "POST",
4777
4467
  headers: {
4778
4468
  "Content-Type": "application/json",
@@ -4842,7 +4532,7 @@ function AccountView() {
4842
4532
  }
4843
4533
  return;
4844
4534
  }
4845
- if ((input === "d" || input === "2") && status === "pro") {
4535
+ if ((input === "d" || input === "2") && (status === "pro" || status === "team")) {
4846
4536
  setConfirmDeactivate(true);
4847
4537
  }
4848
4538
  if (input === "p" || input === "1") {
@@ -4886,6 +4576,7 @@ function AccountView() {
4886
4576
  /* @__PURE__ */ jsxs30(Box29, { gap: SPACING.xs, children: [
4887
4577
  /* @__PURE__ */ jsx33(Text31, { color: COLORS.muted, children: t("account_statusLabel") }),
4888
4578
  status === "pro" && /* @__PURE__ */ jsx33(Text31, { color: COLORS.success, bold: true, children: t("account_pro") }),
4579
+ status === "team" && /* @__PURE__ */ jsx33(Text31, { color: COLORS.success, bold: true, children: t("account_team") }),
4889
4580
  status === "free" && /* @__PURE__ */ jsx33(Text31, { color: COLORS.muted, children: t("account_free") }),
4890
4581
  status === "expired" && /* @__PURE__ */ jsx33(Text31, { color: COLORS.error, children: t("account_expired") })
4891
4582
  ] }),
@@ -4977,7 +4668,7 @@ function AccountView() {
4977
4668
  status === "pro" || status === "team" || status === "expired" ? `v ${t("hint_revalidate")} ` : "",
4978
4669
  revalidating ? t("account_revalidating") : "",
4979
4670
  " ",
4980
- t("app_version", { version: "1.2.1" })
4671
+ t("app_version", { version: "1.2.3" })
4981
4672
  ] }) })
4982
4673
  ] });
4983
4674
  }
@@ -5110,6 +4801,13 @@ async function* executeRollbackPlan(plan, isPro) {
5110
4801
  yield `[skip] ${action.packageName}: removal skipped for safety \u2014 remove manually if needed`;
5111
4802
  continue;
5112
4803
  }
4804
+ try {
4805
+ validatePackageName(action.packageName);
4806
+ if (action.versionedFormula) validatePackageName(action.versionedFormula);
4807
+ } catch (err) {
4808
+ yield `[reject] ${action.packageName}: ${err instanceof Error ? err.message : String(err)}`;
4809
+ continue;
4810
+ }
5113
4811
  if (action.action === "install") {
5114
4812
  if (action.strategy === "pin-only") {
5115
4813
  yield `[warn] ${action.packageName}: cannot install specific version \u2014 skipping`;
@@ -5371,7 +5069,10 @@ function RollbackView() {
5371
5069
  if (loading) return /* @__PURE__ */ jsx34(Loading, { message: t("rollback_select_snapshot") });
5372
5070
  if (error) return /* @__PURE__ */ jsx34(ErrorMessage, { message: error });
5373
5071
  if (phase === "executing") {
5374
- return /* @__PURE__ */ jsx34(Box30, { flexDirection: "column", children: /* @__PURE__ */ jsx34(ProgressLog, { lines: streamLines, isRunning: streamRunning, title: t("rollback_executing") }) });
5072
+ return /* @__PURE__ */ jsxs31(Box30, { flexDirection: "column", children: [
5073
+ /* @__PURE__ */ jsx34(ProgressLog, { lines: streamLines, isRunning: streamRunning, title: t("rollback_executing") }),
5074
+ /* @__PURE__ */ jsx34(Box30, { marginTop: SPACING.xs, children: /* @__PURE__ */ jsx34(Text32, { color: COLORS.warning, children: t("rollback_executing_no_cancel") }) })
5075
+ ] });
5375
5076
  }
5376
5077
  if (phase === "result") {
5377
5078
  return /* @__PURE__ */ jsxs31(Box30, { flexDirection: "column", marginTop: SPACING.xs, children: [
@@ -5984,6 +5685,13 @@ async function* remediateViolations(violations, isPro) {
5984
5685
  let skipped = 0;
5985
5686
  for (const v of violations) {
5986
5687
  if (v.type === "missing") {
5688
+ try {
5689
+ validatePackageName(v.packageName);
5690
+ } catch (err) {
5691
+ yield ` \u2717 Rejected ${v.packageName}: ${err instanceof Error ? err.message : String(err)}`;
5692
+ skipped++;
5693
+ continue;
5694
+ }
5987
5695
  yield `Installing ${v.packageName}...`;
5988
5696
  try {
5989
5697
  for await (const line of streamBrew(["install", v.packageName])) {
@@ -5995,6 +5703,13 @@ async function* remediateViolations(violations, isPro) {
5995
5703
  skipped++;
5996
5704
  }
5997
5705
  } else if (v.type === "wrong-version") {
5706
+ try {
5707
+ validatePackageName(v.packageName);
5708
+ } catch (err) {
5709
+ yield ` \u2717 Rejected ${v.packageName}: ${err instanceof Error ? err.message : String(err)}`;
5710
+ skipped++;
5711
+ continue;
5712
+ }
5998
5713
  yield `Upgrading ${v.packageName}${v.required ? ` to ${v.required}+` : ""}...`;
5999
5714
  try {
6000
5715
  for await (const line of streamBrew(["upgrade", v.packageName])) {
@@ -6461,7 +6176,7 @@ async function reportError(err, context = {}) {
6461
6176
  const config = await resolveConfig();
6462
6177
  if (!config.enabled || !config.endpoint) return;
6463
6178
  const machineId = await getMachineId();
6464
- const version = true ? "1.2.1" : "unknown";
6179
+ const version = true ? "1.2.3" : "unknown";
6465
6180
  await postReport(buildReport("error", err, context, machineId, version), config);
6466
6181
  }
6467
6182
  async function installCrashReporter() {
@@ -6470,7 +6185,7 @@ async function installCrashReporter() {
6470
6185
  if (!config.enabled || !config.endpoint) return;
6471
6186
  _installed = true;
6472
6187
  const machineId = await getMachineId();
6473
- const version = true ? "1.2.1" : "unknown";
6188
+ const version = true ? "1.2.3" : "unknown";
6474
6189
  process.on("uncaughtException", (err) => {
6475
6190
  void postReport(buildReport("fatal", err, { kind: "uncaughtException" }, machineId, version), config);
6476
6191
  });
@@ -6485,7 +6200,7 @@ import { jsx as jsx39 } from "react/jsx-runtime";
6485
6200
  var [, , command, arg] = process.argv;
6486
6201
  async function runCli() {
6487
6202
  if (command === "--version" || command === "-v" || command === "version") {
6488
- process.stdout.write("1.2.1\n");
6203
+ process.stdout.write("1.2.3\n");
6489
6204
  return;
6490
6205
  }
6491
6206
  await ensureDataDirs();
@@ -6580,7 +6295,7 @@ async function runCli() {
6580
6295
  }
6581
6296
  if (isPro) {
6582
6297
  try {
6583
- const { loadSnapshots: loadSnapshots2 } = await import("./snapshot-ZOJETCED.js");
6298
+ const { loadSnapshots: loadSnapshots2 } = await import("./snapshot-RQ444U5L.js");
6584
6299
  const snapshots = await loadSnapshots2();
6585
6300
  if (snapshots.length > 0) {
6586
6301
  const latest = snapshots[0];
@@ -6592,7 +6307,7 @@ Snapshots: ${snapshots.length} (latest: ${latest ? formatDate(latest.capturedAt)
6592
6307
  } catch {
6593
6308
  }
6594
6309
  try {
6595
- const { loadBrewfile: loadBrewfile2, computeDrift: computeDrift2 } = await import("./brewfile-manager-G7Q4IOG3.js");
6310
+ const { loadBrewfile: loadBrewfile2, computeDrift: computeDrift2 } = await import("./brewfile-manager-CPVXIVZC.js");
6596
6311
  const schema = await loadBrewfile2();
6597
6312
  if (schema) {
6598
6313
  const drift = await computeDrift2(schema);
@@ -6601,7 +6316,7 @@ Snapshots: ${snapshots.length} (latest: ${latest ? formatDate(latest.capturedAt)
6601
6316
  } catch {
6602
6317
  }
6603
6318
  try {
6604
- const { loadSyncConfig: loadSyncConfig2 } = await import("./sync-engine-76YMONYH.js");
6319
+ const { loadSyncConfig: loadSyncConfig2 } = await import("./sync-engine-Q4B2PPQS.js");
6605
6320
  const syncConfig = await loadSyncConfig2();
6606
6321
  if (syncConfig?.lastSync) {
6607
6322
  console.log(`Sync: last sync ${formatDate(syncConfig.lastSync)}`);
@@ -6609,8 +6324,8 @@ Snapshots: ${snapshots.length} (latest: ${latest ? formatDate(latest.capturedAt)
6609
6324
  } catch {
6610
6325
  }
6611
6326
  try {
6612
- const { loadPolicy: loadPolicy2 } = await import("./policy-io-EECGRKNA.js");
6613
- const { checkCompliance: checkCompliance2 } = await import("./compliance-checker-IXZHIMQG.js");
6327
+ const { loadPolicy: loadPolicy2 } = await import("./policy-io-P5YIH6C7.js");
6328
+ const { checkCompliance: checkCompliance2 } = await import("./compliance-checker-3FDEX4OI.js");
6614
6329
  const policy = await loadPolicy2(`${process.env["HOME"] ?? "~"}/.brew-tui/policy.yaml`).catch(() => null);
6615
6330
  if (policy) {
6616
6331
  const report = await checkCompliance2(policy, true);
@@ -6624,7 +6339,7 @@ Snapshots: ${snapshots.length} (latest: ${latest ? formatDate(latest.capturedAt)
6624
6339
  if (command === "install-brewbar") {
6625
6340
  await useLicenseStore.getState().initialize();
6626
6341
  const isPro = useLicenseStore.getState().isPro();
6627
- const { installBrewBar } = await import("./brewbar-installer-GWJ76J6G.js");
6342
+ const { installBrewBar } = await import("./brewbar-installer-2OHLRM27.js");
6628
6343
  try {
6629
6344
  console.log(t("cli_brewbarInstalling"));
6630
6345
  await installBrewBar(isPro, arg === "--force");
@@ -6636,7 +6351,7 @@ Snapshots: ${snapshots.length} (latest: ${latest ? formatDate(latest.capturedAt)
6636
6351
  return;
6637
6352
  }
6638
6353
  if (command === "uninstall-brewbar") {
6639
- const { uninstallBrewBar } = await import("./brewbar-installer-GWJ76J6G.js");
6354
+ const { uninstallBrewBar } = await import("./brewbar-installer-2OHLRM27.js");
6640
6355
  try {
6641
6356
  await uninstallBrewBar();
6642
6357
  console.log(t("cli_brewbarUninstalled"));
@@ -6667,8 +6382,8 @@ async function ensureBrewBarRunning() {
6667
6382
  if (process.platform !== "darwin") return;
6668
6383
  await useLicenseStore.getState().initialize();
6669
6384
  if (!useLicenseStore.getState().isPro()) return;
6670
- const { isBrewBarInstalled, installBrewBar, launchBrewBar } = await import("./brewbar-installer-GWJ76J6G.js");
6671
- const { checkBrewBarVersion } = await import("./version-check-AIYMRDFF.js");
6385
+ const { isBrewBarInstalled, installBrewBar, launchBrewBar } = await import("./brewbar-installer-2OHLRM27.js");
6386
+ const { checkBrewBarVersion } = await import("./version-check-NS6RPUSU.js");
6672
6387
  try {
6673
6388
  if (!await isBrewBarInstalled()) {
6674
6389
  console.log(t("cli_brewbarInstalling"));