brew-tui 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/index.js CHANGED
@@ -1,20 +1,22 @@
1
1
  import {
2
2
  appendEntry,
3
3
  clearHistory,
4
+ detectAction,
4
5
  loadHistory
5
- } from "./chunk-3BK3B53S.js";
6
+ } from "./chunk-UBHTQL7T.js";
6
7
  import {
7
8
  PROFILES_DIR,
8
9
  activate,
9
10
  deactivate,
10
11
  ensureDataDirs,
12
+ fetchWithTimeout,
11
13
  loadLicense,
12
14
  requirePro,
13
15
  t,
14
16
  tp,
15
17
  useLicenseStore,
16
18
  useLocaleStore
17
- } from "./chunk-P6PTN4HR.js";
19
+ } from "./chunk-KXDTKY3E.js";
18
20
 
19
21
  // src/index.tsx
20
22
  import { createInterface } from "readline/promises";
@@ -28,7 +30,7 @@ import { useApp } from "ink";
28
30
  import { Box as Box3 } from "ink";
29
31
 
30
32
  // src/components/layout/header.tsx
31
- import React from "react";
33
+ import React2 from "react";
32
34
  import { Box, Text as Text2 } from "ink";
33
35
 
34
36
  // src/stores/navigation-store.ts
@@ -51,24 +53,25 @@ var useNavigationStore = create((set, get) => ({
51
53
  currentView: "dashboard",
52
54
  previousView: null,
53
55
  selectedPackage: null,
54
- viewHistory: ["dashboard"],
56
+ viewHistory: [],
55
57
  navigate: (view) => {
56
58
  const { currentView, viewHistory } = get();
57
59
  if (view === currentView) return;
58
60
  set({
59
61
  currentView: view,
60
62
  previousView: currentView,
61
- viewHistory: [...viewHistory.slice(-19), view]
63
+ viewHistory: [...viewHistory.slice(-19), currentView]
62
64
  });
63
65
  },
64
66
  goBack: () => {
65
- const { previousView } = get();
66
- if (previousView) {
67
- set((state) => ({
68
- currentView: previousView,
69
- previousView: state.currentView
70
- }));
71
- }
67
+ const { viewHistory } = get();
68
+ if (viewHistory.length === 0) return;
69
+ const prev = viewHistory[viewHistory.length - 1];
70
+ set({
71
+ currentView: prev,
72
+ previousView: get().currentView,
73
+ viewHistory: viewHistory.slice(0, -1)
74
+ });
72
75
  },
73
76
  selectPackage: (name) => set({ selectedPackage: name })
74
77
  }));
@@ -93,6 +96,7 @@ function isProView(viewId) {
93
96
  }
94
97
 
95
98
  // src/utils/gradient.tsx
99
+ import React, { useMemo } from "react";
96
100
  import { Text } from "ink";
97
101
  import { Fragment, jsx } from "react/jsx-runtime";
98
102
  function hexToRgb(hex) {
@@ -110,22 +114,25 @@ function interpolateColor(c1, c2, t2) {
110
114
  const [r2, g2, b2] = hexToRgb(c2);
111
115
  return rgbToHex(lerp(r1, r2, t2), lerp(g1, g2, t2), lerp(b1, b2, t2));
112
116
  }
113
- function GradientText({ children, colors, bold }) {
117
+ var GradientText = React.memo(function GradientText2({ children, colors, bold }) {
114
118
  if (colors.length < 2) {
115
119
  return /* @__PURE__ */ jsx(Text, { color: colors[0], bold, children });
116
120
  }
117
- const chars = [...children];
118
- const maxIdx = Math.max(chars.length - 1, 1);
119
- return /* @__PURE__ */ jsx(Fragment, { children: chars.map((char, i) => {
120
- const t2 = i / maxIdx;
121
- const segment = t2 * (colors.length - 1);
122
- const lower = Math.floor(segment);
123
- const upper = Math.min(lower + 1, colors.length - 1);
124
- const frac = segment - lower;
125
- const color = interpolateColor(colors[lower], colors[upper], frac);
126
- return /* @__PURE__ */ jsx(Text, { color, bold, children: char }, i);
127
- }) });
128
- }
121
+ const coloredChars = useMemo(() => {
122
+ const chars = [...children];
123
+ const maxIdx = Math.max(chars.length - 1, 1);
124
+ return chars.map((char, i) => {
125
+ const t2 = i / maxIdx;
126
+ const segment = t2 * (colors.length - 1);
127
+ const lower = Math.floor(segment);
128
+ const upper = Math.min(lower + 1, colors.length - 1);
129
+ const frac = segment - lower;
130
+ const color = interpolateColor(colors[lower], colors[upper], frac);
131
+ return { char, color, key: `${i}-${color}` };
132
+ });
133
+ }, [children, colors]);
134
+ return /* @__PURE__ */ jsx(Fragment, { children: coloredChars.map(({ char, color, key }) => /* @__PURE__ */ jsx(Text, { color, bold, children: char }, key)) });
135
+ });
129
136
  var GRADIENTS = {
130
137
  gold: ["#FFD700", "#FFA500", "#B8860B"],
131
138
  sunset: ["#FF6B2B", "#FFD700", "#FF6B2B"],
@@ -208,7 +215,7 @@ function Header() {
208
215
  const viewLabel = t(VIEW_LABEL_KEYS[view]);
209
216
  const label = key ? `${key}:${viewLabel}` : viewLabel;
210
217
  const isPro = isProView(view);
211
- return /* @__PURE__ */ jsxs(React.Fragment, { children: [
218
+ return /* @__PURE__ */ jsxs(React2.Fragment, { children: [
212
219
  i > 0 && /* @__PURE__ */ jsxs(Text2, { color: "#4B5563", children: [
213
220
  " ",
214
221
  "\u2502",
@@ -233,7 +240,7 @@ function Header() {
233
240
  }
234
241
 
235
242
  // src/components/layout/footer.tsx
236
- import React2 from "react";
243
+ import React3 from "react";
237
244
  import { Box as Box2, Text as Text3 } from "ink";
238
245
  import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
239
246
  var VIEW_HINT_DEFS = {
@@ -265,7 +272,7 @@ function Footer() {
265
272
  return /* @__PURE__ */ jsxs2(Box2, { borderStyle: "single", borderTop: true, borderBottom: false, borderLeft: false, borderRight: false, borderColor: "#FFD700", paddingX: 1, flexWrap: "wrap", children: [
266
273
  defs.map((def, i) => {
267
274
  const key = def.length === 1 ? def[0] : `${def[0]}:${def[1]}`;
268
- return /* @__PURE__ */ jsxs2(React2.Fragment, { children: [
275
+ return /* @__PURE__ */ jsxs2(React3.Fragment, { children: [
269
276
  i > 0 && /* @__PURE__ */ jsxs2(Text3, { color: "#4B5563", children: [
270
277
  " ",
271
278
  "\u2502",
@@ -401,6 +408,12 @@ function UpgradePrompt({ viewId }) {
401
408
  /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", alignItems: "center", children: [
402
409
  /* @__PURE__ */ jsx5(Text4, { color: "#06B6D4", bold: true, children: t("upgrade_pricing") }),
403
410
  /* @__PURE__ */ jsx5(Text4, { children: " " }),
411
+ /* @__PURE__ */ jsx5(Text4, { color: "#9CA3AF", children: t("upgrade_buyAt") }),
412
+ /* @__PURE__ */ jsxs4(Text4, { color: "#38BDF8", bold: true, children: [
413
+ " ",
414
+ t("upgrade_buyUrl")
415
+ ] }),
416
+ /* @__PURE__ */ jsx5(Text4, { children: " " }),
404
417
  /* @__PURE__ */ jsx5(Text4, { color: "#9CA3AF", children: t("upgrade_activateWith") }),
405
418
  /* @__PURE__ */ jsxs4(Text4, { color: "#22C55E", bold: true, children: [
406
419
  " ",
@@ -415,7 +428,7 @@ function UpgradePrompt({ viewId }) {
415
428
  }
416
429
 
417
430
  // src/views/dashboard.tsx
418
- import { useEffect, useMemo } from "react";
431
+ import { useEffect, useMemo as useMemo2 } from "react";
419
432
  import { Box as Box8, Text as Text10 } from "ink";
420
433
 
421
434
  // src/stores/brew-store.ts
@@ -484,7 +497,7 @@ async function* streamBrew(args) {
484
497
  if (lines.length > 0) {
485
498
  yield lines.shift();
486
499
  } else if (!done) {
487
- await new Promise((r) => setTimeout(r, 50));
500
+ await new Promise((r) => setTimeout(r, 100));
488
501
  }
489
502
  }
490
503
  } finally {
@@ -687,6 +700,9 @@ function setLoading(set, key, value) {
687
700
  function setError(set, key, error) {
688
701
  set((s) => ({ errors: { ...s.errors, [key]: error } }));
689
702
  }
703
+ function recordFetchTime(set, key) {
704
+ set((s) => ({ lastFetchedAt: { ...s.lastFetchedAt, [key]: Date.now() } }));
705
+ }
690
706
  var useBrewStore = create3((set) => ({
691
707
  formulae: [],
692
708
  casks: [],
@@ -701,6 +717,7 @@ var useBrewStore = create3((set) => ({
701
717
  // flashing empty/zeroed content for one frame before the fetch starts.
702
718
  loading: { installed: true, outdated: true, services: true, config: true, doctor: false },
703
719
  errors: {},
720
+ lastFetchedAt: {},
704
721
  fetchInstalled: async () => {
705
722
  setLoading(set, "installed", true);
706
723
  setError(set, "installed", null);
@@ -711,6 +728,7 @@ var useBrewStore = create3((set) => ({
711
728
  setError(set, "installed", err instanceof Error ? err.message : String(err));
712
729
  } finally {
713
730
  setLoading(set, "installed", false);
731
+ recordFetchTime(set, "installed");
714
732
  }
715
733
  },
716
734
  fetchOutdated: async () => {
@@ -723,6 +741,7 @@ var useBrewStore = create3((set) => ({
723
741
  setError(set, "outdated", err instanceof Error ? err.message : String(err));
724
742
  } finally {
725
743
  setLoading(set, "outdated", false);
744
+ recordFetchTime(set, "outdated");
726
745
  }
727
746
  },
728
747
  fetchServices: async () => {
@@ -735,6 +754,7 @@ var useBrewStore = create3((set) => ({
735
754
  setError(set, "services", err instanceof Error ? err.message : String(err));
736
755
  } finally {
737
756
  setLoading(set, "services", false);
757
+ recordFetchTime(set, "services");
738
758
  }
739
759
  },
740
760
  fetchConfig: async () => {
@@ -746,6 +766,7 @@ var useBrewStore = create3((set) => ({
746
766
  setError(set, "config", err instanceof Error ? err.message : String(err));
747
767
  } finally {
748
768
  setLoading(set, "config", false);
769
+ recordFetchTime(set, "config");
749
770
  }
750
771
  },
751
772
  fetchLeaves: async () => {
@@ -753,9 +774,11 @@ var useBrewStore = create3((set) => ({
753
774
  const result = await getLeaves();
754
775
  set({ leaves: result });
755
776
  } catch (err) {
756
- if (process.env.NODE_ENV !== "production") {
777
+ if (false) {
757
778
  console.error("[brew-store] fetchLeaves failed:", err instanceof Error ? err.message : String(err));
758
779
  }
780
+ } finally {
781
+ recordFetchTime(set, "leaves");
759
782
  }
760
783
  },
761
784
  fetchDoctor: async () => {
@@ -768,13 +791,12 @@ var useBrewStore = create3((set) => ({
768
791
  setError(set, "doctor", err instanceof Error ? err.message : String(err));
769
792
  } finally {
770
793
  setLoading(set, "doctor", false);
794
+ recordFetchTime(set, "doctor");
771
795
  }
772
796
  },
773
797
  fetchAll: async () => {
774
- try {
775
- await brewUpdate();
776
- } catch {
777
- }
798
+ brewUpdate().catch(() => {
799
+ });
778
800
  const store = useBrewStore.getState();
779
801
  await Promise.all([
780
802
  store.fetchInstalled(),
@@ -906,11 +928,11 @@ function DashboardView() {
906
928
  useEffect(() => {
907
929
  fetchAll();
908
930
  }, []);
909
- const errorServiceList = useMemo(
931
+ const errorServiceList = useMemo2(
910
932
  () => services.filter((s) => s.status === "error"),
911
933
  [services]
912
934
  );
913
- const runningServices = useMemo(
935
+ const runningServices = useMemo2(
914
936
  () => services.filter((s) => s.status === "started").length,
915
937
  [services]
916
938
  );
@@ -981,8 +1003,8 @@ function DashboardView() {
981
1003
  }
982
1004
 
983
1005
  // src/views/installed.tsx
984
- import { useState as useState3, useMemo as useMemo2, useEffect as useEffect5 } from "react";
985
- import { Box as Box12, Text as Text14, useInput as useInput3 } from "ink";
1006
+ import { useState as useState3, useMemo as useMemo3, useEffect as useEffect5 } from "react";
1007
+ import { Box as Box12, Text as Text14, useInput as useInput3, useStdout } from "ink";
986
1008
 
987
1009
  // src/hooks/use-debounce.ts
988
1010
  import { useState, useEffect as useEffect2 } from "react";
@@ -998,24 +1020,11 @@ function useDebounce(value, delayMs) {
998
1020
  // src/hooks/use-brew-stream.ts
999
1021
  import { useState as useState2, useCallback, useRef, useEffect as useEffect3 } from "react";
1000
1022
  var MAX_LINES = 100;
1001
- function detectAction(args) {
1002
- const cmd = args[0];
1003
- if (cmd === "install") return { action: "install", packageName: args[1] ?? null };
1004
- if (cmd === "uninstall") {
1005
- const name = args.find((a) => !a.startsWith("-")) === "uninstall" ? args.find((a, i) => i > 0 && !a.startsWith("-")) ?? null : args[1] ?? null;
1006
- return { action: "uninstall", packageName: name };
1007
- }
1008
- if (cmd === "upgrade") {
1009
- if (args.length === 1) return { action: "upgrade-all", packageName: null };
1010
- return { action: "upgrade", packageName: args[1] ?? null };
1011
- }
1012
- return null;
1013
- }
1014
1023
  async function logToHistory(args, success, error) {
1015
1024
  const detected = detectAction(args);
1016
1025
  if (!detected) return;
1017
1026
  try {
1018
- const { appendEntry: appendEntry2 } = await import("./history-logger-LQT622M2.js");
1027
+ const { appendEntry: appendEntry2 } = await import("./history-logger-65UF2R6F.js");
1019
1028
  await appendEntry2(detected.action, detected.packageName, success, error);
1020
1029
  } catch {
1021
1030
  }
@@ -1143,7 +1152,7 @@ function ProgressLog({ lines, isRunning, title, maxVisible = 15 }) {
1143
1152
  title
1144
1153
  ] })
1145
1154
  ] }),
1146
- visible.map((line, i) => /* @__PURE__ */ jsx13(Text13, { color: "#9CA3AF", wrap: "wrap", children: line }, i)),
1155
+ visible.map((line, i) => /* @__PURE__ */ jsx13(Text13, { color: "#9CA3AF", wrap: "wrap", children: line }, line.slice(0, 30) + (lines.length - visible.length + i))),
1147
1156
  lines.length === 0 && !isRunning && /* @__PURE__ */ jsx13(Text13, { color: "#6B7280", italic: true, children: t("progress_noOutput") })
1148
1157
  ] });
1149
1158
  }
@@ -1184,6 +1193,10 @@ function InstalledView() {
1184
1193
  const debouncedFilter = useDebounce(filter, 200);
1185
1194
  const stream = useBrewStream();
1186
1195
  const { openModal, closeModal } = useModalStore();
1196
+ const { stdout } = useStdout();
1197
+ const cols = stdout?.columns ?? 80;
1198
+ const nameWidth = Math.floor(cols * 0.35);
1199
+ const versionWidth = Math.floor(cols * 0.15);
1187
1200
  useEffect5(() => {
1188
1201
  fetchInstalled();
1189
1202
  }, []);
@@ -1196,7 +1209,7 @@ function InstalledView() {
1196
1209
  }
1197
1210
  return void 0;
1198
1211
  }, [isSearching]);
1199
- const allItems = useMemo2(() => {
1212
+ const allItems = useMemo3(() => {
1200
1213
  const items = tab === "formulae" ? formulaeToListItems(formulae) : casksToListItems(casks);
1201
1214
  if (!debouncedFilter) return items;
1202
1215
  const lower = debouncedFilter.toLowerCase();
@@ -1206,6 +1219,13 @@ function InstalledView() {
1206
1219
  }, [formulae, casks, tab, debouncedFilter]);
1207
1220
  useInput3((input, key) => {
1208
1221
  if (confirmUninstall || stream.isRunning) return;
1222
+ if (!stream.isRunning && stream.lines.length > 0) {
1223
+ if (key.escape) {
1224
+ stream.clear();
1225
+ void fetchInstalled();
1226
+ }
1227
+ return;
1228
+ }
1209
1229
  if (isSearching) {
1210
1230
  if (key.escape) {
1211
1231
  setIsSearching(false);
@@ -1249,10 +1269,20 @@ function InstalledView() {
1249
1269
  title: t("pkgInfo_uninstalling", { name: "..." })
1250
1270
  }
1251
1271
  ),
1252
- !stream.isRunning && /* @__PURE__ */ jsx14(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx14(Box12, { borderStyle: "round", borderColor: stream.error ? "#EF4444" : "#22C55E", paddingX: 2, paddingY: 0, children: /* @__PURE__ */ jsx14(Text14, { color: stream.error ? "#EF4444" : "#22C55E", bold: true, children: stream.error ? `\u2718 ${stream.error}` : `\u2714 ${t("pkgInfo_done")}` }) }) })
1272
+ stream.isRunning && /* @__PURE__ */ jsxs14(Text14, { color: "#6B7280", children: [
1273
+ "esc:",
1274
+ t("hint_cancel")
1275
+ ] }),
1276
+ !stream.isRunning && /* @__PURE__ */ jsxs14(Box12, { flexDirection: "column", marginTop: 1, children: [
1277
+ /* @__PURE__ */ jsx14(Box12, { borderStyle: "round", borderColor: stream.error ? "#EF4444" : "#22C55E", paddingX: 2, paddingY: 0, children: /* @__PURE__ */ jsx14(Text14, { color: stream.error ? "#EF4444" : "#22C55E", bold: true, children: stream.error ? `\u2718 ${stream.error}` : `\u2714 ${t("pkgInfo_done")}` }) }),
1278
+ /* @__PURE__ */ jsxs14(Text14, { color: "#6B7280", children: [
1279
+ "esc:",
1280
+ t("hint_back")
1281
+ ] })
1282
+ ] })
1253
1283
  ] });
1254
1284
  }
1255
- const MAX_VISIBLE_ROWS = 20;
1285
+ const MAX_VISIBLE_ROWS = Math.max(5, (stdout?.rows ?? 24) - 8);
1256
1286
  const start = Math.max(0, cursor - Math.floor(MAX_VISIBLE_ROWS / 2));
1257
1287
  const visible = allItems.slice(start, start + MAX_VISIBLE_ROWS);
1258
1288
  return /* @__PURE__ */ jsxs14(Box12, { flexDirection: "column", children: [
@@ -1302,9 +1332,9 @@ function InstalledView() {
1302
1332
  /* @__PURE__ */ jsxs14(Box12, { gap: 1, borderStyle: "single", borderBottom: true, borderTop: false, borderLeft: false, borderRight: false, borderColor: "#4B5563", children: [
1303
1333
  /* @__PURE__ */ jsxs14(Text14, { color: "#F9FAFB", bold: true, children: [
1304
1334
  " ",
1305
- "Package".padEnd(27)
1335
+ "Package".padEnd(nameWidth)
1306
1336
  ] }),
1307
- /* @__PURE__ */ jsx14(Text14, { color: "#F9FAFB", bold: true, children: "Version".padEnd(12) }),
1337
+ /* @__PURE__ */ jsx14(Text14, { color: "#F9FAFB", bold: true, children: "Version".padEnd(versionWidth) }),
1308
1338
  /* @__PURE__ */ jsx14(Text14, { color: "#F9FAFB", bold: true, children: "Status" })
1309
1339
  ] }),
1310
1340
  /* @__PURE__ */ jsxs14(Box12, { flexDirection: "column", children: [
@@ -1318,8 +1348,8 @@ function InstalledView() {
1318
1348
  const isCurrent = idx === cursor;
1319
1349
  return /* @__PURE__ */ jsxs14(Box12, { gap: 1, children: [
1320
1350
  /* @__PURE__ */ jsx14(Text14, { color: isCurrent ? "#22C55E" : "#9CA3AF", children: isCurrent ? "\u25B6" : " " }),
1321
- /* @__PURE__ */ jsx14(Text14, { bold: isCurrent, inverse: isCurrent, color: isCurrent ? "#F9FAFB" : "#9CA3AF", children: truncate(item.name, 27).padEnd(27) }),
1322
- /* @__PURE__ */ jsx14(Text14, { color: "#2DD4BF", children: item.version.padEnd(12) }),
1351
+ /* @__PURE__ */ jsx14(Text14, { bold: isCurrent, inverse: isCurrent, color: isCurrent ? "#F9FAFB" : "#9CA3AF", children: truncate(item.name, nameWidth).padEnd(nameWidth) }),
1352
+ /* @__PURE__ */ jsx14(Text14, { color: "#2DD4BF", children: item.version.padEnd(versionWidth) }),
1323
1353
  item.outdated && /* @__PURE__ */ jsx14(StatusBadge, { label: t("badge_outdated"), variant: "warning" }),
1324
1354
  item.pinned && /* @__PURE__ */ jsx14(StatusBadge, { label: t("badge_pinned"), variant: "info" }),
1325
1355
  item.kegOnly && /* @__PURE__ */ jsx14(StatusBadge, { label: t("badge_kegOnly"), variant: "muted" }),
@@ -1345,6 +1375,7 @@ function SearchView() {
1345
1375
  const [query, setQuery] = useState4("");
1346
1376
  const [results, setResults] = useState4(null);
1347
1377
  const [searching, setSearching] = useState4(false);
1378
+ const [searchError, setSearchError] = useState4(null);
1348
1379
  const [cursor, setCursor] = useState4(0);
1349
1380
  const [confirmInstall, setConfirmInstall] = useState4(null);
1350
1381
  const stream = useBrewStream();
@@ -1365,13 +1396,14 @@ function SearchView() {
1365
1396
  const doSearch = useCallback2(async (term) => {
1366
1397
  if (term.length < 2) return;
1367
1398
  setSearching(true);
1399
+ setSearchError(null);
1368
1400
  try {
1369
1401
  const r = await search(term);
1370
1402
  setResults(r);
1371
1403
  setCursor(0);
1372
1404
  } catch (err) {
1373
1405
  setResults({ formulae: [], casks: [] });
1374
- void err;
1406
+ setSearchError(err instanceof Error ? err.message : "Search failed");
1375
1407
  } finally {
1376
1408
  setSearching(false);
1377
1409
  }
@@ -1382,7 +1414,10 @@ function SearchView() {
1382
1414
  void fetchInstalled();
1383
1415
  }
1384
1416
  }, [stream.isRunning, stream.error]);
1385
- const allResults = results ? [...results.formulae, ...results.casks] : [];
1417
+ const MAX_VISIBLE = 20;
1418
+ const visibleFormulae = results ? results.formulae.slice(0, MAX_VISIBLE) : [];
1419
+ const visibleCasks = results ? results.casks.slice(0, MAX_VISIBLE) : [];
1420
+ const allVisible = [...visibleFormulae, ...visibleCasks];
1386
1421
  useInput4((input, key) => {
1387
1422
  if (stream.isRunning) {
1388
1423
  if (key.escape) stream.cancel();
@@ -1393,17 +1428,17 @@ function SearchView() {
1393
1428
  void doSearch(query);
1394
1429
  return;
1395
1430
  }
1396
- if (key.return && allResults[cursor]) {
1397
- selectPackage(allResults[cursor]);
1431
+ if (key.return && allVisible[cursor]) {
1432
+ selectPackage(allVisible[cursor]);
1398
1433
  navigate("package-info");
1399
1434
  return;
1400
1435
  }
1401
- if (input === "i" && allResults[cursor]) {
1402
- setConfirmInstall(allResults[cursor]);
1436
+ if (input === "i" && allVisible[cursor]) {
1437
+ setConfirmInstall(allVisible[cursor]);
1403
1438
  return;
1404
1439
  }
1405
1440
  if (input === "j" || key.downArrow) {
1406
- setCursor((c) => Math.min(c + 1, Math.max(0, allResults.length - 1)));
1441
+ setCursor((c) => Math.min(c + 1, Math.max(0, allVisible.length - 1)));
1407
1442
  } else if (input === "k" || key.upArrow) {
1408
1443
  setCursor((c) => Math.max(c - 1, 0));
1409
1444
  } else if (key.escape) {
@@ -1457,6 +1492,7 @@ function SearchView() {
1457
1492
  ] })
1458
1493
  ] }),
1459
1494
  searching && /* @__PURE__ */ jsx15(Loading, { message: t("loading_searching") }),
1495
+ searchError && /* @__PURE__ */ jsx15(Box13, { marginBottom: 1, children: /* @__PURE__ */ jsx15(Text15, { color: "#EF4444", children: searchError }) }),
1460
1496
  confirmInstall && /* @__PURE__ */ jsx15(
1461
1497
  ConfirmDialog,
1462
1498
  {
@@ -1471,44 +1507,44 @@ function SearchView() {
1471
1507
  }
1472
1508
  ),
1473
1509
  results && !searching && !confirmInstall && /* @__PURE__ */ jsxs15(Box13, { flexDirection: "column", children: [
1474
- results.formulae.length > 0 && /* @__PURE__ */ jsxs15(Box13, { flexDirection: "column", marginBottom: 1, children: [
1510
+ visibleFormulae.length > 0 && /* @__PURE__ */ jsxs15(Box13, { flexDirection: "column", marginBottom: 1, children: [
1475
1511
  /* @__PURE__ */ jsx15(Text15, { bold: true, color: "#06B6D4", children: t("search_formulaeHeader", { count: results.formulae.length }) }),
1476
- results.formulae.slice(0, 20).map((name, i) => {
1512
+ visibleFormulae.map((name, i) => {
1477
1513
  const isCurrent = i === cursor;
1478
1514
  return /* @__PURE__ */ jsxs15(Box13, { gap: 1, children: [
1479
1515
  /* @__PURE__ */ jsx15(Text15, { color: isCurrent ? "#22C55E" : "#9CA3AF", children: isCurrent ? "\u25B6" : " " }),
1480
1516
  /* @__PURE__ */ jsx15(Text15, { bold: isCurrent, inverse: isCurrent, children: name })
1481
1517
  ] }, name);
1482
1518
  }),
1483
- results.formulae.length > 20 && /* @__PURE__ */ jsxs15(Text15, { color: "#6B7280", dimColor: true, children: [
1519
+ results.formulae.length > MAX_VISIBLE && /* @__PURE__ */ jsxs15(Text15, { color: "#6B7280", dimColor: true, children: [
1484
1520
  " ",
1485
- t("scroll_moreBelow", { count: results.formulae.length - 20 })
1521
+ t("scroll_moreBelow", { count: results.formulae.length - MAX_VISIBLE })
1486
1522
  ] })
1487
1523
  ] }),
1488
- results.casks.length > 0 && /* @__PURE__ */ jsxs15(Box13, { flexDirection: "column", children: [
1524
+ visibleCasks.length > 0 && /* @__PURE__ */ jsxs15(Box13, { flexDirection: "column", children: [
1489
1525
  /* @__PURE__ */ jsx15(Text15, { bold: true, color: "#A855F7", children: t("search_casksHeader", { count: results.casks.length }) }),
1490
- results.casks.slice(0, 20).map((name, i) => {
1491
- const idx = results.formulae.length + i;
1526
+ visibleCasks.map((name, i) => {
1527
+ const idx = visibleFormulae.length + i;
1492
1528
  const isCurrent = idx === cursor;
1493
1529
  return /* @__PURE__ */ jsxs15(Box13, { gap: 1, children: [
1494
1530
  /* @__PURE__ */ jsx15(Text15, { color: isCurrent ? "#22C55E" : "#9CA3AF", children: isCurrent ? "\u25B6" : " " }),
1495
1531
  /* @__PURE__ */ jsx15(Text15, { bold: isCurrent, inverse: isCurrent, children: name })
1496
1532
  ] }, name);
1497
1533
  }),
1498
- results.casks.length > 20 && /* @__PURE__ */ jsxs15(Text15, { color: "#6B7280", dimColor: true, children: [
1534
+ results.casks.length > MAX_VISIBLE && /* @__PURE__ */ jsxs15(Text15, { color: "#6B7280", dimColor: true, children: [
1499
1535
  " ",
1500
- t("scroll_moreBelow", { count: results.casks.length - 20 })
1536
+ t("scroll_moreBelow", { count: results.casks.length - MAX_VISIBLE })
1501
1537
  ] })
1502
1538
  ] }),
1503
- allResults.length === 0 && /* @__PURE__ */ jsx15(Box13, { borderStyle: "round", borderColor: "#6B7280", paddingX: 2, children: /* @__PURE__ */ jsx15(Text15, { color: "#6B7280", italic: true, children: t("search_noResults") }) }),
1504
- /* @__PURE__ */ jsx15(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx15(Text15, { color: "#F9FAFB", bold: true, children: allResults.length > 0 ? `${cursor + 1}/${allResults.length}` : "" }) })
1539
+ allVisible.length === 0 && /* @__PURE__ */ jsx15(Box13, { borderStyle: "round", borderColor: "#6B7280", paddingX: 2, children: /* @__PURE__ */ jsx15(Text15, { color: "#6B7280", italic: true, children: t("search_noResults") }) }),
1540
+ /* @__PURE__ */ jsx15(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx15(Text15, { color: "#F9FAFB", bold: true, children: allVisible.length > 0 ? `${cursor + 1}/${allVisible.length}` : "" }) })
1505
1541
  ] })
1506
1542
  ] });
1507
1543
  }
1508
1544
 
1509
1545
  // src/views/outdated.tsx
1510
1546
  import { useEffect as useEffect7, useRef as useRef3, useState as useState5 } from "react";
1511
- import { Box as Box14, Text as Text16, useInput as useInput5 } from "ink";
1547
+ import { Box as Box14, Text as Text16, useInput as useInput5, useStdout as useStdout2 } from "ink";
1512
1548
  import { jsx as jsx16, jsxs as jsxs16 } from "react/jsx-runtime";
1513
1549
  function OutdatedView() {
1514
1550
  const { outdated, loading, errors, fetchOutdated } = useBrewStore();
@@ -1548,6 +1584,10 @@ function OutdatedView() {
1548
1584
  void fetchOutdated();
1549
1585
  }
1550
1586
  });
1587
+ const { stdout } = useStdout2();
1588
+ const MAX_VISIBLE_ROWS = Math.max(5, (stdout?.rows ?? 24) - 8);
1589
+ const start = Math.max(0, cursor - Math.floor(MAX_VISIBLE_ROWS / 2));
1590
+ const visible = allOutdated.slice(start, start + MAX_VISIBLE_ROWS);
1551
1591
  if (loading.outdated) return /* @__PURE__ */ jsx16(Loading, { message: t("loading_outdated") });
1552
1592
  if (errors.outdated) return /* @__PURE__ */ jsx16(ErrorMessage, { message: errors.outdated });
1553
1593
  if (stream.isRunning || stream.lines.length > 0) {
@@ -1605,8 +1645,13 @@ function OutdatedView() {
1605
1645
  t("outdated_upToDate")
1606
1646
  ] }) }) }),
1607
1647
  allOutdated.length > 0 && !confirmAction && /* @__PURE__ */ jsxs16(Box14, { flexDirection: "column", marginTop: 1, children: [
1608
- allOutdated.map((pkg, i) => {
1609
- const isCurrent = i === cursor;
1648
+ start > 0 && /* @__PURE__ */ jsxs16(Text16, { color: "#6B7280", dimColor: true, children: [
1649
+ " ",
1650
+ t("scroll_moreAbove", { count: start })
1651
+ ] }),
1652
+ visible.map((pkg, i) => {
1653
+ const idx = start + i;
1654
+ const isCurrent = idx === cursor;
1610
1655
  return /* @__PURE__ */ jsxs16(Box14, { gap: 1, children: [
1611
1656
  /* @__PURE__ */ jsx16(Text16, { color: isCurrent ? "#22C55E" : "#9CA3AF", children: isCurrent ? "\u25B6" : " " }),
1612
1657
  /* @__PURE__ */ jsx16(Text16, { bold: isCurrent, inverse: isCurrent, color: isCurrent ? "#F9FAFB" : "#9CA3AF", children: pkg.name }),
@@ -1614,6 +1659,10 @@ function OutdatedView() {
1614
1659
  pkg.pinned && /* @__PURE__ */ jsx16(StatusBadge, { label: t("outdated_pinned"), variant: "info" })
1615
1660
  ] }, pkg.name);
1616
1661
  }),
1662
+ start + MAX_VISIBLE_ROWS < allOutdated.length && /* @__PURE__ */ jsxs16(Text16, { color: "#6B7280", dimColor: true, children: [
1663
+ " ",
1664
+ t("scroll_moreBelow", { count: allOutdated.length - start - MAX_VISIBLE_ROWS })
1665
+ ] }),
1617
1666
  /* @__PURE__ */ jsx16(Box14, { marginTop: 1, children: /* @__PURE__ */ jsxs16(Text16, { color: "#F9FAFB", bold: true, children: [
1618
1667
  cursor + 1,
1619
1668
  "/",
@@ -1811,7 +1860,7 @@ function PackageInfoView() {
1811
1860
 
1812
1861
  // src/views/services.tsx
1813
1862
  import { useEffect as useEffect9, useState as useState7 } from "react";
1814
- import { Box as Box16, Text as Text18, useInput as useInput7 } from "ink";
1863
+ import { Box as Box16, Text as Text18, useInput as useInput7, useStdout as useStdout3 } from "ink";
1815
1864
  import { jsx as jsx18, jsxs as jsxs18 } from "react/jsx-runtime";
1816
1865
  var STATUS_VARIANTS = {
1817
1866
  started: "success",
@@ -1824,6 +1873,10 @@ function ServicesView() {
1824
1873
  const [cursor, setCursor] = useState7(0);
1825
1874
  const [actionInProgress, setActionInProgress] = useState7(false);
1826
1875
  const [confirmAction, setConfirmAction] = useState7(null);
1876
+ const { stdout } = useStdout3();
1877
+ const cols = stdout?.columns ?? 80;
1878
+ const svcNameWidth = Math.floor(cols * 0.35);
1879
+ const svcStatusWidth = Math.floor(cols * 0.15);
1827
1880
  useEffect9(() => {
1828
1881
  fetchServices();
1829
1882
  }, []);
@@ -1849,6 +1902,7 @@ function ServicesView() {
1849
1902
  else if (input === "S") setConfirmAction({ type: "stop", name: svc.name });
1850
1903
  else if (input === "R") setConfirmAction({ type: "restart", name: svc.name });
1851
1904
  });
1905
+ const serviceActionError = useBrewStore((s) => s.errors["service-action"]);
1852
1906
  if (loading.services) return /* @__PURE__ */ jsx18(Loading, { message: t("loading_services") });
1853
1907
  if (errors.services) return /* @__PURE__ */ jsx18(ErrorMessage, { message: errors.services });
1854
1908
  if (services.length === 0) {
@@ -1878,16 +1932,16 @@ function ServicesView() {
1878
1932
  /* @__PURE__ */ jsxs18(Box16, { gap: 1, borderStyle: "single", borderBottom: true, borderTop: false, borderLeft: false, borderRight: false, borderColor: "#4B5563", paddingBottom: 0, children: [
1879
1933
  /* @__PURE__ */ jsxs18(Text18, { bold: true, color: "#F9FAFB", children: [
1880
1934
  " ",
1881
- t("services_name").padEnd(22)
1935
+ t("services_name").padEnd(svcNameWidth)
1882
1936
  ] }),
1883
- /* @__PURE__ */ jsx18(Text18, { bold: true, color: "#F9FAFB", children: t("services_status").padEnd(12) }),
1937
+ /* @__PURE__ */ jsx18(Text18, { bold: true, color: "#F9FAFB", children: t("services_status").padEnd(svcStatusWidth) }),
1884
1938
  /* @__PURE__ */ jsx18(Text18, { bold: true, color: "#F9FAFB", children: t("services_user") })
1885
1939
  ] }),
1886
1940
  services.map((svc, i) => {
1887
1941
  const isCurrent = i === cursor;
1888
1942
  return /* @__PURE__ */ jsxs18(Box16, { gap: 1, children: [
1889
1943
  /* @__PURE__ */ jsx18(Text18, { color: isCurrent ? "#22C55E" : "#9CA3AF", children: isCurrent ? "\u25B6" : " " }),
1890
- /* @__PURE__ */ jsx18(Text18, { bold: isCurrent, inverse: isCurrent, color: isCurrent ? "#F9FAFB" : "#9CA3AF", children: svc.name.padEnd(20) }),
1944
+ /* @__PURE__ */ jsx18(Text18, { bold: isCurrent, inverse: isCurrent, color: isCurrent ? "#F9FAFB" : "#9CA3AF", children: svc.name.padEnd(svcNameWidth - 2) }),
1891
1945
  /* @__PURE__ */ jsx18(StatusBadge, { label: svc.status, variant: STATUS_VARIANTS[svc.status] }),
1892
1946
  /* @__PURE__ */ jsx18(Text18, { color: "#9CA3AF", children: svc.user ?? "-" }),
1893
1947
  svc.exit_code != null && svc.exit_code !== 0 && /* @__PURE__ */ jsx18(Text18, { color: "#EF4444", children: t("common_exit", { code: svc.exit_code }) })
@@ -1895,6 +1949,7 @@ function ServicesView() {
1895
1949
  })
1896
1950
  ] }),
1897
1951
  actionInProgress && /* @__PURE__ */ jsx18(Text18, { color: "#38BDF8", children: t("services_processing") }),
1952
+ serviceActionError && /* @__PURE__ */ jsx18(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx18(Text18, { color: "#EF4444", children: serviceActionError }) }),
1898
1953
  /* @__PURE__ */ jsx18(Box16, { marginTop: 1, children: /* @__PURE__ */ jsxs18(Text18, { color: "#F9FAFB", bold: true, children: [
1899
1954
  cursor + 1,
1900
1955
  "/",
@@ -1926,7 +1981,7 @@ function DoctorView() {
1926
1981
  t("doctor_clean")
1927
1982
  ] }) }),
1928
1983
  doctorClean === false && doctorWarnings.length === 0 && /* @__PURE__ */ jsx19(Text19, { color: "#F59E0B", children: t("doctor_warningsNotCaptured") }),
1929
- doctorWarnings.map((warning, i) => /* @__PURE__ */ jsx19(Box17, { flexDirection: "column", marginBottom: 1, borderStyle: "single", borderColor: "#F59E0B", paddingX: 1, children: warning.split("\n").map((line, j) => /* @__PURE__ */ jsx19(Text19, { color: j === 0 ? "#F59E0B" : "#9CA3AF", children: line }, j)) }, i))
1984
+ doctorWarnings.map((warning, i) => /* @__PURE__ */ jsx19(Box17, { flexDirection: "column", marginBottom: 1, borderStyle: "single", borderColor: "#F59E0B", paddingX: 1, children: warning.split("\n").map((line, j) => /* @__PURE__ */ jsx19(Text19, { color: j === 0 ? "#F59E0B" : "#9CA3AF", children: line }, j)) }, warning.slice(0, 50) + i))
1930
1985
  ] }),
1931
1986
  /* @__PURE__ */ jsx19(Box17, { marginTop: 1, children: /* @__PURE__ */ jsx19(Text19, { color: "#F9FAFB", bold: true, children: doctorWarnings.length > 0 ? tp("plural_warnings", doctorWarnings.length) : "" }) })
1932
1987
  ] });
@@ -1945,13 +2000,16 @@ import { readFile, writeFile, readdir, rm } from "fs/promises";
1945
2000
  import { join, basename } from "path";
1946
2001
 
1947
2002
  // src/lib/license/watermark.ts
1948
- function getWatermark() {
1949
- const license = useLicenseStore.getState().license;
2003
+ function getWatermark(license) {
1950
2004
  if (!license?.customerEmail) return "";
1951
2005
  return `Licensed to: ${license.customerEmail}`;
1952
2006
  }
1953
2007
 
1954
2008
  // src/lib/profiles/profile-manager.ts
2009
+ function proCheck() {
2010
+ const { license, status } = useLicenseStore.getState();
2011
+ requirePro(license, status);
2012
+ }
1955
2013
  var MAX_PROFILE_NAME_LENGTH = 100;
1956
2014
  function validateProfileName(name) {
1957
2015
  if (!name || name.trim().length === 0) {
@@ -1969,7 +2027,7 @@ function profilePath(name) {
1969
2027
  return join(PROFILES_DIR, `${basename(name)}.json`);
1970
2028
  }
1971
2029
  async function listProfiles() {
1972
- requirePro();
2030
+ proCheck();
1973
2031
  await ensureDataDirs();
1974
2032
  try {
1975
2033
  const files = await readdir(PROFILES_DIR);
@@ -1979,7 +2037,7 @@ async function listProfiles() {
1979
2037
  }
1980
2038
  }
1981
2039
  async function loadProfile(name) {
1982
- requirePro();
2040
+ proCheck();
1983
2041
  const raw = await readFile(profilePath(name), "utf-8");
1984
2042
  let file;
1985
2043
  try {
@@ -1987,26 +2045,29 @@ async function loadProfile(name) {
1987
2045
  } catch (err) {
1988
2046
  throw new Error(`Profile "${name}" is corrupted: ${err instanceof Error ? err.message : String(err)}`);
1989
2047
  }
2048
+ if (file.version !== 1) {
2049
+ throw new Error("Unsupported data version");
2050
+ }
1990
2051
  if (!file.profile) {
1991
2052
  throw new Error(`Profile "${name}" is missing required data`);
1992
2053
  }
1993
2054
  return file.profile;
1994
2055
  }
1995
2056
  async function saveProfile(profile) {
1996
- requirePro();
2057
+ proCheck();
1997
2058
  await ensureDataDirs();
1998
2059
  const file = { version: 1, profile };
1999
- await writeFile(profilePath(profile.name), JSON.stringify(file, null, 2), "utf-8");
2060
+ await writeFile(profilePath(profile.name), JSON.stringify(file, null, 2), { encoding: "utf-8", mode: 384 });
2000
2061
  }
2001
2062
  async function deleteProfile(name) {
2002
- requirePro();
2063
+ proCheck();
2003
2064
  try {
2004
2065
  await rm(profilePath(name));
2005
2066
  } catch {
2006
2067
  }
2007
2068
  }
2008
- async function exportCurrentSetup(name, description) {
2009
- requirePro();
2069
+ async function exportCurrentSetup(name, description, license = null) {
2070
+ proCheck();
2010
2071
  const [installed, leaves, tapsRaw] = await Promise.all([
2011
2072
  getInstalled(),
2012
2073
  getLeaves(),
@@ -2023,14 +2084,14 @@ async function exportCurrentSetup(name, description) {
2023
2084
  formulae: leaves,
2024
2085
  casks,
2025
2086
  taps,
2026
- exportedBy: getWatermark()
2087
+ exportedBy: getWatermark(license)
2027
2088
  // Layer 16: Watermark — who exported this profile
2028
2089
  };
2029
2090
  await saveProfile(profile);
2030
2091
  return profile;
2031
2092
  }
2032
2093
  async function updateProfile(oldName, newName, newDescription) {
2033
- requirePro();
2094
+ proCheck();
2034
2095
  const profile = await loadProfile(oldName);
2035
2096
  if (oldName !== newName) {
2036
2097
  await deleteProfile(oldName);
@@ -2043,12 +2104,18 @@ async function updateProfile(oldName, newName, newDescription) {
2043
2104
  };
2044
2105
  await saveProfile(updated);
2045
2106
  }
2107
+ var TAP_PATTERN = /^[a-z0-9][-a-z0-9]*\/[a-z0-9][-a-z0-9]*$/;
2108
+ var PKG_PATTERN = /^[a-z0-9][-a-z0-9_.@+]*$/;
2046
2109
  async function* importProfile(profile) {
2047
- requirePro();
2110
+ proCheck();
2048
2111
  const installed = await getInstalled();
2049
2112
  const installedFormulae = new Set(installed.formulae.map((f) => f.name));
2050
2113
  const installedCasks = new Set(installed.casks.filter((c) => c.installed).map((c) => c.token));
2051
2114
  for (const tap of profile.taps) {
2115
+ if (!TAP_PATTERN.test(tap)) {
2116
+ yield `Skipping invalid tap name: ${tap}`;
2117
+ continue;
2118
+ }
2052
2119
  yield t("profileMgr_tapping", { name: tap });
2053
2120
  try {
2054
2121
  await execBrew(["tap", tap]);
@@ -2057,6 +2124,10 @@ async function* importProfile(profile) {
2057
2124
  }
2058
2125
  const missingFormulae = profile.formulae.filter((f) => !installedFormulae.has(f));
2059
2126
  for (const name of missingFormulae) {
2127
+ if (!PKG_PATTERN.test(name)) {
2128
+ yield `Skipping invalid formula name: ${name}`;
2129
+ continue;
2130
+ }
2060
2131
  yield t("profileMgr_installing", { name });
2061
2132
  for await (const line of streamBrew(["install", name])) {
2062
2133
  yield line;
@@ -2064,6 +2135,10 @@ async function* importProfile(profile) {
2064
2135
  }
2065
2136
  const missingCasks = profile.casks.filter((c) => !installedCasks.has(c));
2066
2137
  for (const name of missingCasks) {
2138
+ if (!PKG_PATTERN.test(name)) {
2139
+ yield `Skipping invalid cask name: ${name}`;
2140
+ continue;
2141
+ }
2067
2142
  yield t("profileMgr_installingCask", { name });
2068
2143
  for await (const line of streamBrew(["install", "--cask", name])) {
2069
2144
  yield line;
@@ -2096,7 +2171,8 @@ var useProfileStore = create4((set) => ({
2096
2171
  exportCurrent: async (name, description) => {
2097
2172
  set({ loading: true, loadError: null });
2098
2173
  try {
2099
- await exportCurrentSetup(name, description);
2174
+ const license = useLicenseStore.getState().license;
2175
+ await exportCurrentSetup(name, description, license);
2100
2176
  const names = await listProfiles();
2101
2177
  set({ profileNames: names, loading: false });
2102
2178
  } catch (err) {
@@ -2190,6 +2266,9 @@ function ProfilesView() {
2190
2266
  setMode("edit-name");
2191
2267
  }
2192
2268
  }, { isActive: mode === "detail" });
2269
+ useInput9(() => {
2270
+ setMode("list");
2271
+ }, { isActive: mode === "importing" && !importRunning });
2193
2272
  const startImport = async (name) => {
2194
2273
  setMode("importing");
2195
2274
  setImportLines([]);
@@ -2393,7 +2472,8 @@ async function getCellarPath(name) {
2393
2472
  }
2394
2473
  }
2395
2474
  async function analyzeCleanup(formulae, leaves) {
2396
- requirePro();
2475
+ const { license, status } = useLicenseStore.getState();
2476
+ requirePro(license, status);
2397
2477
  const leavesSet = new Set(leaves);
2398
2478
  const reverseDeps = /* @__PURE__ */ new Map();
2399
2479
  for (const f of formulae) {
@@ -2629,8 +2709,8 @@ function SmartCleanupView() {
2629
2709
  }
2630
2710
 
2631
2711
  // src/views/history.tsx
2632
- import { useEffect as useEffect13, useState as useState10, useMemo as useMemo3 } from "react";
2633
- import { Box as Box20, Text as Text22, useInput as useInput11 } from "ink";
2712
+ import { useEffect as useEffect13, useState as useState10, useMemo as useMemo4 } from "react";
2713
+ import { Box as Box20, Text as Text22, useInput as useInput11, useStdout as useStdout4 } from "ink";
2634
2714
 
2635
2715
  // src/stores/history-store.ts
2636
2716
  import { create as create6 } from "zustand";
@@ -2696,7 +2776,7 @@ function HistoryView() {
2696
2776
  }
2697
2777
  return void 0;
2698
2778
  }, [isSearching]);
2699
- const filtered = useMemo3(() => {
2779
+ const filtered = useMemo4(() => {
2700
2780
  let result = entries;
2701
2781
  if (filter !== "all") {
2702
2782
  result = result.filter((e) => e.action === filter);
@@ -2739,7 +2819,8 @@ function HistoryView() {
2739
2819
  });
2740
2820
  if (loading) return /* @__PURE__ */ jsx22(Loading, { message: t("loading_history") });
2741
2821
  if (error) return /* @__PURE__ */ jsx22(ErrorMessage, { message: error });
2742
- const MAX_VISIBLE_ROWS = 20;
2822
+ const { stdout } = useStdout4();
2823
+ const MAX_VISIBLE_ROWS = Math.max(5, (stdout?.rows ?? 24) - 8);
2743
2824
  const start = Math.max(0, cursor - Math.floor(MAX_VISIBLE_ROWS / 2));
2744
2825
  const visible = filtered.slice(start, start + MAX_VISIBLE_ROWS);
2745
2826
  return /* @__PURE__ */ jsxs22(Box20, { flexDirection: "column", children: [
@@ -2857,11 +2938,11 @@ function getFixedVersion(vuln) {
2857
2938
  }
2858
2939
  var BATCH_SIZE = 100;
2859
2940
  async function queryBatch(packages, queries) {
2860
- const res = await fetch(OSV_BATCH_URL, {
2941
+ const res = await fetchWithTimeout(OSV_BATCH_URL, {
2861
2942
  method: "POST",
2862
2943
  headers: { "Content-Type": "application/json" },
2863
2944
  body: JSON.stringify({ queries })
2864
- });
2945
+ }, 15e3);
2865
2946
  if (!res.ok) {
2866
2947
  if (res.status === 400 && queries.length > 1) {
2867
2948
  return queryOneByOne(packages);
@@ -2923,7 +3004,8 @@ var SEVERITY_ORDER = {
2923
3004
  UNKNOWN: 0
2924
3005
  };
2925
3006
  async function runSecurityAudit(formulae, casks) {
2926
- requirePro();
3007
+ const { license, status } = useLicenseStore.getState();
3008
+ requirePro(license, status);
2927
3009
  const packages = [];
2928
3010
  for (const f of formulae) {
2929
3011
  const version = f.installed[0]?.version ?? f.versions.stable;
@@ -3136,8 +3218,11 @@ function AccountView() {
3136
3218
  onConfirm: async () => {
3137
3219
  setConfirmDeactivate(false);
3138
3220
  setDeactivating(true);
3139
- await deactivate2();
3140
- setDeactivating(false);
3221
+ try {
3222
+ await deactivate2();
3223
+ } finally {
3224
+ setDeactivating(false);
3225
+ }
3141
3226
  },
3142
3227
  onCancel: () => setConfirmDeactivate(false)
3143
3228
  }
@@ -3189,6 +3274,11 @@ function AccountView() {
3189
3274
  /* @__PURE__ */ jsx24(Text24, { children: t("account_unlockDesc") }),
3190
3275
  /* @__PURE__ */ jsx24(Text24, { color: "#06B6D4", bold: true, children: t("account_pricing") }),
3191
3276
  /* @__PURE__ */ jsx24(Text24, { children: " " }),
3277
+ /* @__PURE__ */ jsxs24(Text24, { color: "#9CA3AF", children: [
3278
+ t("upgrade_buyAt"),
3279
+ " ",
3280
+ /* @__PURE__ */ jsx24(Text24, { color: "#38BDF8", bold: true, children: t("upgrade_buyUrl") })
3281
+ ] }),
3192
3282
  /* @__PURE__ */ jsxs24(Text24, { color: "#9CA3AF", children: [
3193
3283
  t("account_runActivate"),
3194
3284
  " ",
@@ -3201,7 +3291,7 @@ function AccountView() {
3201
3291
  /* @__PURE__ */ jsx24(Box22, { marginTop: 2, children: /* @__PURE__ */ jsxs24(Text24, { color: "#6B7280", children: [
3202
3292
  status === "pro" ? `d:${t("hint_deactivate")}` : "",
3203
3293
  " ",
3204
- t("app_version", { version: "0.1.0" })
3294
+ t("app_version", { version: "0.2.0" })
3205
3295
  ] }) })
3206
3296
  ] });
3207
3297
  }
@@ -3288,8 +3378,11 @@ async function runCli() {
3288
3378
  console.log(t("cli_deactivateCancelled"));
3289
3379
  return;
3290
3380
  }
3291
- await deactivate(license);
3381
+ const { remoteSuccess } = await deactivate(license);
3292
3382
  console.log(t("cli_deactivated"));
3383
+ if (!remoteSuccess) {
3384
+ console.warn(t("cli_deactivateRemoteFailed"));
3385
+ }
3293
3386
  return;
3294
3387
  }
3295
3388
  if (command === "status") {
@@ -3308,7 +3401,7 @@ async function runCli() {
3308
3401
  return;
3309
3402
  }
3310
3403
  if (command === "install-brewbar") {
3311
- const { installBrewBar } = await import("./brewbar-installer-CPCOE3MI.js");
3404
+ const { installBrewBar } = await import("./brewbar-installer-4Z2WE57I.js");
3312
3405
  try {
3313
3406
  await installBrewBar(arg === "--force");
3314
3407
  console.log(t("cli_brewbarInstalled"));
@@ -3319,7 +3412,7 @@ async function runCli() {
3319
3412
  return;
3320
3413
  }
3321
3414
  if (command === "uninstall-brewbar") {
3322
- const { uninstallBrewBar } = await import("./brewbar-installer-CPCOE3MI.js");
3415
+ const { uninstallBrewBar } = await import("./brewbar-installer-4Z2WE57I.js");
3323
3416
  try {
3324
3417
  await uninstallBrewBar();
3325
3418
  console.log(t("cli_brewbarUninstalled"));
@@ -3335,4 +3428,3 @@ runCli().catch((err) => {
3335
3428
  console.error(err instanceof Error ? err.message : String(err));
3336
3429
  process.exit(1);
3337
3430
  });
3338
- //# sourceMappingURL=index.js.map