brew-tui 1.2.0 → 1.2.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.
Files changed (36) hide show
  1. package/build/{brewbar-installer-GWJ76J6G.js → brewbar-installer-BKE6Z7OI.js} +5 -3
  2. package/build/{brewbar-installer-GWJ76J6G.js.map → brewbar-installer-BKE6Z7OI.js.map} +1 -1
  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 +299 -566
  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-MJZDQG73.js → version-check-FKY5HGSI.js} +2 -2
  24. package/package.json +1 -1
  25. package/build/chunk-BRXZG7ZL.js.map +0 -1
  26. package/build/chunk-KDKXGXN2.js.map +0 -1
  27. package/build/chunk-KN4GCMIE.js.map +0 -1
  28. package/build/chunk-QX5DEW3S.js.map +0 -1
  29. package/build/chunk-WDRT6G63.js.map +0 -1
  30. /package/build/{brewfile-manager-G7Q4IOG3.js.map → brewfile-manager-CPVXIVZC.js.map} +0 -0
  31. /package/build/{chunk-J6HCX7RG.js.map → chunk-JNEIP2LJ.js.map} +0 -0
  32. /package/build/{compliance-checker-IXZHIMQG.js.map → compliance-checker-3FDEX4OI.js.map} +0 -0
  33. /package/build/{policy-io-EECGRKNA.js.map → policy-io-P5YIH6C7.js.map} +0 -0
  34. /package/build/{snapshot-ZOJETCED.js.map → snapshot-RQ444U5L.js.map} +0 -0
  35. /package/build/{sync-engine-76YMONYH.js.map → sync-engine-Q4B2PPQS.js.map} +0 -0
  36. /package/build/{version-check-MJZDQG73.js.map → version-check-FKY5HGSI.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";
@@ -66,7 +86,7 @@ import { rm as rm2 } from "fs/promises";
66
86
  import { render } from "ink";
67
87
 
68
88
  // src/app.tsx
69
- import { useEffect as useEffect23, useState as useState21 } from "react";
89
+ import { useEffect as useEffect22, useState as useState20 } from "react";
70
90
  import { useApp } from "ink";
71
91
 
72
92
  // src/components/layout/app-layout.tsx
@@ -77,28 +97,46 @@ import { Box as Box3 } from "ink";
77
97
  import { Box, Text as Text3 } from "ink";
78
98
 
79
99
  // src/hooks/use-terminal-size.ts
80
- import { useEffect, useState } from "react";
100
+ import { useSyncExternalStore } from "react";
81
101
  import { useStdout } from "ink";
102
+ var FALLBACK = { columns: 80, rows: 24 };
103
+ var cache = /* @__PURE__ */ new WeakMap();
104
+ function snapshot(stdout) {
105
+ return {
106
+ columns: stdout.columns ?? FALLBACK.columns,
107
+ rows: stdout.rows ?? FALLBACK.rows
108
+ };
109
+ }
110
+ function getCache(stdout) {
111
+ const existing = cache.get(stdout);
112
+ if (existing) return existing;
113
+ const entry = {
114
+ current: snapshot(stdout),
115
+ subscribers: /* @__PURE__ */ new Set()
116
+ };
117
+ cache.set(stdout, entry);
118
+ stdout.on("resize", () => {
119
+ entry.current = snapshot(stdout);
120
+ entry.subscribers.forEach((cb) => cb());
121
+ });
122
+ return entry;
123
+ }
82
124
  function useTerminalSize() {
83
125
  const { stdout } = useStdout();
84
- const [size, setSize] = useState(() => ({
85
- columns: stdout?.columns ?? 80,
86
- rows: stdout?.rows ?? 24
87
- }));
88
- useEffect(() => {
89
- if (!stdout) return;
90
- const onResize = () => {
91
- setSize({
92
- columns: stdout.columns ?? 80,
93
- rows: stdout.rows ?? 24
94
- });
95
- };
96
- stdout.on("resize", onResize);
97
- return () => {
98
- stdout.off("resize", onResize);
99
- };
100
- }, [stdout]);
101
- return size;
126
+ return useSyncExternalStore(
127
+ (cb) => {
128
+ if (!stdout) return () => {
129
+ };
130
+ const entry = getCache(stdout);
131
+ entry.subscribers.add(cb);
132
+ return () => {
133
+ entry.subscribers.delete(cb);
134
+ };
135
+ },
136
+ () => stdout ? getCache(stdout).current : FALLBACK,
137
+ // Server snapshot — not used in CLI, but useSyncExternalStore requires it.
138
+ () => FALLBACK
139
+ );
102
140
  }
103
141
 
104
142
  // src/stores/navigation-store.ts
@@ -322,21 +360,26 @@ var GRADIENTS = {
322
360
  };
323
361
 
324
362
  // src/components/common/blinking-text.tsx
325
- import { useEffect as useEffect2, useState as useState2 } from "react";
363
+ import { useEffect, useState } from "react";
326
364
  import { Text as Text2 } from "ink";
327
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
+ }
328
369
  function BlinkingText({
329
370
  color,
330
371
  intervalMs = 600,
331
372
  bold = true,
332
373
  children
333
374
  }) {
334
- const [bright, setBright] = useState2(true);
335
- useEffect2(() => {
375
+ const blinkDisabled = shouldDisableBlink();
376
+ const [bright, setBright] = useState(true);
377
+ useEffect(() => {
378
+ if (blinkDisabled) return;
336
379
  const id = setInterval(() => setBright((b) => !b), intervalMs);
337
380
  return () => clearInterval(id);
338
- }, [intervalMs]);
339
- return /* @__PURE__ */ jsx2(Text2, { color, bold, dimColor: !bright, children });
381
+ }, [intervalMs, blinkDisabled]);
382
+ return /* @__PURE__ */ jsx2(Text2, { color, bold, dimColor: !bright && !blinkDisabled, children });
340
383
  }
341
384
 
342
385
  // src/utils/spacing.ts
@@ -567,12 +610,12 @@ function Footer() {
567
610
  }
568
611
 
569
612
  // src/hooks/use-container-size.ts
570
- import { useEffect as useEffect3, useState as useState3 } from "react";
613
+ import { useEffect as useEffect2, useState as useState2 } from "react";
571
614
  import { measureElement } from "ink";
572
615
  function useContainerSize(ref) {
573
616
  const terminal = useTerminalSize();
574
- const [size, setSize] = useState3({ width: 0, height: 0 });
575
- useEffect3(() => {
617
+ const [size, setSize] = useState2({ width: 0, height: 0 });
618
+ useEffect2(() => {
576
619
  if (!ref.current) return;
577
620
  const measured = measureElement(ref.current);
578
621
  setSize(
@@ -849,7 +892,7 @@ async function markOnboardingComplete() {
849
892
  }
850
893
 
851
894
  // src/views/welcome.tsx
852
- import { useEffect as useEffect4 } from "react";
895
+ import { useEffect as useEffect3 } from "react";
853
896
  import { Box as Box4, Text as Text5 } from "ink";
854
897
 
855
898
  // src/hooks/use-view-input.ts
@@ -863,7 +906,7 @@ function useViewInput(handler, opts) {
863
906
  // src/views/welcome.tsx
864
907
  import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
865
908
  function WelcomeView({ onContinue }) {
866
- useEffect4(() => {
909
+ useEffect3(() => {
867
910
  return () => {
868
911
  };
869
912
  }, []);
@@ -982,7 +1025,7 @@ function UpgradePrompt({ viewId }) {
982
1025
  }
983
1026
 
984
1027
  // src/views/dashboard.tsx
985
- import { useEffect as useEffect5, useMemo as useMemo2 } from "react";
1028
+ import { useEffect as useEffect4, useMemo as useMemo2 } from "react";
986
1029
  import { Box as Box9, Text as Text12 } from "ink";
987
1030
 
988
1031
  // src/hooks/use-visible-rows.ts
@@ -999,372 +1042,6 @@ function useVisibleRows({
999
1042
 
1000
1043
  // src/stores/brew-store.ts
1001
1044
  import { create as create4 } from "zustand";
1002
-
1003
- // src/lib/brew-api.ts
1004
- import { spawn } from "child_process";
1005
-
1006
- // src/lib/parsers/json-parser.ts
1007
- function safeParse(raw, context) {
1008
- try {
1009
- const result = JSON.parse(raw);
1010
- if (result === null || result === void 0) {
1011
- throw new Error(`${context} returned null or empty response`);
1012
- }
1013
- return result;
1014
- } catch (err) {
1015
- throw new Error(`Failed to parse ${context} JSON: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
1016
- }
1017
- }
1018
- function parseInstalledJson(raw) {
1019
- const data = safeParse(raw, "brew info --installed");
1020
- return {
1021
- formulae: Array.isArray(data.formulae) ? data.formulae : [],
1022
- casks: Array.isArray(data.casks) ? data.casks : []
1023
- };
1024
- }
1025
- function parseOutdatedJson(raw) {
1026
- const data = safeParse(raw, "brew outdated");
1027
- return {
1028
- formulae: Array.isArray(data.formulae) ? data.formulae : [],
1029
- casks: Array.isArray(data.casks) ? data.casks : []
1030
- };
1031
- }
1032
- function parseServicesJson(raw) {
1033
- const data = safeParse(raw, "brew services list");
1034
- if (!Array.isArray(data)) return [];
1035
- return data.map((s) => ({
1036
- name: s.name,
1037
- status: s.status ?? "none",
1038
- user: s.user ?? null,
1039
- file: s.file ?? null,
1040
- exit_code: s.exit_code ?? null
1041
- }));
1042
- }
1043
- function parseFormulaInfoJson(raw) {
1044
- const data = safeParse(raw, "brew info");
1045
- return data.formulae?.[0] ?? null;
1046
- }
1047
-
1048
- // src/lib/parsers/text-parser.ts
1049
- function parseSearchResults(raw) {
1050
- const lines = raw.split("\n").map((l) => l.trim()).filter(Boolean);
1051
- const formulae = [];
1052
- const casks = [];
1053
- let section = "formulae";
1054
- for (const line of lines) {
1055
- if (line.startsWith("==> Formulae")) {
1056
- section = "formulae";
1057
- continue;
1058
- }
1059
- if (line.startsWith("==> Casks")) {
1060
- section = "casks";
1061
- continue;
1062
- }
1063
- if (line.startsWith("==>")) continue;
1064
- if (section === "formulae") formulae.push(line);
1065
- else casks.push(line);
1066
- }
1067
- return { formulae, casks };
1068
- }
1069
- function parseDoctorOutput(raw) {
1070
- const cleaned = raw.trim();
1071
- if (cleaned.includes("Your system is ready to brew")) {
1072
- return { warnings: [], isClean: true };
1073
- }
1074
- const warnings = [];
1075
- let current = "";
1076
- for (const line of cleaned.split("\n")) {
1077
- if (line.startsWith("Warning:")) {
1078
- if (current) warnings.push(current.trim());
1079
- current = line;
1080
- } else if (current) {
1081
- current += "\n" + line;
1082
- }
1083
- }
1084
- if (current) warnings.push(current.trim());
1085
- return { warnings, isClean: false };
1086
- }
1087
- function parseBrewConfig(raw) {
1088
- const lines = raw.split("\n");
1089
- const get = (key) => {
1090
- const line = lines.find((l) => l.startsWith(key));
1091
- return line?.split(":").slice(1).join(":").trim() ?? "";
1092
- };
1093
- return {
1094
- HOMEBREW_VERSION: get("HOMEBREW_VERSION"),
1095
- HOMEBREW_PREFIX: get("HOMEBREW_PREFIX"),
1096
- coreUpdated: get("Core tap last commit") || get("Core tap JSON")
1097
- };
1098
- }
1099
- function parseLeavesOutput(raw) {
1100
- return raw.split("\n").map((l) => l.trim()).filter(Boolean);
1101
- }
1102
-
1103
- // src/lib/impact/impact-analyzer.ts
1104
- var HIGH_RISK_PACKAGES = /* @__PURE__ */ new Set([
1105
- "openssl",
1106
- "openssl@3",
1107
- "openssl@1.1",
1108
- "python",
1109
- "python@3",
1110
- "python@3.11",
1111
- "python@3.12",
1112
- "python@3.13",
1113
- "node",
1114
- "node@18",
1115
- "node@20",
1116
- "ruby",
1117
- "ruby@3",
1118
- "sqlite",
1119
- "sqlite3",
1120
- "libpq",
1121
- "postgresql",
1122
- "postgresql@16",
1123
- "glibc",
1124
- "gcc",
1125
- "llvm"
1126
- ]);
1127
- function isMajorVersionBump(from, to) {
1128
- const fromMajor = parseInt(from.split(".")[0] ?? "0", 10);
1129
- const toMajor = parseInt(to.split(".")[0] ?? "0", 10);
1130
- return !isNaN(fromMajor) && !isNaN(toMajor) && toMajor > fromMajor;
1131
- }
1132
- function calculateRisk(name, reverseDeps, fromVersion, toVersion) {
1133
- const reasons = [];
1134
- if (HIGH_RISK_PACKAGES.has(name)) {
1135
- reasons.push(t("impact_reason_critical_package"));
1136
- return { risk: "high", reasons };
1137
- }
1138
- if (reverseDeps.length > 10) {
1139
- reasons.push(t("impact_reason_many_deps", { count: reverseDeps.length }));
1140
- return { risk: "high", reasons };
1141
- }
1142
- let factorCount = 0;
1143
- if (reverseDeps.length >= 3) {
1144
- factorCount++;
1145
- reasons.push(t("impact_reason_many_deps", { count: reverseDeps.length }));
1146
- }
1147
- if (isMajorVersionBump(fromVersion, toVersion)) {
1148
- factorCount++;
1149
- reasons.push(t("impact_reason_major_bump"));
1150
- }
1151
- const risk = factorCount >= 2 ? "high" : factorCount === 1 ? "medium" : "low";
1152
- return { risk, reasons };
1153
- }
1154
- async function analyzeUpgradeImpact(packageName, fromVersion, toVersion, packageType) {
1155
- if (packageType === "cask") {
1156
- return {
1157
- packageName,
1158
- fromVersion,
1159
- toVersion,
1160
- packageType,
1161
- directDeps: [],
1162
- reverseDeps: [],
1163
- risk: "low",
1164
- riskReasons: []
1165
- };
1166
- }
1167
- let directDeps = [];
1168
- let reverseDeps = [];
1169
- try {
1170
- const depsOutput = await execBrew(["deps", "--1", packageName]);
1171
- directDeps = depsOutput.split("\n").filter((l) => l.trim() !== "");
1172
- } catch (err) {
1173
- logger.warn(`impact-analyzer: deps failed for ${packageName}: ${err instanceof Error ? err.message : String(err)}`);
1174
- }
1175
- try {
1176
- const usesOutput = await execBrew(["uses", "--installed", packageName]);
1177
- reverseDeps = usesOutput.split("\n").filter((l) => l.trim() !== "");
1178
- } catch (err) {
1179
- logger.warn(`impact-analyzer: uses failed for ${packageName}: ${err instanceof Error ? err.message : String(err)}`);
1180
- }
1181
- const { risk, reasons } = calculateRisk(packageName, reverseDeps, fromVersion, toVersion);
1182
- return {
1183
- packageName,
1184
- fromVersion,
1185
- toVersion,
1186
- packageType,
1187
- directDeps,
1188
- reverseDeps,
1189
- risk,
1190
- riskReasons: reasons
1191
- };
1192
- }
1193
-
1194
- // src/lib/brew-api.ts
1195
- var PKG_PATTERN = /^[\w@./+-]+$/;
1196
- function validatePackageName(name) {
1197
- if (!PKG_PATTERN.test(name)) throw new Error("Invalid package name: " + name);
1198
- }
1199
- async function brewUpdate() {
1200
- return new Promise((resolve, reject) => {
1201
- const proc = spawn("brew", ["update"], { stdio: "ignore" });
1202
- let settled = false;
1203
- const timeout = setTimeout(() => {
1204
- if (settled) return;
1205
- settled = true;
1206
- proc.kill("SIGTERM");
1207
- reject(new Error("brew update timed out after 120s"));
1208
- }, 12e4);
1209
- proc.on("close", (code) => {
1210
- if (settled) return;
1211
- settled = true;
1212
- clearTimeout(timeout);
1213
- if (code === 0) resolve();
1214
- else reject(new Error(`brew update exited with code ${code}`));
1215
- });
1216
- proc.on("error", (err) => {
1217
- if (settled) return;
1218
- settled = true;
1219
- clearTimeout(timeout);
1220
- reject(err);
1221
- });
1222
- });
1223
- }
1224
- async function getInstalled() {
1225
- const raw = await execBrew(["info", "--json=v2", "--installed"]);
1226
- return parseInstalledJson(raw);
1227
- }
1228
- async function getOutdated() {
1229
- const raw = await execBrew(["outdated", "--json=v2"]);
1230
- return parseOutdatedJson(raw);
1231
- }
1232
- async function getServices() {
1233
- const raw = await execBrew(["services", "list", "--json"]);
1234
- return parseServicesJson(raw);
1235
- }
1236
- async function getFormulaInfo(name) {
1237
- validatePackageName(name);
1238
- const raw = await execBrew(["info", "--json=v2", name]);
1239
- return parseFormulaInfoJson(raw);
1240
- }
1241
- async function getCaskInfo(name) {
1242
- validatePackageName(name);
1243
- try {
1244
- const raw = await execBrew(["info", "--json=v2", "--cask", name]);
1245
- const data = JSON.parse(raw);
1246
- return data.casks?.[0] ?? null;
1247
- } catch {
1248
- return null;
1249
- }
1250
- }
1251
- function formulaeFromCask(cask) {
1252
- return {
1253
- name: cask.token,
1254
- full_name: cask.full_token,
1255
- tap: "",
1256
- desc: cask.desc,
1257
- license: "",
1258
- homepage: cask.homepage,
1259
- versions: { stable: cask.version, head: null, bottle: false },
1260
- dependencies: [],
1261
- build_dependencies: [],
1262
- installed: cask.installed ? [{
1263
- version: cask.installed,
1264
- used_options: [],
1265
- built_as_bottle: false,
1266
- poured_from_bottle: false,
1267
- time: cask.installed_time ?? 0,
1268
- runtime_dependencies: [],
1269
- installed_as_dependency: false,
1270
- installed_on_request: true
1271
- }] : [],
1272
- linked_keg: null,
1273
- pinned: false,
1274
- outdated: cask.outdated,
1275
- deprecated: false,
1276
- keg_only: false,
1277
- caveats: null
1278
- };
1279
- }
1280
- async function search(term) {
1281
- const safeTerm = term.replace(/^-+/, "");
1282
- if (!safeTerm) return { formulae: [], casks: [] };
1283
- const raw = await execBrew(["search", safeTerm]);
1284
- return parseSearchResults(raw);
1285
- }
1286
- async function getDoctor() {
1287
- try {
1288
- const raw = await execBrew(["doctor"]);
1289
- return parseDoctorOutput(raw);
1290
- } catch (err) {
1291
- const msg = err instanceof Error ? err.message : String(err);
1292
- return parseDoctorOutput(msg);
1293
- }
1294
- }
1295
- async function getConfig() {
1296
- const raw = await execBrew(["config"]);
1297
- return parseBrewConfig(raw);
1298
- }
1299
- async function getLeaves() {
1300
- const raw = await execBrew(["leaves"]);
1301
- return parseLeavesOutput(raw);
1302
- }
1303
- async function uninstallPackage(name) {
1304
- validatePackageName(name);
1305
- return execBrew(["uninstall", name]);
1306
- }
1307
- async function serviceAction(name, action) {
1308
- validatePackageName(name);
1309
- return execBrew(["services", action, name]);
1310
- }
1311
- async function pinPackage(name) {
1312
- validatePackageName(name);
1313
- return execBrew(["pin", name]);
1314
- }
1315
- async function unpinPackage(name) {
1316
- validatePackageName(name);
1317
- return execBrew(["unpin", name]);
1318
- }
1319
- function formulaeToListItems(formulae) {
1320
- return formulae.map((f) => {
1321
- const installed = f.installed[0];
1322
- return {
1323
- name: f.name,
1324
- version: installed?.version ?? f.versions.stable,
1325
- desc: f.desc,
1326
- type: "formula",
1327
- outdated: f.outdated,
1328
- pinned: f.pinned,
1329
- kegOnly: f.keg_only,
1330
- installedAsDependency: installed?.installed_as_dependency ?? false,
1331
- installedTime: installed?.time ?? null
1332
- };
1333
- });
1334
- }
1335
- var impactCache = /* @__PURE__ */ new Map();
1336
- var IMPACT_CACHE_LIMIT = 64;
1337
- function impactKey(name, from, to, type) {
1338
- return `${type}::${name}::${from}::${to}`;
1339
- }
1340
- async function getUpgradeImpact(packageName, fromVersion, toVersion, packageType) {
1341
- validatePackageName(packageName);
1342
- const key = impactKey(packageName, fromVersion, toVersion, packageType);
1343
- const cached2 = impactCache.get(key);
1344
- if (cached2) return cached2;
1345
- const result = await analyzeUpgradeImpact(packageName, fromVersion, toVersion, packageType);
1346
- if (impactCache.size >= IMPACT_CACHE_LIMIT) {
1347
- const firstKey = impactCache.keys().next().value;
1348
- if (firstKey !== void 0) impactCache.delete(firstKey);
1349
- }
1350
- impactCache.set(key, result);
1351
- return result;
1352
- }
1353
- function casksToListItems(casks) {
1354
- return casks.map((c) => ({
1355
- name: c.token,
1356
- version: c.installed ?? c.version,
1357
- desc: c.desc,
1358
- type: "cask",
1359
- outdated: c.outdated,
1360
- pinned: false,
1361
- kegOnly: false,
1362
- installedAsDependency: false,
1363
- installedTime: c.installed_time ?? null
1364
- }));
1365
- }
1366
-
1367
- // src/stores/brew-store.ts
1368
1045
  var BREW_UPDATE_COOLDOWN_MS = 5 * 60 * 1e3;
1369
1046
  var fetchAllInFlight = null;
1370
1047
  var brewUpdateInFlight = null;
@@ -2143,7 +1820,7 @@ function DashboardView() {
2143
1820
  minRows: 2
2144
1821
  });
2145
1822
  const halfRows = Math.max(1, Math.floor(splitRows / 2));
2146
- useEffect5(() => {
1823
+ useEffect4(() => {
2147
1824
  fetchAll();
2148
1825
  }, []);
2149
1826
  useViewInput((input) => {
@@ -2256,14 +1933,14 @@ function DashboardView() {
2256
1933
  }
2257
1934
 
2258
1935
  // src/views/installed.tsx
2259
- import { useState as useState6, useMemo as useMemo3, useEffect as useEffect9, useRef as useRef3 } from "react";
1936
+ import { useState as useState5, useMemo as useMemo3, useEffect as useEffect8, useRef as useRef3 } from "react";
2260
1937
  import { Box as Box15, Text as Text18 } from "ink";
2261
1938
 
2262
1939
  // src/hooks/use-debounce.ts
2263
- import { useState as useState4, useEffect as useEffect6 } from "react";
1940
+ import { useState as useState3, useEffect as useEffect5 } from "react";
2264
1941
  function useDebounce(value, delayMs) {
2265
- const [debounced, setDebounced] = useState4(value);
2266
- useEffect6(() => {
1942
+ const [debounced, setDebounced] = useState3(value);
1943
+ useEffect5(() => {
2267
1944
  const timer = setTimeout(() => setDebounced(value), delayMs);
2268
1945
  return () => clearTimeout(timer);
2269
1946
  }, [value, delayMs]);
@@ -2271,7 +1948,7 @@ function useDebounce(value, delayMs) {
2271
1948
  }
2272
1949
 
2273
1950
  // src/hooks/use-brew-stream.ts
2274
- import { useState as useState5, useCallback, useRef as useRef2, useEffect as useEffect7 } from "react";
1951
+ import { useState as useState4, useCallback, useRef as useRef2, useEffect as useEffect6 } from "react";
2275
1952
  var MAX_LINES = 100;
2276
1953
  async function logToHistory(args, success, error) {
2277
1954
  const detected = detectAction(args);
@@ -2284,13 +1961,13 @@ async function logToHistory(args, success, error) {
2284
1961
  }
2285
1962
  }
2286
1963
  function useBrewStream() {
2287
- const [lines, setLines] = useState5([]);
2288
- const [isRunning, setIsRunning] = useState5(false);
2289
- const [error, setError2] = useState5(null);
1964
+ const [lines, setLines] = useState4([]);
1965
+ const [isRunning, setIsRunning] = useState4(false);
1966
+ const [error, setError2] = useState4(null);
2290
1967
  const cancelRef = useRef2(false);
2291
1968
  const generatorRef = useRef2(null);
2292
1969
  const mountedRef = useRef2(true);
2293
- useEffect7(() => {
1970
+ useEffect6(() => {
2294
1971
  mountedRef.current = true;
2295
1972
  return () => {
2296
1973
  mountedRef.current = false;
@@ -2330,7 +2007,7 @@ function useBrewStream() {
2330
2007
  }
2331
2008
  const MUTATING_COMMANDS = /* @__PURE__ */ new Set(["install", "uninstall", "upgrade", "pin", "unpin", "tap", "untap"]);
2332
2009
  if (!cancelRef.current && MUTATING_COMMANDS.has(args[0] ?? "")) {
2333
- void import("./snapshot-ZOJETCED.js").then(({ captureSnapshot: captureSnapshot2, saveSnapshot: saveSnapshot2 }) => {
2010
+ void import("./snapshot-RQ444U5L.js").then(({ captureSnapshot: captureSnapshot2, saveSnapshot: saveSnapshot2 }) => {
2334
2011
  captureSnapshot2().then((s) => saveSnapshot2(s)).catch((err) => logger.warn("snapshot: capture/save failed", { error: String(err) }));
2335
2012
  });
2336
2013
  }
@@ -2370,13 +2047,13 @@ function SearchInput({ defaultValue, onChange, placeholder, isActive = true }) {
2370
2047
  }
2371
2048
 
2372
2049
  // src/components/common/confirm-dialog.tsx
2373
- import { useEffect as useEffect8 } from "react";
2050
+ import { useEffect as useEffect7 } from "react";
2374
2051
  import { Box as Box11, Text as Text14, useInput as useInput3 } from "ink";
2375
2052
  import { jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
2376
2053
  function ConfirmDialog({ message, onConfirm, onCancel }) {
2377
2054
  const locale = useLocaleStore((s) => s.locale);
2378
2055
  const { openModal, closeModal } = useModalStore();
2379
- useEffect8(() => {
2056
+ useEffect7(() => {
2380
2057
  openModal();
2381
2058
  return () => {
2382
2059
  closeModal();
@@ -2467,11 +2144,11 @@ function InstalledView() {
2467
2144
  /* cursor + gap */
2468
2145
  ) : Math.max(12, Math.floor(columns * 0.35));
2469
2146
  const versionWidth = Math.max(8, Math.floor(columns * 0.15));
2470
- const [filter, setFilter] = useState6("");
2471
- const [cursor, setCursor] = useState6(0);
2472
- const [tab, setTab] = useState6("formulae");
2473
- const [isSearching, setIsSearching] = useState6(false);
2474
- const [confirmUninstall, setConfirmUninstall] = useState6(null);
2147
+ const [filter, setFilter] = useState5("");
2148
+ const [cursor, setCursor] = useState5(0);
2149
+ const [tab, setTab] = useState5("formulae");
2150
+ const [isSearching, setIsSearching] = useState5(false);
2151
+ const [confirmUninstall, setConfirmUninstall] = useState5(null);
2475
2152
  const debouncedFilter = useDebounce(filter, 200);
2476
2153
  const stream = useBrewStream();
2477
2154
  const listRows = useVisibleRows({
@@ -2484,10 +2161,10 @@ function InstalledView() {
2484
2161
  fallbackReservedRows: 14,
2485
2162
  minRows: 1
2486
2163
  });
2487
- useEffect9(() => {
2164
+ useEffect8(() => {
2488
2165
  fetchInstalled();
2489
2166
  }, []);
2490
- useEffect9(() => {
2167
+ useEffect8(() => {
2491
2168
  if (isSearching) {
2492
2169
  openModal();
2493
2170
  return () => {
@@ -2621,6 +2298,13 @@ function InstalledView() {
2621
2298
  setConfirmUninstall(null);
2622
2299
  void stream.run(["uninstall", name]).then(() => {
2623
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
+ });
2624
2308
  });
2625
2309
  },
2626
2310
  onCancel: () => setConfirmUninstall(null)
@@ -2673,23 +2357,24 @@ function InstalledView() {
2673
2357
  }
2674
2358
 
2675
2359
  // src/views/search.tsx
2676
- import { useState as useState7, useCallback as useCallback2, useEffect as useEffect10, useRef as useRef4 } from "react";
2360
+ import { useState as useState6, useCallback as useCallback2, useEffect as useEffect9, useRef as useRef4 } from "react";
2677
2361
  import { Box as Box16, Text as Text19 } from "ink";
2678
2362
  import { TextInput as TextInput2 } from "@inkjs/ui";
2679
2363
  import { jsx as jsx20, jsxs as jsxs17 } from "react/jsx-runtime";
2680
2364
  function SearchView() {
2681
- const [query, setQuery] = useState7("");
2682
- const [results, setResults] = useState7(null);
2683
- const [searching, setSearching] = useState7(false);
2684
- const [searchError, setSearchError] = useState7(null);
2685
- const [cursor, setCursor] = useState7(0);
2686
- const [confirmInstall, setConfirmInstall] = useState7(null);
2365
+ const [query, setQuery] = useState6("");
2366
+ const [results, setResults] = useState6(null);
2367
+ const [searching, setSearching] = useState6(false);
2368
+ const [searchError, setSearchError] = useState6(null);
2369
+ const [cursor, setCursor] = useState6(0);
2370
+ const [confirmInstall, setConfirmInstall] = useState6(null);
2687
2371
  const stream = useBrewStream();
2688
2372
  const { openModal, closeModal } = useModalStore();
2689
2373
  const navigate = useNavigationStore((s) => s.navigate);
2690
2374
  const selectPackage = useNavigationStore((s) => s.selectPackage);
2691
2375
  const fetchInstalled = useBrewStore((s) => s.fetchInstalled);
2692
2376
  const hasRefreshed = useRef4(false);
2377
+ const pendingInstallRef = useRef4(null);
2693
2378
  const resultRows = useVisibleRows({
2694
2379
  reservedRows: searchError ? 8 : 6,
2695
2380
  fallbackReservedRows: searchError ? 18 : 16,
@@ -2700,7 +2385,7 @@ function SearchView() {
2700
2385
  fallbackReservedRows: 14,
2701
2386
  minRows: 1
2702
2387
  });
2703
- useEffect10(() => {
2388
+ useEffect9(() => {
2704
2389
  if (results !== null) {
2705
2390
  openModal();
2706
2391
  return () => {
@@ -2728,10 +2413,21 @@ function SearchView() {
2728
2413
  setSearching(false);
2729
2414
  }
2730
2415
  }, []);
2731
- useEffect10(() => {
2416
+ useEffect9(() => {
2732
2417
  if (!stream.isRunning && !stream.error && stream.lines.length > 0 && !hasRefreshed.current) {
2733
2418
  hasRefreshed.current = true;
2734
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
+ }
2735
2431
  }
2736
2432
  }, [stream.isRunning, stream.error]);
2737
2433
  const allResults = results ? [
@@ -2743,7 +2439,7 @@ function SearchView() {
2743
2439
  Math.max(0, allResults.length - resultRows)
2744
2440
  );
2745
2441
  const visibleResults = allResults.slice(start, start + resultRows);
2746
- useEffect10(() => {
2442
+ useEffect9(() => {
2747
2443
  setCursor((current) => Math.min(current, Math.max(0, allResults.length - 1)));
2748
2444
  }, [allResults.length]);
2749
2445
  useViewInput((input, key) => {
@@ -2842,6 +2538,7 @@ function SearchView() {
2842
2538
  onConfirm: () => {
2843
2539
  const name = confirmInstall;
2844
2540
  hasRefreshed.current = false;
2541
+ pendingInstallRef.current = name;
2845
2542
  setConfirmInstall(null);
2846
2543
  void stream.run(["install", name]);
2847
2544
  },
@@ -2885,7 +2582,7 @@ function SearchView() {
2885
2582
  }
2886
2583
 
2887
2584
  // src/views/outdated.tsx
2888
- import { useEffect as useEffect11, useMemo as useMemo4, useRef as useRef5, useState as useState8 } from "react";
2585
+ import { useEffect as useEffect10, useMemo as useMemo4, useRef as useRef5, useState as useState7 } from "react";
2889
2586
  import { Box as Box17, Text as Text20 } from "ink";
2890
2587
  import { jsx as jsx21, jsxs as jsxs18 } from "react/jsx-runtime";
2891
2588
  function ImpactPanel({ impact }) {
@@ -2910,14 +2607,17 @@ function ImpactPanel({ impact }) {
2910
2607
  ] });
2911
2608
  }
2912
2609
  function OutdatedView() {
2913
- 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);
2914
2614
  const stream = useBrewStream();
2915
- const [cursor, setCursor] = useState8(0);
2916
- const [confirmAction, setConfirmAction] = useState8(null);
2615
+ const [cursor, setCursor] = useState7(0);
2616
+ const [confirmAction, setConfirmAction] = useState7(null);
2917
2617
  const hasRefreshed = useRef5(false);
2918
2618
  const pendingUpgradeRef = useRef5(null);
2919
- const [impact, setImpact] = useState8(null);
2920
- const [impactLoading, setImpactLoading] = useState8(false);
2619
+ const [impact, setImpact] = useState7(null);
2620
+ const [impactLoading, setImpactLoading] = useState7(false);
2921
2621
  const listRows = useVisibleRows({
2922
2622
  reservedRows: impact || impactLoading ? 11 : 7,
2923
2623
  fallbackReservedRows: impact || impactLoading ? 18 : 14,
@@ -2928,10 +2628,10 @@ function OutdatedView() {
2928
2628
  fallbackReservedRows: 14,
2929
2629
  minRows: 1
2930
2630
  });
2931
- useEffect11(() => {
2631
+ useEffect10(() => {
2932
2632
  fetchOutdated();
2933
2633
  }, []);
2934
- useEffect11(() => {
2634
+ useEffect10(() => {
2935
2635
  if (!stream.isRunning && !stream.error && stream.lines.length > 0 && !hasRefreshed.current) {
2936
2636
  hasRefreshed.current = true;
2937
2637
  void fetchOutdated().then(() => {
@@ -2958,7 +2658,7 @@ function OutdatedView() {
2958
2658
  [outdated.formulae, outdated.casks]
2959
2659
  );
2960
2660
  const debouncedCursor = useDebounce(cursor, 150);
2961
- useEffect11(() => {
2661
+ useEffect10(() => {
2962
2662
  const pkg = allOutdated[debouncedCursor];
2963
2663
  if (!pkg || stream.isRunning) {
2964
2664
  setImpact(null);
@@ -3115,7 +2815,7 @@ ${t("outdated_upgradeAllList", { list: allOutdated.map((p) => p.name).join(", ")
3115
2815
  }
3116
2816
 
3117
2817
  // src/views/package-info.tsx
3118
- import { useEffect as useEffect12, useRef as useRef6, useState as useState9 } from "react";
2818
+ import { useEffect as useEffect11, useRef as useRef6, useState as useState8 } from "react";
3119
2819
  import { Box as Box18, Text as Text21 } from "ink";
3120
2820
  import { Fragment as Fragment4, jsx as jsx22, jsxs as jsxs19 } from "react/jsx-runtime";
3121
2821
  var ACTION_PROGRESS_KEYS = {
@@ -3131,21 +2831,21 @@ var ACTION_CONFIRM_KEYS = {
3131
2831
  function PackageInfoView() {
3132
2832
  const packageName = useNavigationStore((s) => s.selectedPackage);
3133
2833
  const packageType = useNavigationStore((s) => s.selectedPackageType);
3134
- const [formula, setFormula] = useState9(null);
3135
- const [loading, setLoading2] = useState9(true);
3136
- const [error, setError2] = useState9(null);
3137
- const [confirmAction, setConfirmAction] = useState9(null);
2834
+ const [formula, setFormula] = useState8(null);
2835
+ const [loading, setLoading2] = useState8(true);
2836
+ const [error, setError2] = useState8(null);
2837
+ const [confirmAction, setConfirmAction] = useState8(null);
3138
2838
  const activeActionRef = useRef6("install");
3139
2839
  const mountedRef = useRef6(true);
3140
2840
  const hasRefreshed = useRef6(false);
3141
2841
  const stream = useBrewStream();
3142
- useEffect12(() => {
2842
+ useEffect11(() => {
3143
2843
  mountedRef.current = true;
3144
2844
  return () => {
3145
2845
  mountedRef.current = false;
3146
2846
  };
3147
2847
  }, []);
3148
- useEffect12(() => {
2848
+ useEffect11(() => {
3149
2849
  if (!packageName) return;
3150
2850
  setLoading2(true);
3151
2851
  const fetchInfo = async () => {
@@ -3170,7 +2870,7 @@ function PackageInfoView() {
3170
2870
  }
3171
2871
  });
3172
2872
  }, [packageName, packageType]);
3173
- useEffect12(() => {
2873
+ useEffect11(() => {
3174
2874
  if (!stream.isRunning && !stream.error && stream.lines.length > 0 && !hasRefreshed.current && packageName) {
3175
2875
  hasRefreshed.current = true;
3176
2876
  const refreshFn = packageType === "cask" ? getCaskInfo(packageName).then((c) => c ? { ...c, installed: c.installed ? [{ version: c.installed }] : [] } : null) : getFormulaInfo(packageName);
@@ -3330,7 +3030,7 @@ function PackageInfoView() {
3330
3030
  }
3331
3031
 
3332
3032
  // src/views/services.tsx
3333
- import { useEffect as useEffect13, useRef as useRef7, useState as useState10 } from "react";
3033
+ import { useEffect as useEffect12, useRef as useRef7, useState as useState9 } from "react";
3334
3034
  import { Box as Box19, Text as Text22 } from "ink";
3335
3035
  import { jsx as jsx23, jsxs as jsxs20 } from "react/jsx-runtime";
3336
3036
  var STATUS_VARIANTS = {
@@ -3346,11 +3046,15 @@ function humaniseServiceError(message) {
3346
3046
  return message;
3347
3047
  }
3348
3048
  function ServicesView() {
3349
- const { services, loading, errors, fetchServices, serviceAction: serviceAction2 } = useBrewStore();
3350
- const [cursor, setCursor] = useState10(0);
3351
- const [actionInProgress, setActionInProgress] = useState10(false);
3352
- const [confirmAction, setConfirmAction] = useState10(null);
3353
- const [lastError, setLastError] = useState10(null);
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);
3054
+ const [cursor, setCursor] = useState9(0);
3055
+ const [actionInProgress, setActionInProgress] = useState9(false);
3056
+ const [confirmAction, setConfirmAction] = useState9(null);
3057
+ const [lastError, setLastError] = useState9(null);
3354
3058
  const containerRef = useRef7(null);
3355
3059
  const { width: containerWidth } = useContainerSize(containerRef);
3356
3060
  const cols = containerWidth > 0 ? containerWidth : 80;
@@ -3366,7 +3070,7 @@ function ServicesView() {
3366
3070
  fallbackReservedRows: lastError || actionInProgress ? 16 : 14,
3367
3071
  minRows: 1
3368
3072
  });
3369
- useEffect13(() => {
3073
+ useEffect12(() => {
3370
3074
  fetchServices();
3371
3075
  }, []);
3372
3076
  useViewInput((input, key) => {
@@ -3477,25 +3181,29 @@ function ServicesView() {
3477
3181
  }
3478
3182
 
3479
3183
  // src/views/doctor.tsx
3480
- import { useEffect as useEffect14, useRef as useRef8, useState as useState11 } from "react";
3184
+ import { useEffect as useEffect13, useRef as useRef8, useState as useState10 } from "react";
3481
3185
  import { Box as Box20, Text as Text23 } from "ink";
3482
3186
  import { jsx as jsx24, jsxs as jsxs21 } from "react/jsx-runtime";
3483
3187
  function DoctorView() {
3484
- const { doctorWarnings, doctorClean, loading, errors, fetchDoctor } = useBrewStore();
3485
- const [cursor, setCursor] = useState11(0);
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);
3193
+ const [cursor, setCursor] = useState10(0);
3486
3194
  const visibleWarnings = useVisibleRows({
3487
3195
  reservedRows: 6,
3488
3196
  fallbackReservedRows: 14,
3489
3197
  minRows: 1
3490
3198
  });
3491
3199
  const mountedRef = useRef8(true);
3492
- useEffect14(() => {
3200
+ useEffect13(() => {
3493
3201
  mountedRef.current = true;
3494
3202
  return () => {
3495
3203
  mountedRef.current = false;
3496
3204
  };
3497
3205
  }, []);
3498
- useEffect14(() => {
3206
+ useEffect13(() => {
3499
3207
  fetchDoctor();
3500
3208
  }, []);
3501
3209
  useViewInput((input, key) => {
@@ -3533,7 +3241,7 @@ function DoctorView() {
3533
3241
  }
3534
3242
 
3535
3243
  // src/views/profiles.tsx
3536
- import { useEffect as useEffect15, useRef as useRef9, useState as useState12 } from "react";
3244
+ import { useEffect as useEffect14, useRef as useRef9, useState as useState11 } from "react";
3537
3245
  import { Box as Box25 } from "ink";
3538
3246
 
3539
3247
  // src/stores/profile-store.ts
@@ -3656,7 +3364,7 @@ async function updateProfile(isPro, oldName, newName, newDescription) {
3656
3364
  await saveProfile(isPro, updated);
3657
3365
  }
3658
3366
  var TAP_PATTERN = /^[a-z0-9][-a-z0-9]*\/[a-z0-9][-a-z0-9]*$/;
3659
- var PKG_PATTERN2 = /^[a-z0-9][-a-z0-9_.@+]*$/;
3367
+ var PKG_PATTERN = /^[a-z0-9][-a-z0-9_.@+]*$/;
3660
3368
  async function* importProfile(isPro, profile) {
3661
3369
  proCheck(isPro);
3662
3370
  const installed = await getInstalled();
@@ -3675,7 +3383,7 @@ async function* importProfile(isPro, profile) {
3675
3383
  }
3676
3384
  const missingFormulae = profile.formulae.filter((f) => !installedFormulae.has(f));
3677
3385
  for (const name of missingFormulae) {
3678
- if (!PKG_PATTERN2.test(name)) {
3386
+ if (!PKG_PATTERN.test(name)) {
3679
3387
  yield `Skipping invalid formula name: ${name}`;
3680
3388
  continue;
3681
3389
  }
@@ -3686,7 +3394,7 @@ async function* importProfile(isPro, profile) {
3686
3394
  }
3687
3395
  const missingCasks = profile.casks.filter((c) => !installedCasks.has(c));
3688
3396
  for (const name of missingCasks) {
3689
- if (!PKG_PATTERN2.test(name)) {
3397
+ if (!PKG_PATTERN.test(name)) {
3690
3398
  yield `Skipping invalid cask name: ${name}`;
3691
3399
  continue;
3692
3400
  }
@@ -3905,23 +3613,23 @@ function ProfileEditDesc({ name, defaultDesc, loadError, onSubmit }) {
3905
3613
  import { jsx as jsx29, jsxs as jsxs26 } from "react/jsx-runtime";
3906
3614
  function ProfilesView() {
3907
3615
  const { profileNames, selectedProfile, loading, loadError, fetchProfiles, loadProfile: loadProfile2, exportCurrent, deleteProfile: deleteProfile2, updateProfile: updateProfile2 } = useProfileStore();
3908
- const [cursor, setCursor] = useState12(0);
3909
- const [mode, setMode] = useState12("list");
3910
- const [newName, setNewName] = useState12("");
3911
- const [confirmDelete, setConfirmDelete] = useState12(false);
3912
- const [editName, setEditName] = useState12("");
3913
- const [editDesc, setEditDesc] = useState12("");
3914
- const [importLines, setImportLines] = useState12([]);
3915
- const [importRunning, setImportRunning] = useState12(false);
3916
- const [importHadError, setImportHadError] = useState12(false);
3917
- const [importProfile2, setImportProfile] = useState12(null);
3616
+ const [cursor, setCursor] = useState11(0);
3617
+ const [mode, setMode] = useState11("list");
3618
+ const [newName, setNewName] = useState11("");
3619
+ const [confirmDelete, setConfirmDelete] = useState11(false);
3620
+ const [editName, setEditName] = useState11("");
3621
+ const [editDesc, setEditDesc] = useState11("");
3622
+ const [importLines, setImportLines] = useState11([]);
3623
+ const [importRunning, setImportRunning] = useState11(false);
3624
+ const [importHadError, setImportHadError] = useState11(false);
3625
+ const [importProfile2, setImportProfile] = useState11(null);
3918
3626
  const { openModal, closeModal } = useModalStore();
3919
3627
  const importGenRef = useRef9(null);
3920
3628
  const mountedRef = useRef9(true);
3921
- useEffect15(() => {
3629
+ useEffect14(() => {
3922
3630
  fetchProfiles();
3923
3631
  }, []);
3924
- useEffect15(() => {
3632
+ useEffect14(() => {
3925
3633
  mountedRef.current = true;
3926
3634
  return () => {
3927
3635
  mountedRef.current = false;
@@ -3929,7 +3637,7 @@ function ProfilesView() {
3929
3637
  importGenRef.current = null;
3930
3638
  };
3931
3639
  }, []);
3932
- useEffect15(() => {
3640
+ useEffect14(() => {
3933
3641
  if (mode !== "list") {
3934
3642
  openModal();
3935
3643
  return () => {
@@ -4105,7 +3813,7 @@ function ProfilesView() {
4105
3813
  }
4106
3814
 
4107
3815
  // src/views/smart-cleanup.tsx
4108
- import { useEffect as useEffect16, useRef as useRef10, useState as useState13 } from "react";
3816
+ import { useEffect as useEffect15, useRef as useRef10, useState as useState12 } from "react";
4109
3817
  import { Box as Box26, Text as Text28 } from "ink";
4110
3818
 
4111
3819
  // src/stores/cleanup-store.ts
@@ -4229,10 +3937,10 @@ var useCleanupStore = create10((set, get) => ({
4229
3937
  import { jsx as jsx30, jsxs as jsxs27 } from "react/jsx-runtime";
4230
3938
  function SmartCleanupView() {
4231
3939
  const { summary, selected, loading, error, analyze, toggleSelect, selectAll } = useCleanupStore();
4232
- const [cursor, setCursor] = useState13(0);
4233
- const [confirmClean, setConfirmClean] = useState13(false);
4234
- const [confirmForce, setConfirmForce] = useState13(false);
4235
- const [failedNames, setFailedNames] = useState13([]);
3940
+ const [cursor, setCursor] = useState12(0);
3941
+ const [confirmClean, setConfirmClean] = useState12(false);
3942
+ const [confirmForce, setConfirmForce] = useState12(false);
3943
+ const [failedNames, setFailedNames] = useState12([]);
4236
3944
  const stream = useBrewStream();
4237
3945
  const hasRefreshed = useRef10(false);
4238
3946
  const listRows = useVisibleRows({
@@ -4240,10 +3948,10 @@ function SmartCleanupView() {
4240
3948
  fallbackReservedRows: confirmClean || confirmForce ? 18 : 14,
4241
3949
  minRows: 1
4242
3950
  });
4243
- useEffect16(() => {
3951
+ useEffect15(() => {
4244
3952
  analyze();
4245
3953
  }, []);
4246
- useEffect16(() => {
3954
+ useEffect15(() => {
4247
3955
  if (!stream.isRunning && !stream.error && stream.lines.length > 0 && !hasRefreshed.current) {
4248
3956
  hasRefreshed.current = true;
4249
3957
  void analyze();
@@ -4379,7 +4087,7 @@ function SmartCleanupView() {
4379
4087
  }
4380
4088
 
4381
4089
  // src/views/history.tsx
4382
- import { useEffect as useEffect17, useState as useState14, useMemo as useMemo5 } from "react";
4090
+ import { useEffect as useEffect16, useState as useState13, useMemo as useMemo5 } from "react";
4383
4091
  import { Box as Box27, Text as Text29 } from "ink";
4384
4092
 
4385
4093
  // src/stores/history-store.ts
@@ -4432,12 +4140,12 @@ var ACTION_LABEL_KEYS = {
4432
4140
  var FILTERS = ["all", "install", "uninstall", "upgrade", "upgrade-all"];
4433
4141
  function HistoryView() {
4434
4142
  const { entries, loading, error, fetchHistory, clearHistory: clearHistory2 } = useHistoryStore();
4435
- const [cursor, setCursor] = useState14(0);
4436
- const [filter, setFilter] = useState14("all");
4437
- const [searchQuery, setSearchQuery] = useState14("");
4438
- const [isSearching, setIsSearching] = useState14(false);
4439
- const [confirmClear, setConfirmClear] = useState14(false);
4440
- const [confirmReplay, setConfirmReplay] = useState14(null);
4143
+ const [cursor, setCursor] = useState13(0);
4144
+ const [filter, setFilter] = useState13("all");
4145
+ const [searchQuery, setSearchQuery] = useState13("");
4146
+ const [isSearching, setIsSearching] = useState13(false);
4147
+ const [confirmClear, setConfirmClear] = useState13(false);
4148
+ const [confirmReplay, setConfirmReplay] = useState13(null);
4441
4149
  const stream = useBrewStream();
4442
4150
  const debouncedQuery = useDebounce(searchQuery, 200);
4443
4151
  const { openModal, closeModal } = useModalStore();
@@ -4451,10 +4159,10 @@ function HistoryView() {
4451
4159
  fallbackReservedRows: 16,
4452
4160
  minRows: 1
4453
4161
  });
4454
- useEffect17(() => {
4162
+ useEffect16(() => {
4455
4163
  fetchHistory();
4456
4164
  }, []);
4457
- useEffect17(() => {
4165
+ useEffect16(() => {
4458
4166
  if (isSearching) {
4459
4167
  openModal();
4460
4168
  return () => {
@@ -4595,7 +4303,7 @@ function HistoryView() {
4595
4303
  }
4596
4304
 
4597
4305
  // src/views/security-audit.tsx
4598
- import { useEffect as useEffect18, useState as useState15 } from "react";
4306
+ import { useEffect as useEffect17, useState as useState14 } from "react";
4599
4307
  import { Box as Box28, Text as Text30 } from "ink";
4600
4308
  import { jsx as jsx32, jsxs as jsxs29 } from "react/jsx-runtime";
4601
4309
  var SEVERITY_COLORS = {
@@ -4618,11 +4326,11 @@ function isNetworkError(msg) {
4618
4326
  function SecurityAuditView() {
4619
4327
  const { summary, loading, error, scan, cachedAt } = useSecurityStore();
4620
4328
  const navigate = useNavigationStore((s) => s.navigate);
4621
- const [cursor, setCursor] = useState15(0);
4622
- const [expandedPkg, setExpandedPkg] = useState15(null);
4623
- const [confirmUpgrade, setConfirmUpgrade] = useState15(null);
4329
+ const [cursor, setCursor] = useState14(0);
4330
+ const [expandedPkg, setExpandedPkg] = useState14(null);
4331
+ const [confirmUpgrade, setConfirmUpgrade] = useState14(null);
4624
4332
  const stream = useBrewStream();
4625
- useEffect18(() => {
4333
+ useEffect17(() => {
4626
4334
  scan();
4627
4335
  }, []);
4628
4336
  const results = summary?.results ?? [];
@@ -4727,7 +4435,7 @@ function SecurityAuditView() {
4727
4435
  }
4728
4436
 
4729
4437
  // src/views/account.tsx
4730
- import { useState as useState16 } from "react";
4438
+ import { useState as useState15 } from "react";
4731
4439
  import { Box as Box29, Text as Text31 } from "ink";
4732
4440
  import { TextInput as TextInput5 } from "@inkjs/ui";
4733
4441
 
@@ -4754,7 +4462,7 @@ async function redeemPromoCode(code) {
4754
4462
  let serverType;
4755
4463
  const idempotencyKey = randomUUID();
4756
4464
  try {
4757
- const res = await fetchWithTimeout(`${PROMO_API_URL}/redeem`, {
4465
+ const res = await fetchWithRetry(`${PROMO_API_URL}/redeem`, {
4758
4466
  method: "POST",
4759
4467
  headers: {
4760
4468
  "Content-Type": "application/json",
@@ -4809,13 +4517,13 @@ async function redeemPromoCode(code) {
4809
4517
  import { Fragment as Fragment5, jsx as jsx33, jsxs as jsxs30 } from "react/jsx-runtime";
4810
4518
  function AccountView() {
4811
4519
  const { status, license, deactivate: deactivate2, revalidate: revalidate2, degradation } = useLicenseStore();
4812
- const [confirmDeactivate, setConfirmDeactivate] = useState16(false);
4813
- const [deactivating, setDeactivating] = useState16(false);
4814
- const [deactivateError, setDeactivateError] = useState16(null);
4815
- const [promoMode, setPromoMode] = useState16(false);
4816
- const [promoLoading, setPromoLoading] = useState16(false);
4817
- const [promoResult, setPromoResult] = useState16(null);
4818
- const [revalidating, setRevalidating] = useState16(false);
4520
+ const [confirmDeactivate, setConfirmDeactivate] = useState15(false);
4521
+ const [deactivating, setDeactivating] = useState15(false);
4522
+ const [deactivateError, setDeactivateError] = useState15(null);
4523
+ const [promoMode, setPromoMode] = useState15(false);
4524
+ const [promoLoading, setPromoLoading] = useState15(false);
4525
+ const [promoResult, setPromoResult] = useState15(null);
4526
+ const [revalidating, setRevalidating] = useState15(false);
4819
4527
  useViewInput((input, key) => {
4820
4528
  if (confirmDeactivate || deactivating || promoMode || revalidating) {
4821
4529
  if (key.escape && promoMode) {
@@ -4824,7 +4532,7 @@ function AccountView() {
4824
4532
  }
4825
4533
  return;
4826
4534
  }
4827
- if ((input === "d" || input === "2") && status === "pro") {
4535
+ if ((input === "d" || input === "2") && (status === "pro" || status === "team")) {
4828
4536
  setConfirmDeactivate(true);
4829
4537
  }
4830
4538
  if (input === "p" || input === "1") {
@@ -4868,6 +4576,7 @@ function AccountView() {
4868
4576
  /* @__PURE__ */ jsxs30(Box29, { gap: SPACING.xs, children: [
4869
4577
  /* @__PURE__ */ jsx33(Text31, { color: COLORS.muted, children: t("account_statusLabel") }),
4870
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") }),
4871
4580
  status === "free" && /* @__PURE__ */ jsx33(Text31, { color: COLORS.muted, children: t("account_free") }),
4872
4581
  status === "expired" && /* @__PURE__ */ jsx33(Text31, { color: COLORS.error, children: t("account_expired") })
4873
4582
  ] }),
@@ -4959,13 +4668,13 @@ function AccountView() {
4959
4668
  status === "pro" || status === "team" || status === "expired" ? `v ${t("hint_revalidate")} ` : "",
4960
4669
  revalidating ? t("account_revalidating") : "",
4961
4670
  " ",
4962
- t("app_version", { version: "1.2.0" })
4671
+ t("app_version", { version: "1.2.2" })
4963
4672
  ] }) })
4964
4673
  ] });
4965
4674
  }
4966
4675
 
4967
4676
  // src/views/rollback.tsx
4968
- import { useCallback as useCallback3, useEffect as useEffect19, useRef as useRef11, useState as useState17 } from "react";
4677
+ import { useCallback as useCallback3, useEffect as useEffect18, useRef as useRef11, useState as useState16 } from "react";
4969
4678
  import { Box as Box30, Text as Text32 } from "ink";
4970
4679
 
4971
4680
  // src/stores/rollback-store.ts
@@ -5001,10 +4710,10 @@ async function detectStrategy(name, targetVersion, packageType) {
5001
4710
  }
5002
4711
  return { strategy: "pin-only" };
5003
4712
  }
5004
- async function buildRollbackPlan(snapshot, isPro) {
4713
+ async function buildRollbackPlan(snapshot2, isPro) {
5005
4714
  if (!isPro) throw new Error("Pro license required");
5006
4715
  const current = await captureSnapshot();
5007
- const diff = diffSnapshots(snapshot, current);
4716
+ const diff = diffSnapshots(snapshot2, current);
5008
4717
  const actions = [];
5009
4718
  const warnings = [];
5010
4719
  for (const entry of diff.upgraded) {
@@ -5070,8 +4779,8 @@ async function buildRollbackPlan(snapshot, isPro) {
5070
4779
  warnings.push(`${caskPinCount} cask(s) will be pinned only (version restoration not supported)`);
5071
4780
  }
5072
4781
  const canExecute = actions.some((a) => a.strategy !== "unavailable");
5073
- const snapshotLabel = snapshot.label ?? "Auto";
5074
- const snapshotDate = new Date(snapshot.capturedAt).toLocaleString();
4782
+ const snapshotLabel = snapshot2.label ?? "Auto";
4783
+ const snapshotDate = new Date(snapshot2.capturedAt).toLocaleString();
5075
4784
  logger.debug("Built rollback plan", { actionCount: actions.length, canExecute });
5076
4785
  return {
5077
4786
  snapshotLabel,
@@ -5092,6 +4801,13 @@ async function* executeRollbackPlan(plan, isPro) {
5092
4801
  yield `[skip] ${action.packageName}: removal skipped for safety \u2014 remove manually if needed`;
5093
4802
  continue;
5094
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
+ }
5095
4811
  if (action.action === "install") {
5096
4812
  if (action.strategy === "pin-only") {
5097
4813
  yield `[warn] ${action.packageName}: cannot install specific version \u2014 skipping`;
@@ -5159,14 +4875,14 @@ var useRollbackStore = create12((set) => ({
5159
4875
  set({ error: err instanceof Error ? err.message : String(err), loading: false });
5160
4876
  }
5161
4877
  },
5162
- selectSnapshot: async (snapshot, isPro) => {
5163
- if (!snapshot) {
4878
+ selectSnapshot: async (snapshot2, isPro) => {
4879
+ if (!snapshot2) {
5164
4880
  set({ selectedSnapshot: null, plan: null, planError: null });
5165
4881
  return;
5166
4882
  }
5167
- set({ selectedSnapshot: snapshot, plan: null, planLoading: true, planError: null });
4883
+ set({ selectedSnapshot: snapshot2, plan: null, planLoading: true, planError: null });
5168
4884
  try {
5169
- const plan = await buildRollbackPlan(snapshot, isPro);
4885
+ const plan = await buildRollbackPlan(snapshot2, isPro);
5170
4886
  set({ plan, planLoading: false });
5171
4887
  } catch (err) {
5172
4888
  set({ planError: err instanceof Error ? err.message : String(err), planLoading: false });
@@ -5271,11 +4987,11 @@ function PlanView({ plan }) {
5271
4987
  function RollbackView() {
5272
4988
  const isPro = useLicenseStore((s) => s.isPro);
5273
4989
  const { snapshots, loading, error, plan, planLoading, planError, fetchSnapshots, selectSnapshot, clearPlan } = useRollbackStore();
5274
- const [cursor, setCursor] = useState17(0);
5275
- const [phase, setPhase] = useState17("list");
5276
- const [streamLines, setStreamLines] = useState17([]);
5277
- const [streamRunning, setStreamRunning] = useState17(false);
5278
- const [streamError, setStreamError] = useState17(null);
4990
+ const [cursor, setCursor] = useState16(0);
4991
+ const [phase, setPhase] = useState16("list");
4992
+ const [streamLines, setStreamLines] = useState16([]);
4993
+ const [streamRunning, setStreamRunning] = useState16(false);
4994
+ const [streamError, setStreamError] = useState16(null);
5279
4995
  const generatorRef = useRef11(null);
5280
4996
  const mountedRef = useRef11(true);
5281
4997
  const snapshotRows = useVisibleRows({
@@ -5283,14 +4999,14 @@ function RollbackView() {
5283
4999
  fallbackReservedRows: 14,
5284
5000
  minRows: 1
5285
5001
  });
5286
- useEffect19(() => {
5002
+ useEffect18(() => {
5287
5003
  mountedRef.current = true;
5288
5004
  return () => {
5289
5005
  mountedRef.current = false;
5290
5006
  void generatorRef.current?.return(void 0);
5291
5007
  };
5292
5008
  }, []);
5293
- useEffect19(() => {
5009
+ useEffect18(() => {
5294
5010
  void fetchSnapshots(isPro());
5295
5011
  }, []);
5296
5012
  const runRollback = useCallback3(async (p) => {
@@ -5353,7 +5069,10 @@ function RollbackView() {
5353
5069
  if (loading) return /* @__PURE__ */ jsx34(Loading, { message: t("rollback_select_snapshot") });
5354
5070
  if (error) return /* @__PURE__ */ jsx34(ErrorMessage, { message: error });
5355
5071
  if (phase === "executing") {
5356
- 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
+ ] });
5357
5076
  }
5358
5077
  if (phase === "result") {
5359
5078
  return /* @__PURE__ */ jsxs31(Box30, { flexDirection: "column", marginTop: SPACING.xs, children: [
@@ -5427,7 +5146,7 @@ function RollbackView() {
5427
5146
  }
5428
5147
 
5429
5148
  // src/views/brewfile.tsx
5430
- import { useCallback as useCallback4, useEffect as useEffect20, useRef as useRef12, useState as useState18 } from "react";
5149
+ import { useCallback as useCallback4, useEffect as useEffect19, useRef as useRef12, useState as useState17 } from "react";
5431
5150
  import { Box as Box31, Text as Text33 } from "ink";
5432
5151
  import { TextInput as TextInput6 } from "@inkjs/ui";
5433
5152
  import { jsx as jsx35, jsxs as jsxs32 } from "react/jsx-runtime";
@@ -5473,14 +5192,14 @@ function DriftSummary({ drift }) {
5473
5192
  function BrewfileView() {
5474
5193
  const isPro = useLicenseStore((s) => s.isPro);
5475
5194
  const { schema, drift, loading, driftLoading, error, load, createFromCurrent } = useBrewfileStore();
5476
- const [phase, setPhase] = useState18("overview");
5477
- const [streamLines, setStreamLines] = useState18([]);
5478
- const [streamRunning, setStreamRunning] = useState18(false);
5479
- const [streamError, setStreamError] = useState18(null);
5480
- const [resultMessage, setResultMessage] = useState18("");
5195
+ const [phase, setPhase] = useState17("overview");
5196
+ const [streamLines, setStreamLines] = useState17([]);
5197
+ const [streamRunning, setStreamRunning] = useState17(false);
5198
+ const [streamError, setStreamError] = useState17(null);
5199
+ const [resultMessage, setResultMessage] = useState17("");
5481
5200
  const generatorRef = useRef12(null);
5482
5201
  const mountedRef = useRef12(true);
5483
- useEffect20(() => {
5202
+ useEffect19(() => {
5484
5203
  mountedRef.current = true;
5485
5204
  void load();
5486
5205
  return () => {
@@ -5656,7 +5375,7 @@ function BrewfileView() {
5656
5375
  }
5657
5376
 
5658
5377
  // src/views/sync.tsx
5659
- import { useCallback as useCallback5, useEffect as useEffect21, useState as useState19 } from "react";
5378
+ import { useCallback as useCallback5, useEffect as useEffect20, useState as useState18 } from "react";
5660
5379
  import { Box as Box32, Text as Text34 } from "ink";
5661
5380
  import { Fragment as Fragment6, jsx as jsx36, jsxs as jsxs33 } from "react/jsx-runtime";
5662
5381
  function OverviewSection({
@@ -5782,14 +5501,14 @@ function SyncView() {
5782
5501
  const isPro = useLicenseStore((s) => s.isPro);
5783
5502
  const navigate = useNavigationStore((s) => s.navigate);
5784
5503
  const { config, lastResult, conflicts, loading, error, initialize, syncNow, resolveConflicts } = useSyncStore();
5785
- const [phase, setPhase] = useState19("overview");
5786
- const [syncError, setSyncError] = useState19(null);
5787
- const [conflictEntries, setConflictEntries] = useState19([]);
5788
- const [cursor, setCursor] = useState19(0);
5789
- useEffect21(() => {
5504
+ const [phase, setPhase] = useState18("overview");
5505
+ const [syncError, setSyncError] = useState18(null);
5506
+ const [conflictEntries, setConflictEntries] = useState18([]);
5507
+ const [cursor, setCursor] = useState18(0);
5508
+ useEffect20(() => {
5790
5509
  void initialize(isPro());
5791
5510
  }, []);
5792
- useEffect21(() => {
5511
+ useEffect20(() => {
5793
5512
  if (conflicts.length > 0) {
5794
5513
  setConflictEntries(
5795
5514
  conflicts.map((c) => ({ conflict: c, resolution: "pending" }))
@@ -5954,7 +5673,7 @@ function SyncView() {
5954
5673
  }
5955
5674
 
5956
5675
  // src/views/compliance.tsx
5957
- import { useCallback as useCallback6, useEffect as useEffect22, useRef as useRef13, useState as useState20 } from "react";
5676
+ import { useCallback as useCallback6, useEffect as useEffect21, useRef as useRef13, useState as useState19 } from "react";
5958
5677
  import { Box as Box33, Text as Text35 } from "ink";
5959
5678
  import { TextInput as TextInput7 } from "@inkjs/ui";
5960
5679
 
@@ -5966,6 +5685,13 @@ async function* remediateViolations(violations, isPro) {
5966
5685
  let skipped = 0;
5967
5686
  for (const v of violations) {
5968
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
+ }
5969
5695
  yield `Installing ${v.packageName}...`;
5970
5696
  try {
5971
5697
  for await (const line of streamBrew(["install", v.packageName])) {
@@ -5977,6 +5703,13 @@ async function* remediateViolations(violations, isPro) {
5977
5703
  skipped++;
5978
5704
  }
5979
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
+ }
5980
5713
  yield `Upgrading ${v.packageName}${v.required ? ` to ${v.required}+` : ""}...`;
5981
5714
  try {
5982
5715
  for await (const line of streamBrew(["upgrade", v.packageName])) {
@@ -6081,13 +5814,13 @@ function ViolationList({ violations }) {
6081
5814
  function ComplianceView() {
6082
5815
  const isPro = useLicenseStore((s) => s.isPro);
6083
5816
  const { policy, report, loading, error, importPolicy, runCheck } = useComplianceStore();
6084
- const [phase, setPhase] = useState20("overview");
6085
- const [resultMessage, setResultMessage] = useState20(null);
6086
- const [streamLines, setStreamLines] = useState20([]);
6087
- const [streamRunning, setStreamRunning] = useState20(false);
5817
+ const [phase, setPhase] = useState19("overview");
5818
+ const [resultMessage, setResultMessage] = useState19(null);
5819
+ const [streamLines, setStreamLines] = useState19([]);
5820
+ const [streamRunning, setStreamRunning] = useState19(false);
6088
5821
  const generatorRef = useRef13(null);
6089
5822
  const mountedRef = useRef13(true);
6090
- useEffect22(() => {
5823
+ useEffect21(() => {
6091
5824
  mountedRef.current = true;
6092
5825
  return () => {
6093
5826
  mountedRef.current = false;
@@ -6292,7 +6025,7 @@ function ComplianceView() {
6292
6025
  import { Fragment as Fragment7, jsx as jsx38, jsxs as jsxs35 } from "react/jsx-runtime";
6293
6026
  function LicenseInitializer() {
6294
6027
  const initLicense = useLicenseStore((s) => s.initialize);
6295
- useEffect23(() => {
6028
+ useEffect22(() => {
6296
6029
  initLicense();
6297
6030
  }, []);
6298
6031
  return null;
@@ -6345,8 +6078,8 @@ function App() {
6345
6078
  const { exit } = useApp();
6346
6079
  const currentView = useNavigationStore((s) => s.currentView);
6347
6080
  const isTestEnv = typeof process !== "undefined" && false;
6348
- const [showWelcome, setShowWelcome] = useState21(isTestEnv ? false : null);
6349
- useEffect23(() => {
6081
+ const [showWelcome, setShowWelcome] = useState20(isTestEnv ? false : null);
6082
+ useEffect22(() => {
6350
6083
  if (isTestEnv) return;
6351
6084
  void hasCompletedOnboarding().then((done) => setShowWelcome(!done));
6352
6085
  }, []);
@@ -6443,7 +6176,7 @@ async function reportError(err, context = {}) {
6443
6176
  const config = await resolveConfig();
6444
6177
  if (!config.enabled || !config.endpoint) return;
6445
6178
  const machineId = await getMachineId();
6446
- const version = true ? "1.2.0" : "unknown";
6179
+ const version = true ? "1.2.2" : "unknown";
6447
6180
  await postReport(buildReport("error", err, context, machineId, version), config);
6448
6181
  }
6449
6182
  async function installCrashReporter() {
@@ -6452,7 +6185,7 @@ async function installCrashReporter() {
6452
6185
  if (!config.enabled || !config.endpoint) return;
6453
6186
  _installed = true;
6454
6187
  const machineId = await getMachineId();
6455
- const version = true ? "1.2.0" : "unknown";
6188
+ const version = true ? "1.2.2" : "unknown";
6456
6189
  process.on("uncaughtException", (err) => {
6457
6190
  void postReport(buildReport("fatal", err, { kind: "uncaughtException" }, machineId, version), config);
6458
6191
  });
@@ -6467,7 +6200,7 @@ import { jsx as jsx39 } from "react/jsx-runtime";
6467
6200
  var [, , command, arg] = process.argv;
6468
6201
  async function runCli() {
6469
6202
  if (command === "--version" || command === "-v" || command === "version") {
6470
- process.stdout.write("1.2.0\n");
6203
+ process.stdout.write("1.2.2\n");
6471
6204
  return;
6472
6205
  }
6473
6206
  await ensureDataDirs();
@@ -6562,7 +6295,7 @@ async function runCli() {
6562
6295
  }
6563
6296
  if (isPro) {
6564
6297
  try {
6565
- const { loadSnapshots: loadSnapshots2 } = await import("./snapshot-ZOJETCED.js");
6298
+ const { loadSnapshots: loadSnapshots2 } = await import("./snapshot-RQ444U5L.js");
6566
6299
  const snapshots = await loadSnapshots2();
6567
6300
  if (snapshots.length > 0) {
6568
6301
  const latest = snapshots[0];
@@ -6574,7 +6307,7 @@ Snapshots: ${snapshots.length} (latest: ${latest ? formatDate(latest.capturedAt)
6574
6307
  } catch {
6575
6308
  }
6576
6309
  try {
6577
- const { loadBrewfile: loadBrewfile2, computeDrift: computeDrift2 } = await import("./brewfile-manager-G7Q4IOG3.js");
6310
+ const { loadBrewfile: loadBrewfile2, computeDrift: computeDrift2 } = await import("./brewfile-manager-CPVXIVZC.js");
6578
6311
  const schema = await loadBrewfile2();
6579
6312
  if (schema) {
6580
6313
  const drift = await computeDrift2(schema);
@@ -6583,7 +6316,7 @@ Snapshots: ${snapshots.length} (latest: ${latest ? formatDate(latest.capturedAt)
6583
6316
  } catch {
6584
6317
  }
6585
6318
  try {
6586
- const { loadSyncConfig: loadSyncConfig2 } = await import("./sync-engine-76YMONYH.js");
6319
+ const { loadSyncConfig: loadSyncConfig2 } = await import("./sync-engine-Q4B2PPQS.js");
6587
6320
  const syncConfig = await loadSyncConfig2();
6588
6321
  if (syncConfig?.lastSync) {
6589
6322
  console.log(`Sync: last sync ${formatDate(syncConfig.lastSync)}`);
@@ -6591,8 +6324,8 @@ Snapshots: ${snapshots.length} (latest: ${latest ? formatDate(latest.capturedAt)
6591
6324
  } catch {
6592
6325
  }
6593
6326
  try {
6594
- const { loadPolicy: loadPolicy2 } = await import("./policy-io-EECGRKNA.js");
6595
- 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");
6596
6329
  const policy = await loadPolicy2(`${process.env["HOME"] ?? "~"}/.brew-tui/policy.yaml`).catch(() => null);
6597
6330
  if (policy) {
6598
6331
  const report = await checkCompliance2(policy, true);
@@ -6606,7 +6339,7 @@ Snapshots: ${snapshots.length} (latest: ${latest ? formatDate(latest.capturedAt)
6606
6339
  if (command === "install-brewbar") {
6607
6340
  await useLicenseStore.getState().initialize();
6608
6341
  const isPro = useLicenseStore.getState().isPro();
6609
- const { installBrewBar } = await import("./brewbar-installer-GWJ76J6G.js");
6342
+ const { installBrewBar } = await import("./brewbar-installer-BKE6Z7OI.js");
6610
6343
  try {
6611
6344
  console.log(t("cli_brewbarInstalling"));
6612
6345
  await installBrewBar(isPro, arg === "--force");
@@ -6618,7 +6351,7 @@ Snapshots: ${snapshots.length} (latest: ${latest ? formatDate(latest.capturedAt)
6618
6351
  return;
6619
6352
  }
6620
6353
  if (command === "uninstall-brewbar") {
6621
- const { uninstallBrewBar } = await import("./brewbar-installer-GWJ76J6G.js");
6354
+ const { uninstallBrewBar } = await import("./brewbar-installer-BKE6Z7OI.js");
6622
6355
  try {
6623
6356
  await uninstallBrewBar();
6624
6357
  console.log(t("cli_brewbarUninstalled"));
@@ -6649,8 +6382,8 @@ async function ensureBrewBarRunning() {
6649
6382
  if (process.platform !== "darwin") return;
6650
6383
  await useLicenseStore.getState().initialize();
6651
6384
  if (!useLicenseStore.getState().isPro()) return;
6652
- const { isBrewBarInstalled, installBrewBar, launchBrewBar } = await import("./brewbar-installer-GWJ76J6G.js");
6653
- const { checkBrewBarVersion } = await import("./version-check-MJZDQG73.js");
6385
+ const { isBrewBarInstalled, installBrewBar, launchBrewBar } = await import("./brewbar-installer-BKE6Z7OI.js");
6386
+ const { checkBrewBarVersion } = await import("./version-check-FKY5HGSI.js");
6654
6387
  try {
6655
6388
  if (!await isBrewBarInstalled()) {
6656
6389
  console.log(t("cli_brewbarInstalling"));