brew-tui 0.1.0 → 0.3.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,23 +1,25 @@
1
1
  import {
2
+ DATA_DIR,
3
+ LICENSE_PATH,
4
+ PROFILES_DIR,
2
5
  appendEntry,
3
6
  clearHistory,
7
+ detectAction,
8
+ ensureDataDirs,
4
9
  loadHistory
5
- } from "./chunk-3BK3B53S.js";
10
+ } from "./chunk-65YZJX2E.js";
6
11
  import {
7
- PROFILES_DIR,
8
- activate,
9
- deactivate,
10
- ensureDataDirs,
11
- loadLicense,
12
- requirePro,
12
+ fetchWithTimeout,
13
+ getLocale,
14
+ logger,
13
15
  t,
14
16
  tp,
15
- useLicenseStore,
16
17
  useLocaleStore
17
- } from "./chunk-P6PTN4HR.js";
18
+ } from "./chunk-PTLSNG2N.js";
18
19
 
19
20
  // src/index.tsx
20
21
  import { createInterface } from "readline/promises";
22
+ import { rm as rm3 } from "fs/promises";
21
23
  import { render } from "ink";
22
24
 
23
25
  // src/app.tsx
@@ -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
@@ -49,28 +51,27 @@ var VIEWS = [
49
51
  ];
50
52
  var useNavigationStore = create((set, get) => ({
51
53
  currentView: "dashboard",
52
- previousView: null,
53
54
  selectedPackage: null,
54
- viewHistory: ["dashboard"],
55
+ selectedPackageType: null,
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
- previousView: currentView,
61
- viewHistory: [...viewHistory.slice(-19), view]
62
+ viewHistory: [...viewHistory.slice(-19), currentView]
62
63
  });
63
64
  },
64
65
  goBack: () => {
65
- const { previousView } = get();
66
- if (previousView) {
67
- set((state) => ({
68
- currentView: previousView,
69
- previousView: state.currentView
70
- }));
71
- }
66
+ const { viewHistory } = get();
67
+ if (viewHistory.length === 0) return;
68
+ const prev = viewHistory[viewHistory.length - 1];
69
+ set({
70
+ currentView: prev,
71
+ viewHistory: viewHistory.slice(0, -1)
72
+ });
72
73
  },
73
- selectPackage: (name) => set({ selectedPackage: name })
74
+ selectPackage: (name, type = null) => set({ selectedPackage: name, selectedPackageType: type })
74
75
  }));
75
76
  function getNextView(current) {
76
77
  const idx = VIEWS.indexOf(current);
@@ -92,7 +93,26 @@ function isProView(viewId) {
92
93
  return PRO_VIEWS.has(viewId);
93
94
  }
94
95
 
96
+ // src/utils/colors.ts
97
+ var COLORS = {
98
+ success: "#22C55E",
99
+ error: "#EF4444",
100
+ warning: "#F59E0B",
101
+ info: "#06B6D4",
102
+ brand: "#FF6B2B",
103
+ muted: "#9CA3AF",
104
+ text: "#F9FAFB",
105
+ textSecondary: "#6B7280",
106
+ teal: "#2DD4BF",
107
+ sky: "#38BDF8",
108
+ gold: "#FFD700",
109
+ purple: "#A855F7",
110
+ blue: "#3B82F6",
111
+ border: "#4B5563"
112
+ };
113
+
95
114
  // src/utils/gradient.tsx
115
+ import React, { useMemo } from "react";
96
116
  import { Text } from "ink";
97
117
  import { Fragment, jsx } from "react/jsx-runtime";
98
118
  function hexToRgb(hex) {
@@ -110,22 +130,25 @@ function interpolateColor(c1, c2, t2) {
110
130
  const [r2, g2, b2] = hexToRgb(c2);
111
131
  return rgbToHex(lerp(r1, r2, t2), lerp(g1, g2, t2), lerp(b1, b2, t2));
112
132
  }
113
- function GradientText({ children, colors, bold }) {
133
+ var GradientText = React.memo(function GradientText2({ children, colors, bold }) {
114
134
  if (colors.length < 2) {
115
135
  return /* @__PURE__ */ jsx(Text, { color: colors[0], bold, children });
116
136
  }
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
- }
137
+ const coloredChars = useMemo(() => {
138
+ const chars = [...children];
139
+ const maxIdx = Math.max(chars.length - 1, 1);
140
+ return chars.map((char, i) => {
141
+ const t2 = i / maxIdx;
142
+ const segment = t2 * (colors.length - 1);
143
+ const lower = Math.floor(segment);
144
+ const upper = Math.min(lower + 1, colors.length - 1);
145
+ const frac = segment - lower;
146
+ const color = interpolateColor(colors[lower], colors[upper], frac);
147
+ return { char, color, key: `${i}-${color}` };
148
+ });
149
+ }, [children, colors]);
150
+ return /* @__PURE__ */ jsx(Fragment, { children: coloredChars.map(({ char, color, key }) => /* @__PURE__ */ jsx(Text, { color, bold, children: char }, key)) });
151
+ });
129
152
  var GRADIENTS = {
130
153
  gold: ["#FFD700", "#FFA500", "#B8860B"],
131
154
  sunset: ["#FF6B2B", "#FFD700", "#FF6B2B"],
@@ -187,6 +210,7 @@ var TAB_VIEWS = [
187
210
  "installed",
188
211
  "search",
189
212
  "outdated",
213
+ "package-info",
190
214
  "services",
191
215
  "doctor",
192
216
  "profiles",
@@ -203,13 +227,13 @@ function Header() {
203
227
  /* @__PURE__ */ jsx2(GradientText, { colors: GRADIENTS.gold, children: brew }),
204
228
  /* @__PURE__ */ jsx2(GradientText, { colors: ["#B8860B", "#8B6914", "#6B4F10"], children: LOGO_TUI[i] })
205
229
  ] }, i)) }),
206
- /* @__PURE__ */ jsx2(Box, { borderStyle: "single", borderBottom: true, borderLeft: false, borderRight: false, borderTop: false, borderColor: "#FFD700", paddingX: 1, flexWrap: "wrap", children: TAB_VIEWS.map((view, i) => {
230
+ /* @__PURE__ */ jsx2(Box, { borderStyle: "single", borderBottom: true, borderLeft: false, borderRight: false, borderTop: false, borderColor: COLORS.gold, paddingX: 1, flexWrap: "wrap", children: TAB_VIEWS.map((view, i) => {
207
231
  const key = VIEW_KEYS[view];
208
232
  const viewLabel = t(VIEW_LABEL_KEYS[view]);
209
233
  const label = key ? `${key}:${viewLabel}` : viewLabel;
210
234
  const isPro = isProView(view);
211
- return /* @__PURE__ */ jsxs(React.Fragment, { children: [
212
- i > 0 && /* @__PURE__ */ jsxs(Text2, { color: "#4B5563", children: [
235
+ return /* @__PURE__ */ jsxs(React2.Fragment, { children: [
236
+ i > 0 && /* @__PURE__ */ jsxs(Text2, { color: COLORS.border, children: [
213
237
  " ",
214
238
  "\u2502",
215
239
  " "
@@ -218,12 +242,12 @@ function Header() {
218
242
  Text2,
219
243
  {
220
244
  bold: view === currentView,
221
- color: view === currentView ? "#22C55E" : "#6B7280",
245
+ color: view === currentView ? COLORS.success : COLORS.textSecondary,
222
246
  underline: view === currentView,
223
247
  children: view === currentView ? `\u25CF ${label}` : label
224
248
  }
225
249
  ),
226
- isPro && /* @__PURE__ */ jsxs(Text2, { color: "#FF6B2B", bold: true, children: [
250
+ isPro && /* @__PURE__ */ jsxs(Text2, { color: COLORS.brand, bold: true, children: [
227
251
  " ",
228
252
  t("pro_badge")
229
253
  ] })
@@ -233,7 +257,7 @@ function Header() {
233
257
  }
234
258
 
235
259
  // src/components/layout/footer.tsx
236
- import React2 from "react";
260
+ import React3 from "react";
237
261
  import { Box as Box2, Text as Text3 } from "ink";
238
262
  import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
239
263
  var VIEW_HINT_DEFS = {
@@ -251,22 +275,22 @@ var VIEW_HINT_DEFS = {
251
275
  account: [["d", "hint_deactivate"], ["q", "hint_quit"]]
252
276
  };
253
277
  function HintItem({ def }) {
254
- if (def.length === 1) return /* @__PURE__ */ jsx3(Text3, { color: "#FFD700", dimColor: true, children: t(def[0]) });
278
+ if (def.length === 1) return /* @__PURE__ */ jsx3(Text3, { color: COLORS.gold, dimColor: true, children: t(def[0]) });
255
279
  return /* @__PURE__ */ jsxs2(Fragment2, { children: [
256
- /* @__PURE__ */ jsx3(Text3, { color: "#F9FAFB", bold: true, children: def[0] }),
257
- /* @__PURE__ */ jsx3(Text3, { color: "#6B7280", children: ":" }),
258
- /* @__PURE__ */ jsx3(Text3, { color: "#FFD700", dimColor: true, children: t(def[1]) })
280
+ /* @__PURE__ */ jsx3(Text3, { color: COLORS.text, bold: true, children: def[0] }),
281
+ /* @__PURE__ */ jsx3(Text3, { color: COLORS.textSecondary, children: ":" }),
282
+ /* @__PURE__ */ jsx3(Text3, { color: COLORS.gold, dimColor: true, children: t(def[1]) })
259
283
  ] });
260
284
  }
261
285
  function Footer() {
262
286
  const currentView = useNavigationStore((s) => s.currentView);
263
287
  const locale = useLocaleStore((s) => s.locale);
264
288
  const defs = VIEW_HINT_DEFS[currentView] ?? [];
265
- return /* @__PURE__ */ jsxs2(Box2, { borderStyle: "single", borderTop: true, borderBottom: false, borderLeft: false, borderRight: false, borderColor: "#FFD700", paddingX: 1, flexWrap: "wrap", children: [
289
+ return /* @__PURE__ */ jsxs2(Box2, { borderStyle: "single", borderTop: true, borderBottom: false, borderLeft: false, borderRight: false, borderColor: COLORS.gold, paddingX: 1, flexWrap: "wrap", children: [
266
290
  defs.map((def, i) => {
267
291
  const key = def.length === 1 ? def[0] : `${def[0]}:${def[1]}`;
268
- return /* @__PURE__ */ jsxs2(React2.Fragment, { children: [
269
- i > 0 && /* @__PURE__ */ jsxs2(Text3, { color: "#4B5563", children: [
292
+ return /* @__PURE__ */ jsxs2(React3.Fragment, { children: [
293
+ i > 0 && /* @__PURE__ */ jsxs2(Text3, { color: COLORS.border, children: [
270
294
  " ",
271
295
  "\u2502",
272
296
  " "
@@ -274,14 +298,14 @@ function Footer() {
274
298
  /* @__PURE__ */ jsx3(HintItem, { def })
275
299
  ] }, key);
276
300
  }),
277
- /* @__PURE__ */ jsxs2(Text3, { color: "#4B5563", children: [
301
+ /* @__PURE__ */ jsxs2(Text3, { color: COLORS.border, children: [
278
302
  " ",
279
303
  "\u2502",
280
304
  " "
281
305
  ] }),
282
- /* @__PURE__ */ jsx3(Text3, { color: "#F9FAFB", bold: true, children: "L" }),
283
- /* @__PURE__ */ jsx3(Text3, { color: "#6B7280", children: ":" }),
284
- /* @__PURE__ */ jsxs2(Text3, { color: "#FFD700", dimColor: true, children: [
306
+ /* @__PURE__ */ jsx3(Text3, { color: COLORS.text, bold: true, children: "L" }),
307
+ /* @__PURE__ */ jsx3(Text3, { color: COLORS.textSecondary, children: ":" }),
308
+ /* @__PURE__ */ jsxs2(Text3, { color: COLORS.gold, dimColor: true, children: [
285
309
  t("hint_lang"),
286
310
  "(",
287
311
  locale,
@@ -300,12 +324,469 @@ function AppLayout({ children }) {
300
324
  ] });
301
325
  }
302
326
 
327
+ // src/stores/license-store.ts
328
+ import { create as create2 } from "zustand";
329
+
330
+ // src/lib/license/license-manager.ts
331
+ import { readFile as readFile2, writeFile as writeFile2, rename, rm } from "fs/promises";
332
+ import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from "crypto";
333
+
334
+ // src/lib/license/polar-api.ts
335
+ import { randomUUID } from "crypto";
336
+ import { readFile, writeFile, mkdir } from "fs/promises";
337
+ import { join } from "path";
338
+ import { homedir } from "os";
339
+ var BASE_URL = "https://api.polar.sh/v1/customer-portal/license-keys";
340
+ var POLAR_ORGANIZATION_ID = "b8f245c0-d116-4457-92fb-1bda47139f82";
341
+ var DATA_DIR2 = join(homedir(), ".brew-tui");
342
+ var MACHINE_ID_PATH = join(DATA_DIR2, "machine-id");
343
+ async function getMachineId() {
344
+ try {
345
+ const id2 = (await readFile(MACHINE_ID_PATH, "utf-8")).trim();
346
+ if (id2) return id2;
347
+ } catch {
348
+ }
349
+ const id = randomUUID();
350
+ await mkdir(DATA_DIR2, { recursive: true, mode: 448 });
351
+ await writeFile(MACHINE_ID_PATH, id, { encoding: "utf-8", mode: 384 });
352
+ return id;
353
+ }
354
+ function validateApiUrl(url) {
355
+ const parsed = new URL(url);
356
+ if (parsed.protocol !== "https:") {
357
+ throw new Error("HTTPS required for license API");
358
+ }
359
+ if (!parsed.hostname.endsWith("polar.sh")) {
360
+ throw new Error("Invalid API host");
361
+ }
362
+ }
363
+ async function post(endpoint, body, expectEmpty = false) {
364
+ const url = `${BASE_URL}/${endpoint}`;
365
+ validateApiUrl(url);
366
+ const res = await fetchWithTimeout(url, {
367
+ method: "POST",
368
+ headers: { "Content-Type": "application/json" },
369
+ body: JSON.stringify(body)
370
+ }, 15e3);
371
+ if (!res.ok) {
372
+ let message = `Request failed with status ${res.status}`;
373
+ try {
374
+ const errBody = await res.json();
375
+ if (typeof errBody.detail === "string") message = errBody.detail;
376
+ else if (typeof errBody.error === "string") message = errBody.error;
377
+ else if (typeof errBody.message === "string") message = errBody.message;
378
+ } catch {
379
+ }
380
+ throw new Error(message);
381
+ }
382
+ if (expectEmpty || res.status === 204) return void 0;
383
+ return res.json();
384
+ }
385
+ async function activateLicense(key) {
386
+ const machineId = await getMachineId();
387
+ const activation = await post("activate", {
388
+ key,
389
+ organization_id: POLAR_ORGANIZATION_ID,
390
+ label: machineId
391
+ // SEG-004: Use machine UUID instead of hostname
392
+ });
393
+ if (!activation || typeof activation.id !== "string" || !activation.license_key) {
394
+ throw new Error("Invalid activation response: missing required fields");
395
+ }
396
+ let customerEmail = "";
397
+ let customerName = "";
398
+ try {
399
+ const validated = await post("validate", {
400
+ key,
401
+ organization_id: POLAR_ORGANIZATION_ID,
402
+ activation_id: activation.id
403
+ });
404
+ customerEmail = validated.customer?.email ?? "";
405
+ customerName = validated.customer?.name ?? "";
406
+ } catch {
407
+ }
408
+ return {
409
+ activated: true,
410
+ error: null,
411
+ instance: { id: activation.id },
412
+ license_key: {
413
+ id: 0,
414
+ status: activation.license_key.status,
415
+ key,
416
+ activation_limit: 0,
417
+ activations_count: 0,
418
+ expires_at: activation.license_key.expires_at
419
+ },
420
+ meta: { customer_email: customerEmail, customer_name: customerName }
421
+ };
422
+ }
423
+ async function validateLicense(key, instanceId) {
424
+ const res = await post("validate", {
425
+ key,
426
+ organization_id: POLAR_ORGANIZATION_ID,
427
+ activation_id: instanceId
428
+ });
429
+ if (!res || typeof res.id !== "string" || typeof res.status !== "string" || !res.customer) {
430
+ throw new Error("Invalid validation response: missing required fields");
431
+ }
432
+ const notExpired = res.expires_at === null || new Date(res.expires_at) > /* @__PURE__ */ new Date();
433
+ const valid = res.status === "granted" && notExpired;
434
+ return {
435
+ valid,
436
+ error: valid ? null : `License ${res.status}`,
437
+ license_key: {
438
+ id: 0,
439
+ status: res.status,
440
+ key,
441
+ expires_at: res.expires_at
442
+ },
443
+ instance: { id: instanceId }
444
+ };
445
+ }
446
+ async function deactivateLicense(key, instanceId) {
447
+ await post(
448
+ "deactivate",
449
+ { key, organization_id: POLAR_ORGANIZATION_ID, activation_id: instanceId },
450
+ true
451
+ );
452
+ }
453
+
454
+ // src/lib/license/license-manager.ts
455
+ var REVALIDATION_INTERVAL_MS = 24 * 60 * 60 * 1e3;
456
+ var GRACE_PERIOD_MS = 7 * 24 * 60 * 60 * 1e3;
457
+ var ACTIVATION_COOLDOWN_MS = 3e4;
458
+ var MAX_ATTEMPTS = 5;
459
+ var LOCKOUT_MS = 15 * 60 * 1e3;
460
+ var tracker = {
461
+ attempts: 0,
462
+ lastAttempt: 0,
463
+ lockedUntil: 0
464
+ };
465
+ function checkRateLimit() {
466
+ const now = Date.now();
467
+ if (now < tracker.lockedUntil) {
468
+ const remaining = Math.ceil((tracker.lockedUntil - now) / 6e4);
469
+ throw new Error(t("cli_rateLimited", { minutes: remaining }));
470
+ }
471
+ if (now - tracker.lastAttempt < ACTIVATION_COOLDOWN_MS) {
472
+ throw new Error(t("cli_cooldown"));
473
+ }
474
+ }
475
+ function recordAttempt(success) {
476
+ const now = Date.now();
477
+ tracker.lastAttempt = now;
478
+ if (success) {
479
+ tracker.attempts = 0;
480
+ return;
481
+ }
482
+ tracker.attempts++;
483
+ if (tracker.attempts >= MAX_ATTEMPTS) {
484
+ tracker.lockedUntil = now + LOCKOUT_MS;
485
+ tracker.attempts = 0;
486
+ }
487
+ }
488
+ var ENCRYPTION_SECRET = "brew-tui-license-aes256gcm-v1";
489
+ var SCRYPT_SALT = "brew-tui-salt-v1";
490
+ var _derivedKey = null;
491
+ function deriveEncryptionKey() {
492
+ if (!_derivedKey) _derivedKey = scryptSync(ENCRYPTION_SECRET, SCRYPT_SALT, 32);
493
+ return _derivedKey;
494
+ }
495
+ function encryptLicenseData(data) {
496
+ const key = deriveEncryptionKey();
497
+ const iv = randomBytes(12);
498
+ const cipher = createCipheriv("aes-256-gcm", key, iv);
499
+ const plaintext = JSON.stringify(data);
500
+ const ciphertext = Buffer.concat([cipher.update(plaintext, "utf-8"), cipher.final()]);
501
+ const tag = cipher.getAuthTag();
502
+ return {
503
+ encrypted: ciphertext.toString("base64"),
504
+ iv: iv.toString("base64"),
505
+ tag: tag.toString("base64")
506
+ };
507
+ }
508
+ function decryptLicenseData(encrypted, iv, tag) {
509
+ const key = deriveEncryptionKey();
510
+ const decipher = createDecipheriv(
511
+ "aes-256-gcm",
512
+ key,
513
+ Buffer.from(iv, "base64")
514
+ );
515
+ decipher.setAuthTag(Buffer.from(tag, "base64"));
516
+ const plaintext = Buffer.concat([
517
+ decipher.update(Buffer.from(encrypted, "base64")),
518
+ decipher.final()
519
+ ]);
520
+ return JSON.parse(plaintext.toString("utf-8"));
521
+ }
522
+ function isLicenseFile(obj) {
523
+ return typeof obj === "object" && obj !== null && obj.version === 1;
524
+ }
525
+ function isEncryptedLicenseFile(obj) {
526
+ if (!isLicenseFile(obj)) return false;
527
+ const record = obj;
528
+ return typeof record.encrypted === "string" && typeof record.iv === "string" && typeof record.tag === "string";
529
+ }
530
+ async function getMachineId2() {
531
+ try {
532
+ const { readFile: readMachineId } = await import("fs/promises");
533
+ const { join: join3 } = await import("path");
534
+ const { homedir: homedir2 } = await import("os");
535
+ const machineIdPath = join3(homedir2(), ".brew-tui", "machine-id");
536
+ return (await readMachineId(machineIdPath, "utf-8")).trim() || null;
537
+ } catch {
538
+ return null;
539
+ }
540
+ }
541
+ async function loadLicense() {
542
+ try {
543
+ const raw = await readFile2(LICENSE_PATH, "utf-8");
544
+ const parsed = JSON.parse(raw);
545
+ if (!isLicenseFile(parsed)) {
546
+ throw new Error("Invalid license data format");
547
+ }
548
+ const file = parsed;
549
+ if (file.version !== 1) {
550
+ throw new Error("Unsupported data version");
551
+ }
552
+ if (isEncryptedLicenseFile(file)) {
553
+ const data = decryptLicenseData(file.encrypted, file.iv, file.tag);
554
+ const fileRecord = file;
555
+ if (fileRecord.machineId) {
556
+ const currentMachineId = await getMachineId2();
557
+ if (currentMachineId && fileRecord.machineId !== currentMachineId) {
558
+ throw new Error("License was activated on a different machine");
559
+ }
560
+ }
561
+ return data;
562
+ }
563
+ if (file.license) {
564
+ const data = file.license;
565
+ await saveLicense(data);
566
+ return data;
567
+ }
568
+ return null;
569
+ } catch {
570
+ return null;
571
+ }
572
+ }
573
+ async function saveLicense(data) {
574
+ await ensureDataDirs();
575
+ const { encrypted, iv, tag } = encryptLicenseData(data);
576
+ const machineId = await getMachineId2();
577
+ const file = { version: 1, encrypted, iv, tag };
578
+ if (machineId) file.machineId = machineId;
579
+ const tmpPath = LICENSE_PATH + ".tmp";
580
+ await writeFile2(tmpPath, JSON.stringify(file, null, 2), { encoding: "utf-8", mode: 384 });
581
+ await rename(tmpPath, LICENSE_PATH);
582
+ }
583
+ async function clearLicense() {
584
+ try {
585
+ await rm(LICENSE_PATH);
586
+ } catch {
587
+ }
588
+ }
589
+ function isExpired(license) {
590
+ if (!license.expiresAt) return false;
591
+ return new Date(license.expiresAt).getTime() < Date.now();
592
+ }
593
+ function needsRevalidation(license) {
594
+ const lastValidated = new Date(license.lastValidatedAt).getTime();
595
+ if (isNaN(lastValidated)) return true;
596
+ return Date.now() - lastValidated > REVALIDATION_INTERVAL_MS;
597
+ }
598
+ function isWithinGracePeriod(license) {
599
+ const lastValidated = new Date(license.lastValidatedAt).getTime();
600
+ if (isNaN(lastValidated)) return false;
601
+ return Date.now() - lastValidated < GRACE_PERIOD_MS;
602
+ }
603
+ function getDegradationLevel(license) {
604
+ const lastValidated = new Date(license.lastValidatedAt).getTime();
605
+ if (isNaN(lastValidated)) return "expired";
606
+ const elapsed = Date.now() - lastValidated;
607
+ if (elapsed < 0) return "none";
608
+ const days = elapsed / (24 * 60 * 60 * 1e3);
609
+ if (days <= 7) return "none";
610
+ if (days <= 14) return "warning";
611
+ if (days <= 30) return "limited";
612
+ return "expired";
613
+ }
614
+ function validateLicenseKey(key) {
615
+ if (key.length < 10 || key.length > 100) {
616
+ throw new Error("Invalid license key format");
617
+ }
618
+ if (!/^[\w-]+$/.test(key)) {
619
+ throw new Error("Invalid license key format");
620
+ }
621
+ }
622
+ async function activate(key) {
623
+ validateLicenseKey(key);
624
+ checkRateLimit();
625
+ let success = false;
626
+ try {
627
+ const res = await activateLicense(key);
628
+ if (!res.activated) {
629
+ throw new Error(res.error ?? "Activation failed");
630
+ }
631
+ const license = {
632
+ key,
633
+ instanceId: res.instance.id,
634
+ status: "active",
635
+ customerEmail: res.meta.customer_email,
636
+ customerName: res.meta.customer_name,
637
+ plan: "pro",
638
+ activatedAt: (/* @__PURE__ */ new Date()).toISOString(),
639
+ expiresAt: res.license_key.expires_at,
640
+ lastValidatedAt: (/* @__PURE__ */ new Date()).toISOString()
641
+ };
642
+ await saveLicense(license);
643
+ success = true;
644
+ return license;
645
+ } finally {
646
+ recordAttempt(success);
647
+ }
648
+ }
649
+ function isNetworkError(err) {
650
+ const msg = err instanceof Error ? err.message : String(err);
651
+ return /fetch failed|ECONNREFUSED|ENOTFOUND|ETIMEDOUT|network|timeout|abort/i.test(msg);
652
+ }
653
+ async function revalidate(license) {
654
+ try {
655
+ const res = await validateLicense(license.key, license.instanceId);
656
+ if (res.valid) {
657
+ const updated = {
658
+ ...license,
659
+ lastValidatedAt: (/* @__PURE__ */ new Date()).toISOString(),
660
+ status: "active",
661
+ expiresAt: res.license_key.expires_at
662
+ };
663
+ await saveLicense(updated);
664
+ return "valid";
665
+ }
666
+ await saveLicense({ ...license, status: "expired" });
667
+ return "expired";
668
+ } catch (err) {
669
+ if (isNetworkError(err)) {
670
+ return isWithinGracePeriod(license) ? "grace" : "expired";
671
+ }
672
+ await saveLicense({ ...license, status: "expired" });
673
+ return "expired";
674
+ }
675
+ }
676
+ async function deactivate(license) {
677
+ let remoteSuccess = false;
678
+ for (let attempt = 0; attempt < 3; attempt++) {
679
+ try {
680
+ await deactivateLicense(license.key, license.instanceId);
681
+ remoteSuccess = true;
682
+ break;
683
+ } catch {
684
+ if (attempt < 2) await new Promise((r) => setTimeout(r, 1e3));
685
+ }
686
+ }
687
+ await clearLicense();
688
+ return { remoteSuccess };
689
+ }
690
+
691
+ // src/lib/license/anti-tamper.ts
692
+ var _originalIsPro = null;
693
+ var _originalGetState = null;
694
+ var _storeApi = null;
695
+ function initStoreIntegrity(store) {
696
+ _storeApi = store;
697
+ _originalIsPro = store.getState().isPro;
698
+ _originalGetState = store.getState;
699
+ }
700
+
701
+ // src/stores/license-store.ts
702
+ var REVALIDATION_CHECK_MS = 60 * 60 * 1e3;
703
+ var _revalidatingPromise = null;
704
+ var _revalidationInterval = null;
705
+ async function doRevalidation(license, set) {
706
+ const result = await revalidate(license);
707
+ if (result === "expired") {
708
+ set({ status: "expired", license: { ...license, status: "expired" }, degradation: "expired" });
709
+ } else {
710
+ const updated = await loadLicense();
711
+ const effective = updated ?? license;
712
+ set({ license: effective, degradation: getDegradationLevel(effective) });
713
+ }
714
+ }
715
+ var useLicenseStore = create2((set, get) => ({
716
+ status: "validating",
717
+ license: null,
718
+ error: null,
719
+ degradation: "none",
720
+ initialize: async () => {
721
+ initStoreIntegrity(useLicenseStore);
722
+ await ensureDataDirs();
723
+ const license = await loadLicense();
724
+ if (!license) {
725
+ set({ status: "free", license: null, degradation: "none" });
726
+ return;
727
+ }
728
+ if (isExpired(license)) {
729
+ set({ status: "expired", license, degradation: "expired" });
730
+ return;
731
+ }
732
+ const level = getDegradationLevel(license);
733
+ if (level === "expired") {
734
+ set({ status: "expired", license, degradation: "expired" });
735
+ return;
736
+ }
737
+ set({ status: "pro", license, degradation: level });
738
+ if (needsRevalidation(license)) {
739
+ if (!_revalidatingPromise) {
740
+ _revalidatingPromise = doRevalidation(license, set).finally(() => {
741
+ _revalidatingPromise = null;
742
+ });
743
+ }
744
+ await _revalidatingPromise;
745
+ }
746
+ if (_revalidationInterval) clearInterval(_revalidationInterval);
747
+ _revalidationInterval = setInterval(() => {
748
+ const current = get().license;
749
+ if (!current || get().status !== "pro") return;
750
+ if (!needsRevalidation(current)) return;
751
+ if (_revalidatingPromise) return;
752
+ _revalidatingPromise = doRevalidation(current, set).finally(() => {
753
+ _revalidatingPromise = null;
754
+ });
755
+ }, REVALIDATION_CHECK_MS);
756
+ _revalidationInterval.unref();
757
+ },
758
+ activate: async (key) => {
759
+ set({ error: null });
760
+ try {
761
+ const license = await activate(key);
762
+ set({ status: "pro", license, degradation: "none" });
763
+ return true;
764
+ } catch (err) {
765
+ const msg = err instanceof Error ? err.message : String(err);
766
+ set({ error: msg });
767
+ return false;
768
+ }
769
+ },
770
+ deactivate: async () => {
771
+ const { license } = get();
772
+ if (license) {
773
+ const { remoteSuccess } = await deactivate(license);
774
+ if (!remoteSuccess) {
775
+ set({ status: "free", license: null, degradation: "none", error: "License removed locally but server deactivation failed. It may remain active remotely." });
776
+ return;
777
+ }
778
+ }
779
+ set({ status: "free", license: null, degradation: "none", error: null });
780
+ },
781
+ isPro: () => get().status === "pro"
782
+ }));
783
+
303
784
  // src/hooks/use-keyboard.ts
304
785
  import { useInput } from "ink";
305
786
 
306
787
  // src/stores/modal-store.ts
307
- import { create as create2 } from "zustand";
308
- var useModalStore = create2((set) => ({
788
+ import { create as create3 } from "zustand";
789
+ var useModalStore = create3((set) => ({
309
790
  _count: 0,
310
791
  isOpen: false,
311
792
  openModal: () => set((s) => {
@@ -383,31 +864,37 @@ function UpgradePrompt({ viewId }) {
383
864
  Box4,
384
865
  {
385
866
  borderStyle: "double",
386
- borderColor: "#FF6B2B",
867
+ borderColor: COLORS.brand,
387
868
  paddingX: 3,
388
869
  paddingY: 2,
389
870
  flexDirection: "column",
390
871
  alignItems: "center",
391
872
  width: "80%",
392
873
  children: [
393
- /* @__PURE__ */ jsxs4(Text4, { bold: true, color: "#FF6B2B", children: [
874
+ /* @__PURE__ */ jsxs4(Text4, { bold: true, color: COLORS.brand, children: [
394
875
  "\u2B50",
395
876
  " ",
396
877
  t("upgrade_proFeature", { title })
397
878
  ] }),
398
879
  /* @__PURE__ */ jsx5(Text4, { children: " " }),
399
- /* @__PURE__ */ jsx5(Text4, { color: "#F9FAFB", wrap: "wrap", children: t(keys.desc) }),
880
+ /* @__PURE__ */ jsx5(Text4, { color: COLORS.text, wrap: "wrap", children: t(keys.desc) }),
400
881
  /* @__PURE__ */ jsx5(Text4, { children: " " }),
401
882
  /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", alignItems: "center", children: [
402
- /* @__PURE__ */ jsx5(Text4, { color: "#06B6D4", bold: true, children: t("upgrade_pricing") }),
883
+ /* @__PURE__ */ jsx5(Text4, { color: COLORS.info, bold: true, children: t("upgrade_pricing") }),
884
+ /* @__PURE__ */ jsx5(Text4, { children: " " }),
885
+ /* @__PURE__ */ jsx5(Text4, { color: COLORS.muted, children: t("upgrade_buyAt") }),
886
+ /* @__PURE__ */ jsxs4(Text4, { color: COLORS.sky, bold: true, children: [
887
+ " ",
888
+ t("upgrade_buyUrl")
889
+ ] }),
403
890
  /* @__PURE__ */ jsx5(Text4, { children: " " }),
404
- /* @__PURE__ */ jsx5(Text4, { color: "#9CA3AF", children: t("upgrade_activateWith") }),
405
- /* @__PURE__ */ jsxs4(Text4, { color: "#22C55E", bold: true, children: [
891
+ /* @__PURE__ */ jsx5(Text4, { color: COLORS.muted, children: t("upgrade_activateWith") }),
892
+ /* @__PURE__ */ jsxs4(Text4, { color: COLORS.success, bold: true, children: [
406
893
  " ",
407
894
  t("upgrade_activateCmd")
408
895
  ] }),
409
896
  /* @__PURE__ */ jsx5(Text4, { children: " " }),
410
- /* @__PURE__ */ jsx5(Text4, { color: "#FF6B2B", children: t("upgrade_proLabel") })
897
+ /* @__PURE__ */ jsx5(Text4, { color: COLORS.brand, children: t("upgrade_proLabel") })
411
898
  ] })
412
899
  ]
413
900
  }
@@ -415,22 +902,30 @@ function UpgradePrompt({ viewId }) {
415
902
  }
416
903
 
417
904
  // src/views/dashboard.tsx
418
- import { useEffect, useMemo } from "react";
419
- import { Box as Box8, Text as Text10 } from "ink";
905
+ import { useEffect, useMemo as useMemo2 } from "react";
906
+ import { Box as Box8, Text as Text10, useStdout } from "ink";
420
907
 
421
908
  // src/stores/brew-store.ts
422
- import { create as create3 } from "zustand";
909
+ import { create as create4 } from "zustand";
423
910
 
424
911
  // src/lib/brew-api.ts
425
912
  import { spawn as spawn2 } from "child_process";
426
913
 
427
914
  // src/lib/brew-cli.ts
428
915
  import { spawn } from "child_process";
429
- async function execBrew(args) {
916
+ var DEFAULT_TIMEOUT_MS = 3e4;
917
+ var STREAM_IDLE_TIMEOUT_MS = 5 * 60 * 1e3;
918
+ async function execBrew(args, timeoutMs = DEFAULT_TIMEOUT_MS) {
430
919
  return new Promise((resolve, reject) => {
431
920
  const proc = spawn("brew", args, { env: { ...process.env, HOMEBREW_NO_AUTO_UPDATE: "1" } });
432
921
  let stdout = "";
433
922
  let stderr = "";
923
+ let killed = false;
924
+ const timer = setTimeout(() => {
925
+ killed = true;
926
+ proc.kill();
927
+ reject(new Error(`brew ${args.join(" ")} timed out after ${timeoutMs}ms`));
928
+ }, timeoutMs);
434
929
  proc.stdout.on("data", (d) => {
435
930
  stdout += d.toString();
436
931
  });
@@ -438,6 +933,8 @@ async function execBrew(args) {
438
933
  stderr += d.toString();
439
934
  });
440
935
  proc.on("close", (code) => {
936
+ clearTimeout(timer);
937
+ if (killed) return;
441
938
  if (code === 0) {
442
939
  resolve(stdout);
443
940
  } else {
@@ -445,6 +942,8 @@ async function execBrew(args) {
445
942
  }
446
943
  });
447
944
  proc.on("error", (err) => {
945
+ clearTimeout(timer);
946
+ if (killed) return;
448
947
  reject(new Error(`Failed to run brew: ${err.message}`));
449
948
  });
450
949
  });
@@ -458,7 +957,9 @@ async function* streamBrew(args) {
458
957
  const lines = [];
459
958
  let done = false;
460
959
  let exitError = null;
960
+ let lastOutputAt = Date.now();
461
961
  const push = (chunk) => {
962
+ lastOutputAt = Date.now();
462
963
  buffer += chunk.toString();
463
964
  const parts = buffer.split("\n");
464
965
  buffer = parts.pop() ?? "";
@@ -484,7 +985,11 @@ async function* streamBrew(args) {
484
985
  if (lines.length > 0) {
485
986
  yield lines.shift();
486
987
  } else if (!done) {
487
- await new Promise((r) => setTimeout(r, 50));
988
+ if (Date.now() - lastOutputAt > STREAM_IDLE_TIMEOUT_MS) {
989
+ proc.kill();
990
+ throw new Error(`brew ${args.join(" ")} timed out: no output for ${STREAM_IDLE_TIMEOUT_MS / 1e3}s`);
991
+ }
992
+ await new Promise((r) => setTimeout(r, 100));
488
993
  }
489
994
  }
490
995
  } finally {
@@ -595,6 +1100,10 @@ function parseLeavesOutput(raw) {
595
1100
  }
596
1101
 
597
1102
  // src/lib/brew-api.ts
1103
+ var PKG_PATTERN = /^[\w@./+-]+$/;
1104
+ function validatePackageName(name) {
1105
+ if (!PKG_PATTERN.test(name)) throw new Error("Invalid package name: " + name);
1106
+ }
598
1107
  async function brewUpdate() {
599
1108
  return new Promise((resolve, reject) => {
600
1109
  const proc = spawn2("brew", ["update"], { stdio: "ignore" });
@@ -618,9 +1127,20 @@ async function getServices() {
618
1127
  return parseServicesJson(raw);
619
1128
  }
620
1129
  async function getFormulaInfo(name) {
1130
+ validatePackageName(name);
621
1131
  const raw = await execBrew(["info", "--json=v2", name]);
622
1132
  return parseFormulaInfoJson(raw);
623
1133
  }
1134
+ async function getCaskInfo(name) {
1135
+ validatePackageName(name);
1136
+ try {
1137
+ const raw = await execBrew(["info", "--json=v2", "--cask", name]);
1138
+ const data = JSON.parse(raw);
1139
+ return data.casks?.[0] ?? null;
1140
+ } catch {
1141
+ return null;
1142
+ }
1143
+ }
624
1144
  async function search(term) {
625
1145
  const safeTerm = term.replace(/^-+/, "");
626
1146
  if (!safeTerm) return { formulae: [], casks: [] };
@@ -645,11 +1165,21 @@ async function getLeaves() {
645
1165
  return parseLeavesOutput(raw);
646
1166
  }
647
1167
  async function uninstallPackage(name) {
1168
+ validatePackageName(name);
648
1169
  return execBrew(["uninstall", name]);
649
1170
  }
650
1171
  async function serviceAction(name, action) {
1172
+ validatePackageName(name);
651
1173
  return execBrew(["services", action, name]);
652
1174
  }
1175
+ async function pinPackage(name) {
1176
+ validatePackageName(name);
1177
+ return execBrew(["pin", name]);
1178
+ }
1179
+ async function unpinPackage(name) {
1180
+ validatePackageName(name);
1181
+ return execBrew(["unpin", name]);
1182
+ }
653
1183
  function formulaeToListItems(formulae) {
654
1184
  return formulae.map((f) => {
655
1185
  const installed = f.installed[0];
@@ -681,13 +1211,20 @@ function casksToListItems(casks) {
681
1211
  }
682
1212
 
683
1213
  // src/stores/brew-store.ts
1214
+ var BREW_UPDATE_COOLDOWN_MS = 5 * 60 * 1e3;
1215
+ var fetchAllInFlight = null;
1216
+ var brewUpdateInFlight = null;
1217
+ var lastBrewUpdateStartedAt = 0;
684
1218
  function setLoading(set, key, value) {
685
1219
  set((s) => ({ loading: { ...s.loading, [key]: value } }));
686
1220
  }
687
1221
  function setError(set, key, error) {
688
1222
  set((s) => ({ errors: { ...s.errors, [key]: error } }));
689
1223
  }
690
- var useBrewStore = create3((set) => ({
1224
+ function recordFetchTime(set, key) {
1225
+ set((s) => ({ lastFetchedAt: { ...s.lastFetchedAt, [key]: Date.now() } }));
1226
+ }
1227
+ var useBrewStore = create4((set, get) => ({
691
1228
  formulae: [],
692
1229
  casks: [],
693
1230
  outdated: { formulae: [], casks: [] },
@@ -699,8 +1236,10 @@ var useBrewStore = create3((set) => ({
699
1236
  // Pre-initialize loading flags for keys that fetchAll always triggers so
700
1237
  // views that check loading.X get a spinner on first render rather than
701
1238
  // flashing empty/zeroed content for one frame before the fetch starts.
702
- loading: { installed: true, outdated: true, services: true, config: true, doctor: false },
1239
+ // SCR-013: Pre-initialize doctor loading to true
1240
+ loading: { installed: true, outdated: true, services: true, config: true, doctor: true },
703
1241
  errors: {},
1242
+ lastFetchedAt: {},
704
1243
  fetchInstalled: async () => {
705
1244
  setLoading(set, "installed", true);
706
1245
  setError(set, "installed", null);
@@ -711,6 +1250,7 @@ var useBrewStore = create3((set) => ({
711
1250
  setError(set, "installed", err instanceof Error ? err.message : String(err));
712
1251
  } finally {
713
1252
  setLoading(set, "installed", false);
1253
+ recordFetchTime(set, "installed");
714
1254
  }
715
1255
  },
716
1256
  fetchOutdated: async () => {
@@ -723,6 +1263,7 @@ var useBrewStore = create3((set) => ({
723
1263
  setError(set, "outdated", err instanceof Error ? err.message : String(err));
724
1264
  } finally {
725
1265
  setLoading(set, "outdated", false);
1266
+ recordFetchTime(set, "outdated");
726
1267
  }
727
1268
  },
728
1269
  fetchServices: async () => {
@@ -735,6 +1276,7 @@ var useBrewStore = create3((set) => ({
735
1276
  setError(set, "services", err instanceof Error ? err.message : String(err));
736
1277
  } finally {
737
1278
  setLoading(set, "services", false);
1279
+ recordFetchTime(set, "services");
738
1280
  }
739
1281
  },
740
1282
  fetchConfig: async () => {
@@ -746,6 +1288,7 @@ var useBrewStore = create3((set) => ({
746
1288
  setError(set, "config", err instanceof Error ? err.message : String(err));
747
1289
  } finally {
748
1290
  setLoading(set, "config", false);
1291
+ recordFetchTime(set, "config");
749
1292
  }
750
1293
  },
751
1294
  fetchLeaves: async () => {
@@ -753,9 +1296,9 @@ var useBrewStore = create3((set) => ({
753
1296
  const result = await getLeaves();
754
1297
  set({ leaves: result });
755
1298
  } catch (err) {
756
- if (process.env.NODE_ENV !== "production") {
757
- console.error("[brew-store] fetchLeaves failed:", err instanceof Error ? err.message : String(err));
758
- }
1299
+ logger.error("fetchLeaves failed", { error: err instanceof Error ? err.message : String(err) });
1300
+ } finally {
1301
+ recordFetchTime(set, "leaves");
759
1302
  }
760
1303
  },
761
1304
  fetchDoctor: async () => {
@@ -768,27 +1311,40 @@ var useBrewStore = create3((set) => ({
768
1311
  setError(set, "doctor", err instanceof Error ? err.message : String(err));
769
1312
  } finally {
770
1313
  setLoading(set, "doctor", false);
1314
+ recordFetchTime(set, "doctor");
771
1315
  }
772
1316
  },
773
1317
  fetchAll: async () => {
774
- try {
775
- await brewUpdate();
776
- } catch {
1318
+ if (fetchAllInFlight) {
1319
+ return fetchAllInFlight;
1320
+ }
1321
+ const now = Date.now();
1322
+ if (!brewUpdateInFlight && now - lastBrewUpdateStartedAt > BREW_UPDATE_COOLDOWN_MS) {
1323
+ lastBrewUpdateStartedAt = now;
1324
+ brewUpdateInFlight = brewUpdate().catch((err) => {
1325
+ set((s) => ({ errors: { ...s.errors, update: String(err) } }));
1326
+ }).finally(() => {
1327
+ brewUpdateInFlight = null;
1328
+ });
777
1329
  }
778
- const store = useBrewStore.getState();
779
- await Promise.all([
1330
+ const store = get();
1331
+ fetchAllInFlight = Promise.all([
780
1332
  store.fetchInstalled(),
781
1333
  store.fetchOutdated(),
782
1334
  store.fetchServices(),
783
1335
  store.fetchConfig(),
784
- store.fetchLeaves()
785
- ]);
1336
+ store.fetchLeaves(),
1337
+ store.fetchDoctor()
1338
+ ]).then(() => void 0).finally(() => {
1339
+ fetchAllInFlight = null;
1340
+ });
1341
+ return fetchAllInFlight;
786
1342
  },
787
1343
  uninstallPackage: async (name) => {
788
1344
  setLoading(set, "action", true);
789
1345
  try {
790
1346
  await uninstallPackage(name);
791
- await useBrewStore.getState().fetchInstalled();
1347
+ await get().fetchInstalled();
792
1348
  } catch (err) {
793
1349
  setError(set, "action", err instanceof Error ? err.message : String(err));
794
1350
  } finally {
@@ -799,7 +1355,7 @@ var useBrewStore = create3((set) => ({
799
1355
  setLoading(set, "service-action", true);
800
1356
  try {
801
1357
  await serviceAction(name, action);
802
- await useBrewStore.getState().fetchServices();
1358
+ await get().fetchServices();
803
1359
  } catch (err) {
804
1360
  setError(set, "service-action", err instanceof Error ? err.message : String(err));
805
1361
  } finally {
@@ -824,7 +1380,7 @@ function StatCard({ label, value, color = "white" }) {
824
1380
  minWidth: 16,
825
1381
  children: [
826
1382
  /* @__PURE__ */ jsx6(Text5, { bold: true, color, children: value }),
827
- /* @__PURE__ */ jsx6(Text5, { color: "#9CA3AF", children: label })
1383
+ /* @__PURE__ */ jsx6(Text5, { color: COLORS.muted, children: label })
828
1384
  ]
829
1385
  }
830
1386
  );
@@ -841,12 +1397,12 @@ function Loading({ message }) {
841
1397
  function ErrorMessage({ message }) {
842
1398
  useLocaleStore((s) => s.locale);
843
1399
  return /* @__PURE__ */ jsxs6(Box6, { paddingY: 1, children: [
844
- /* @__PURE__ */ jsxs6(Text6, { color: "#EF4444", bold: true, children: [
1400
+ /* @__PURE__ */ jsxs6(Text6, { color: COLORS.error, bold: true, children: [
845
1401
  "\u2718",
846
1402
  " ",
847
1403
  t("error_prefix")
848
1404
  ] }),
849
- /* @__PURE__ */ jsx7(Text6, { color: "#EF4444", children: message })
1405
+ /* @__PURE__ */ jsx7(Text6, { color: COLORS.error, children: message })
850
1406
  ] });
851
1407
  }
852
1408
 
@@ -854,11 +1410,11 @@ function ErrorMessage({ message }) {
854
1410
  import { Text as Text7 } from "ink";
855
1411
  import { jsxs as jsxs7 } from "react/jsx-runtime";
856
1412
  var BADGE_STYLES = {
857
- success: { icon: "\u2714", color: "#22C55E" },
858
- warning: { icon: "\u25CF", color: "#F59E0B" },
859
- error: { icon: "\u2718", color: "#EF4444" },
860
- info: { icon: "\u25C6", color: "#3B82F6" },
861
- muted: { icon: "\u25CB", color: "#6B7280" }
1413
+ success: { icon: "\u2714", color: COLORS.success },
1414
+ warning: { icon: "\u25CF", color: COLORS.warning },
1415
+ error: { icon: "\u2718", color: COLORS.error },
1416
+ info: { icon: "\u25C6", color: COLORS.blue },
1417
+ muted: { icon: "\u25CB", color: COLORS.textSecondary }
862
1418
  };
863
1419
  function StatusBadge({ label, variant }) {
864
1420
  const { icon, color } = BADGE_STYLES[variant];
@@ -872,11 +1428,11 @@ function StatusBadge({ label, variant }) {
872
1428
  // src/components/common/section-header.tsx
873
1429
  import { Box as Box7, Text as Text8 } from "ink";
874
1430
  import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
875
- function SectionHeader({ emoji, title, color = "#FFD700", gradient, count }) {
1431
+ function SectionHeader({ emoji, title, color = COLORS.gold, gradient, count }) {
876
1432
  return /* @__PURE__ */ jsxs8(Box7, { gap: 1, children: [
877
1433
  /* @__PURE__ */ jsx8(Text8, { children: emoji }),
878
1434
  gradient ? /* @__PURE__ */ jsx8(GradientText, { colors: gradient, bold: true, children: title }) : /* @__PURE__ */ jsx8(Text8, { bold: true, color, children: title }),
879
- count !== void 0 && /* @__PURE__ */ jsxs8(Text8, { color: "#6B7280", children: [
1435
+ count !== void 0 && /* @__PURE__ */ jsxs8(Text8, { color: COLORS.textSecondary, children: [
880
1436
  "(",
881
1437
  count,
882
1438
  ")"
@@ -889,100 +1445,153 @@ import { Text as Text9 } from "ink";
889
1445
  import { Fragment as Fragment3, jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
890
1446
  function VersionArrow({ current, latest }) {
891
1447
  return /* @__PURE__ */ jsxs9(Fragment3, { children: [
892
- /* @__PURE__ */ jsx9(Text9, { color: "#EF4444", children: current }),
893
- /* @__PURE__ */ jsx9(Text9, { color: "#F59E0B", children: " \u2500\u2500 " }),
894
- /* @__PURE__ */ jsx9(Text9, { color: "#FFD700", children: "\u25B6" }),
895
- /* @__PURE__ */ jsxs9(Text9, { color: "#2DD4BF", children: [
1448
+ /* @__PURE__ */ jsxs9(Text9, { color: COLORS.muted, children: [
1449
+ t("version_installed"),
1450
+ " "
1451
+ ] }),
1452
+ /* @__PURE__ */ jsx9(Text9, { color: COLORS.error, children: current }),
1453
+ /* @__PURE__ */ jsx9(Text9, { color: COLORS.warning, children: " \u2500\u2500 " }),
1454
+ /* @__PURE__ */ jsx9(Text9, { color: COLORS.gold, children: "\u25B6" }),
1455
+ /* @__PURE__ */ jsxs9(Text9, { color: COLORS.muted, children: [
896
1456
  " ",
897
- latest
898
- ] })
1457
+ t("version_available"),
1458
+ " "
1459
+ ] }),
1460
+ /* @__PURE__ */ jsx9(Text9, { color: COLORS.teal, children: latest })
899
1461
  ] });
900
1462
  }
901
1463
 
1464
+ // src/utils/format.ts
1465
+ function formatBytes(bytes) {
1466
+ if (!isFinite(bytes) || bytes <= 0) return "0 B";
1467
+ const units = ["B", "KB", "MB", "GB", "TB"];
1468
+ const i = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), units.length - 1);
1469
+ return `${(bytes / Math.pow(1024, i)).toFixed(1)} ${units[i]}`;
1470
+ }
1471
+ function formatRelativeTime(timestamp) {
1472
+ if (!timestamp || !isFinite(timestamp)) return t("time_justNow");
1473
+ const diff = Date.now() / 1e3 - timestamp;
1474
+ if (diff < 0) return t("time_justNow");
1475
+ if (diff < 60) return t("time_justNow");
1476
+ if (diff < 3600) return t("time_minutesAgo", { n: Math.floor(diff / 60) });
1477
+ if (diff < 86400) return t("time_hoursAgo", { n: Math.floor(diff / 3600) });
1478
+ if (diff < 2592e3) return t("time_daysAgo", { n: Math.floor(diff / 86400) });
1479
+ return t("time_monthsAgo", { n: Math.floor(diff / 2592e3) });
1480
+ }
1481
+ function formatDate(value) {
1482
+ const date = value instanceof Date ? value : new Date(value);
1483
+ const locale = getLocale();
1484
+ return date.toLocaleDateString(locale === "es" ? "es-ES" : "en-US");
1485
+ }
1486
+ function truncate(str, maxLen) {
1487
+ if (str.length <= maxLen) return str;
1488
+ return str.slice(0, maxLen - 1) + "\u2026";
1489
+ }
1490
+
902
1491
  // src/views/dashboard.tsx
903
1492
  import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
904
1493
  function DashboardView() {
905
- const { formulae, casks, outdated, services, config, loading, errors, fetchAll } = useBrewStore();
1494
+ const { formulae, casks, outdated, services, config, loading, errors, lastFetchedAt, fetchAll } = useBrewStore();
1495
+ const { stdout } = useStdout();
1496
+ const columns = stdout?.columns ?? 80;
906
1497
  useEffect(() => {
907
1498
  fetchAll();
908
1499
  }, []);
909
- const errorServiceList = useMemo(
1500
+ const errorServiceList = useMemo2(
910
1501
  () => services.filter((s) => s.status === "error"),
911
1502
  [services]
912
1503
  );
913
- const runningServices = useMemo(
1504
+ const runningServices = useMemo2(
914
1505
  () => services.filter((s) => s.status === "started").length,
915
1506
  [services]
916
1507
  );
917
1508
  const errorServices = errorServiceList.length;
1509
+ const partialErrors = [
1510
+ errors.outdated ? { label: t("dashboard_outdated"), message: errors.outdated } : null,
1511
+ errors.services ? { label: t("dashboard_services"), message: errors.services } : null,
1512
+ errors.config ? { label: t("dashboard_systemInfo"), message: errors.config } : null
1513
+ ].filter((item) => item !== null);
1514
+ const outdatedValue = loading.outdated ? "..." : errors.outdated ? t("dashboard_statError") : outdated.formulae.length + outdated.casks.length;
1515
+ const servicesValue = loading.services ? "..." : errors.services ? t("dashboard_statError") : `${runningServices}/${services.length}`;
1516
+ const lastUpdated = lastFetchedAt.installed ? formatRelativeTime(lastFetchedAt.installed / 1e3) : null;
918
1517
  if (loading.installed) return /* @__PURE__ */ jsx10(Loading, { message: t("loading_fetchingBrew") });
919
1518
  if (errors.installed) return /* @__PURE__ */ jsx10(ErrorMessage, { message: errors.installed });
1519
+ const isNarrow = columns < 60;
920
1520
  return /* @__PURE__ */ jsxs10(Box8, { flexDirection: "column", gap: 2, children: [
921
1521
  /* @__PURE__ */ jsx10(SectionHeader, { emoji: "\u{1F4CA}", title: t("dashboard_overview"), gradient: GRADIENTS.gold }),
922
- /* @__PURE__ */ jsxs10(Box8, { gap: 1, flexWrap: "wrap", children: [
923
- /* @__PURE__ */ jsx10(StatCard, { label: t("dashboard_formulae"), value: formulae.length, color: "#06B6D4" }),
924
- /* @__PURE__ */ jsx10(StatCard, { label: t("dashboard_casks"), value: casks.length, color: "#A855F7" }),
1522
+ /* @__PURE__ */ jsxs10(Box8, { gap: 1, flexWrap: "wrap", flexDirection: isNarrow ? "column" : "row", children: [
1523
+ /* @__PURE__ */ jsx10(StatCard, { label: t("dashboard_formulae"), value: formulae.length, color: COLORS.info }),
1524
+ /* @__PURE__ */ jsx10(StatCard, { label: t("dashboard_casks"), value: casks.length, color: COLORS.purple }),
925
1525
  /* @__PURE__ */ jsx10(
926
1526
  StatCard,
927
1527
  {
928
1528
  label: t("dashboard_outdated"),
929
- value: outdated.formulae.length + outdated.casks.length,
930
- color: outdated.formulae.length + outdated.casks.length > 0 ? "#F59E0B" : "#22C55E"
1529
+ value: outdatedValue,
1530
+ color: typeof outdatedValue === "number" && outdatedValue > 0 ? COLORS.warning : errors.outdated ? COLORS.error : COLORS.success
931
1531
  }
932
1532
  ),
933
1533
  /* @__PURE__ */ jsx10(
934
1534
  StatCard,
935
1535
  {
936
1536
  label: t("dashboard_services"),
937
- value: `${runningServices}/${services.length}`,
938
- color: errorServices > 0 ? "#EF4444" : "#22C55E"
1537
+ value: servicesValue,
1538
+ color: errors.services || errorServices > 0 ? COLORS.error : COLORS.success
939
1539
  }
940
1540
  )
941
1541
  ] }),
942
- config && /* @__PURE__ */ jsxs10(Box8, { flexDirection: "column", children: [
943
- /* @__PURE__ */ jsx10(SectionHeader, { emoji: "\u2139\uFE0F", title: t("dashboard_systemInfo"), gradient: ["#F9FAFB", "#9CA3AF"] }),
944
- /* @__PURE__ */ jsxs10(Box8, { borderStyle: "round", borderColor: "#3B82F6", paddingX: 2, paddingY: 0, flexDirection: "column", marginTop: 1, children: [
1542
+ lastUpdated && /* @__PURE__ */ jsx10(Text10, { color: COLORS.muted, children: t("dashboard_lastUpdated", { time: lastUpdated }) }),
1543
+ partialErrors.length > 0 && /* @__PURE__ */ jsxs10(Box8, { flexDirection: "column", borderStyle: "round", borderColor: COLORS.warning, paddingX: 2, paddingY: 0, children: [
1544
+ /* @__PURE__ */ jsx10(Text10, { color: COLORS.warning, bold: true, children: t("dashboard_partialData") }),
1545
+ partialErrors.map((item) => /* @__PURE__ */ jsxs10(Text10, { color: COLORS.muted, children: [
1546
+ item.label,
1547
+ ": ",
1548
+ item.message
1549
+ ] }, item.label))
1550
+ ] }),
1551
+ config && !errors.config && /* @__PURE__ */ jsxs10(Box8, { flexDirection: "column", children: [
1552
+ /* @__PURE__ */ jsx10(SectionHeader, { emoji: "\u2139\uFE0F", title: t("dashboard_systemInfo"), gradient: [COLORS.text, COLORS.muted] }),
1553
+ /* @__PURE__ */ jsxs10(Box8, { borderStyle: "round", borderColor: COLORS.blue, paddingX: 2, paddingY: 0, flexDirection: "column", marginTop: 1, children: [
945
1554
  /* @__PURE__ */ jsxs10(Text10, { children: [
946
- /* @__PURE__ */ jsx10(Text10, { color: "#9CA3AF", children: t("dashboard_homebrew") }),
1555
+ /* @__PURE__ */ jsx10(Text10, { color: COLORS.muted, children: t("dashboard_homebrew") }),
947
1556
  " ",
948
1557
  config.HOMEBREW_VERSION
949
1558
  ] }),
950
1559
  /* @__PURE__ */ jsxs10(Text10, { children: [
951
- /* @__PURE__ */ jsx10(Text10, { color: "#9CA3AF", children: t("dashboard_prefix") }),
1560
+ /* @__PURE__ */ jsx10(Text10, { color: COLORS.muted, children: t("dashboard_prefix") }),
952
1561
  " ",
953
1562
  config.HOMEBREW_PREFIX
954
1563
  ] }),
955
1564
  /* @__PURE__ */ jsxs10(Text10, { children: [
956
- /* @__PURE__ */ jsx10(Text10, { color: "#9CA3AF", children: t("dashboard_updated") }),
1565
+ /* @__PURE__ */ jsx10(Text10, { color: COLORS.muted, children: t("dashboard_updated") }),
957
1566
  " ",
958
1567
  config.coreUpdated
959
1568
  ] })
960
1569
  ] })
961
1570
  ] }),
962
- outdated.formulae.length > 0 && /* @__PURE__ */ jsxs10(Box8, { flexDirection: "column", marginTop: 1, children: [
1571
+ !errors.outdated && outdated.formulae.length > 0 && /* @__PURE__ */ jsxs10(Box8, { flexDirection: "column", marginTop: 1, children: [
963
1572
  /* @__PURE__ */ jsx10(SectionHeader, { emoji: "\u{1F4E6}", title: t("dashboard_outdatedPackages"), gradient: GRADIENTS.fire }),
964
1573
  /* @__PURE__ */ jsxs10(Box8, { paddingLeft: 2, flexDirection: "column", children: [
965
1574
  outdated.formulae.slice(0, 10).map((pkg) => /* @__PURE__ */ jsxs10(Box8, { gap: 1, children: [
966
- /* @__PURE__ */ jsx10(Text10, { color: "#F9FAFB", children: pkg.name }),
1575
+ /* @__PURE__ */ jsx10(Text10, { color: COLORS.text, children: pkg.name }),
967
1576
  /* @__PURE__ */ jsx10(VersionArrow, { current: pkg.installed_versions[0] ?? "", latest: pkg.current_version })
968
1577
  ] }, pkg.name)),
969
- outdated.formulae.length > 10 && /* @__PURE__ */ jsx10(Text10, { color: "#6B7280", italic: true, children: t("common_andMore", { count: outdated.formulae.length - 10 }) })
1578
+ outdated.formulae.length > 10 && /* @__PURE__ */ jsx10(Text10, { color: COLORS.textSecondary, italic: true, children: t("common_andMore", { count: outdated.formulae.length - 10 }) })
970
1579
  ] })
971
1580
  ] }),
972
- errorServices > 0 && /* @__PURE__ */ jsxs10(Box8, { flexDirection: "column", marginTop: 1, children: [
973
- /* @__PURE__ */ jsx10(SectionHeader, { emoji: "\u26A0\uFE0F", title: t("dashboard_serviceErrors"), color: "#EF4444" }),
1581
+ !errors.services && errorServices > 0 && /* @__PURE__ */ jsxs10(Box8, { flexDirection: "column", marginTop: 1, children: [
1582
+ /* @__PURE__ */ jsx10(SectionHeader, { emoji: "\u26A0\uFE0F", title: t("dashboard_serviceErrors"), color: COLORS.error }),
974
1583
  /* @__PURE__ */ jsx10(Box8, { paddingLeft: 2, flexDirection: "column", children: errorServiceList.map((s) => /* @__PURE__ */ jsxs10(Box8, { gap: 1, children: [
975
1584
  /* @__PURE__ */ jsx10(StatusBadge, { label: t("badge_error"), variant: "error" }),
976
1585
  /* @__PURE__ */ jsx10(Text10, { children: s.name }),
977
- s.exit_code != null && /* @__PURE__ */ jsx10(Text10, { color: "#9CA3AF", children: t("common_exit", { code: s.exit_code }) })
1586
+ s.exit_code != null && /* @__PURE__ */ jsx10(Text10, { color: COLORS.muted, children: t("common_exit", { code: s.exit_code }) })
978
1587
  ] }, s.name)) })
979
1588
  ] })
980
1589
  ] });
981
1590
  }
982
1591
 
983
1592
  // 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";
1593
+ import { useState as useState3, useMemo as useMemo3, useEffect as useEffect5 } from "react";
1594
+ import { Box as Box14, Text as Text16, useInput as useInput3, useStdout as useStdout2 } from "ink";
986
1595
 
987
1596
  // src/hooks/use-debounce.ts
988
1597
  import { useState, useEffect as useEffect2 } from "react";
@@ -998,25 +1607,13 @@ function useDebounce(value, delayMs) {
998
1607
  // src/hooks/use-brew-stream.ts
999
1608
  import { useState as useState2, useCallback, useRef, useEffect as useEffect3 } from "react";
1000
1609
  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
1610
  async function logToHistory(args, success, error) {
1015
1611
  const detected = detectAction(args);
1016
1612
  if (!detected) return;
1017
1613
  try {
1018
- const { appendEntry: appendEntry2 } = await import("./history-logger-LQT622M2.js");
1019
- await appendEntry2(detected.action, detected.packageName, success, error);
1614
+ const isPro = useLicenseStore.getState().isPro();
1615
+ const { appendEntry: appendEntry2 } = await import("./history-logger-2PGYSPFL.js");
1616
+ await appendEntry2(isPro, detected.action, detected.packageName, success, error);
1020
1617
  } catch {
1021
1618
  }
1022
1619
  }
@@ -1085,7 +1682,7 @@ import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
1085
1682
  function SearchInput({ defaultValue, onChange, placeholder, isActive = true }) {
1086
1683
  const resolvedPlaceholder = placeholder ?? t("searchInput_placeholder");
1087
1684
  return /* @__PURE__ */ jsxs11(Box9, { children: [
1088
- /* @__PURE__ */ jsxs11(Text11, { color: "#FFD700", children: [
1685
+ /* @__PURE__ */ jsxs11(Text11, { color: COLORS.gold, children: [
1089
1686
  "\u{1F50D}",
1090
1687
  " "
1091
1688
  ] }),
@@ -1096,7 +1693,7 @@ function SearchInput({ defaultValue, onChange, placeholder, isActive = true }) {
1096
1693
  defaultValue,
1097
1694
  onChange
1098
1695
  }
1099
- ) : /* @__PURE__ */ jsx11(Text11, { color: "#6B7280", children: defaultValue || placeholder })
1696
+ ) : /* @__PURE__ */ jsx11(Text11, { color: COLORS.textSecondary, children: defaultValue || placeholder })
1100
1697
  ] });
1101
1698
  }
1102
1699
 
@@ -1119,12 +1716,12 @@ function ConfirmDialog({ message, onConfirm, onCancel }) {
1119
1716
  else if (input === "n" || input === "N") onCancel();
1120
1717
  else if (key.escape) onCancel();
1121
1718
  });
1122
- return /* @__PURE__ */ jsxs12(Box10, { borderStyle: "double", borderColor: "#A855F7", paddingX: 2, paddingY: 1, flexDirection: "column", children: [
1123
- /* @__PURE__ */ jsx12(Text12, { bold: true, color: "#F9FAFB", children: message }),
1719
+ return /* @__PURE__ */ jsxs12(Box10, { borderStyle: "double", borderColor: COLORS.purple, paddingX: 2, paddingY: 1, flexDirection: "column", children: [
1720
+ /* @__PURE__ */ jsx12(Text12, { bold: true, color: COLORS.text, children: message }),
1124
1721
  /* @__PURE__ */ jsxs12(Box10, { marginTop: 1, children: [
1125
- /* @__PURE__ */ jsx12(Text12, { color: "#22C55E", children: t("confirm_yes") }),
1722
+ /* @__PURE__ */ jsx12(Text12, { color: COLORS.success, children: t("confirm_yes") }),
1126
1723
  /* @__PURE__ */ jsx12(Text12, { children: " / " }),
1127
- /* @__PURE__ */ jsx12(Text12, { color: "#EF4444", children: t("confirm_no") })
1724
+ /* @__PURE__ */ jsx12(Text12, { color: COLORS.error, children: t("confirm_no") })
1128
1725
  ] })
1129
1726
  ] });
1130
1727
  }
@@ -1135,43 +1732,44 @@ import { Spinner as Spinner2 } from "@inkjs/ui";
1135
1732
  import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
1136
1733
  function ProgressLog({ lines, isRunning, title, maxVisible = 15 }) {
1137
1734
  const visible = lines.slice(-maxVisible);
1138
- return /* @__PURE__ */ jsxs13(Box11, { flexDirection: "column", borderStyle: "round", borderColor: "#38BDF8", paddingX: 1, children: [
1735
+ return /* @__PURE__ */ jsxs13(Box11, { flexDirection: "column", borderStyle: "round", borderColor: COLORS.sky, paddingX: 1, children: [
1139
1736
  title && /* @__PURE__ */ jsxs13(Box11, { marginBottom: 1, children: [
1140
1737
  isRunning && /* @__PURE__ */ jsx13(Spinner2, { label: "" }),
1141
- /* @__PURE__ */ jsxs13(Text13, { bold: true, color: "#38BDF8", children: [
1738
+ /* @__PURE__ */ jsxs13(Text13, { bold: true, color: COLORS.sky, children: [
1142
1739
  " ",
1143
1740
  title
1144
1741
  ] })
1145
1742
  ] }),
1146
- visible.map((line, i) => /* @__PURE__ */ jsx13(Text13, { color: "#9CA3AF", wrap: "wrap", children: line }, i)),
1147
- lines.length === 0 && !isRunning && /* @__PURE__ */ jsx13(Text13, { color: "#6B7280", italic: true, children: t("progress_noOutput") })
1743
+ visible.map((line, i) => /* @__PURE__ */ jsx13(Text13, { color: COLORS.muted, wrap: "wrap", children: line }, lines.length - visible.length + i)),
1744
+ lines.length === 0 && !isRunning && /* @__PURE__ */ jsx13(Text13, { color: COLORS.textSecondary, italic: true, children: t("progress_noOutput") })
1148
1745
  ] });
1149
1746
  }
1150
1747
 
1151
- // src/utils/format.ts
1152
- function formatBytes(bytes) {
1153
- if (!isFinite(bytes) || bytes <= 0) return "0 B";
1154
- const units = ["B", "KB", "MB", "GB", "TB"];
1155
- const i = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), units.length - 1);
1156
- return `${(bytes / Math.pow(1024, i)).toFixed(1)} ${units[i]}`;
1157
- }
1158
- function formatRelativeTime(timestamp) {
1159
- if (!timestamp || !isFinite(timestamp)) return t("time_justNow");
1160
- const diff = Date.now() / 1e3 - timestamp;
1161
- if (diff < 0) return t("time_justNow");
1162
- if (diff < 60) return t("time_justNow");
1163
- if (diff < 3600) return t("time_minutesAgo", { n: Math.floor(diff / 60) });
1164
- if (diff < 86400) return t("time_hoursAgo", { n: Math.floor(diff / 3600) });
1165
- if (diff < 2592e3) return t("time_daysAgo", { n: Math.floor(diff / 86400) });
1166
- return t("time_monthsAgo", { n: Math.floor(diff / 2592e3) });
1748
+ // src/components/common/result-banner.tsx
1749
+ import { Box as Box12, Text as Text14 } from "ink";
1750
+ import { jsx as jsx14 } from "react/jsx-runtime";
1751
+ var STATUS_COLORS = {
1752
+ success: COLORS.success,
1753
+ error: COLORS.error,
1754
+ warning: COLORS.warning,
1755
+ info: COLORS.info
1756
+ };
1757
+ function ResultBanner({ status, message }) {
1758
+ return /* @__PURE__ */ jsx14(Box12, { borderStyle: "round", borderColor: STATUS_COLORS[status], paddingX: 2, paddingY: 0, children: /* @__PURE__ */ jsx14(Text14, { color: STATUS_COLORS[status], bold: true, children: message }) });
1167
1759
  }
1168
- function truncate(str, maxLen) {
1169
- if (str.length <= maxLen) return str;
1170
- return str.slice(0, maxLen - 1) + "\u2026";
1760
+
1761
+ // src/components/common/selectable-row.tsx
1762
+ import { Box as Box13, Text as Text15 } from "ink";
1763
+ import { jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
1764
+ function SelectableRow({ isCurrent, children, gap = 1 }) {
1765
+ return /* @__PURE__ */ jsxs14(Box13, { gap, children: [
1766
+ /* @__PURE__ */ jsx15(Text15, { color: isCurrent ? COLORS.success : COLORS.muted, children: isCurrent ? "\u25B6" : " " }),
1767
+ children
1768
+ ] });
1171
1769
  }
1172
1770
 
1173
1771
  // src/views/installed.tsx
1174
- import { jsx as jsx14, jsxs as jsxs14 } from "react/jsx-runtime";
1772
+ import { jsx as jsx16, jsxs as jsxs15 } from "react/jsx-runtime";
1175
1773
  function InstalledView() {
1176
1774
  const { formulae, casks, loading, errors, fetchInstalled } = useBrewStore();
1177
1775
  const navigate = useNavigationStore((s) => s.navigate);
@@ -1184,6 +1782,10 @@ function InstalledView() {
1184
1782
  const debouncedFilter = useDebounce(filter, 200);
1185
1783
  const stream = useBrewStream();
1186
1784
  const { openModal, closeModal } = useModalStore();
1785
+ const { stdout } = useStdout2();
1786
+ const cols = stdout?.columns ?? 80;
1787
+ const nameWidth = Math.floor(cols * 0.35);
1788
+ const versionWidth = Math.floor(cols * 0.15);
1187
1789
  useEffect5(() => {
1188
1790
  fetchInstalled();
1189
1791
  }, []);
@@ -1196,7 +1798,7 @@ function InstalledView() {
1196
1798
  }
1197
1799
  return void 0;
1198
1800
  }, [isSearching]);
1199
- const allItems = useMemo2(() => {
1801
+ const allItems = useMemo3(() => {
1200
1802
  const items = tab === "formulae" ? formulaeToListItems(formulae) : casksToListItems(casks);
1201
1803
  if (!debouncedFilter) return items;
1202
1804
  const lower = debouncedFilter.toLowerCase();
@@ -1206,6 +1808,13 @@ function InstalledView() {
1206
1808
  }, [formulae, casks, tab, debouncedFilter]);
1207
1809
  useInput3((input, key) => {
1208
1810
  if (confirmUninstall || stream.isRunning) return;
1811
+ if (!stream.isRunning && stream.lines.length > 0) {
1812
+ if (key.escape) {
1813
+ stream.clear();
1814
+ void fetchInstalled();
1815
+ }
1816
+ return;
1817
+ }
1209
1818
  if (isSearching) {
1210
1819
  if (key.escape) {
1211
1820
  setIsSearching(false);
@@ -1230,18 +1839,18 @@ function InstalledView() {
1230
1839
  } else if (input === "G") {
1231
1840
  setCursor(Math.max(allItems.length - 1, 0));
1232
1841
  } else if (key.return && allItems[cursor]) {
1233
- selectPackage(allItems[cursor].name);
1842
+ selectPackage(allItems[cursor].name, tab === "formulae" ? "formula" : "cask");
1234
1843
  navigate("package-info");
1235
1844
  } else if (input === "f") {
1236
1845
  setTab((t2) => t2 === "formulae" ? "casks" : "formulae");
1237
1846
  setCursor(0);
1238
1847
  }
1239
1848
  }, { isActive: true });
1240
- if (loading.installed) return /* @__PURE__ */ jsx14(Loading, { message: t("loading_installed") });
1241
- if (errors.installed) return /* @__PURE__ */ jsx14(ErrorMessage, { message: errors.installed });
1849
+ if (loading.installed) return /* @__PURE__ */ jsx16(Loading, { message: t("loading_installed") });
1850
+ if (errors.installed) return /* @__PURE__ */ jsx16(ErrorMessage, { message: errors.installed });
1242
1851
  if (stream.isRunning || stream.lines.length > 0) {
1243
- return /* @__PURE__ */ jsxs14(Box12, { flexDirection: "column", children: [
1244
- /* @__PURE__ */ jsx14(
1852
+ return /* @__PURE__ */ jsxs15(Box14, { flexDirection: "column", children: [
1853
+ /* @__PURE__ */ jsx16(
1245
1854
  ProgressLog,
1246
1855
  {
1247
1856
  lines: stream.lines,
@@ -1249,34 +1858,50 @@ function InstalledView() {
1249
1858
  title: t("pkgInfo_uninstalling", { name: "..." })
1250
1859
  }
1251
1860
  ),
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")}` }) }) })
1861
+ stream.isRunning && /* @__PURE__ */ jsxs15(Text16, { color: COLORS.textSecondary, children: [
1862
+ "esc:",
1863
+ t("hint_cancel")
1864
+ ] }),
1865
+ !stream.isRunning && /* @__PURE__ */ jsxs15(Box14, { flexDirection: "column", marginTop: 1, children: [
1866
+ /* @__PURE__ */ jsx16(
1867
+ ResultBanner,
1868
+ {
1869
+ status: stream.error ? "error" : "success",
1870
+ message: stream.error ? `\u2718 ${stream.error}` : `\u2714 ${t("pkgInfo_done")}`
1871
+ }
1872
+ ),
1873
+ /* @__PURE__ */ jsxs15(Text16, { color: COLORS.textSecondary, children: [
1874
+ "esc:",
1875
+ t("hint_back")
1876
+ ] })
1877
+ ] })
1253
1878
  ] });
1254
1879
  }
1255
- const MAX_VISIBLE_ROWS = 20;
1880
+ const MAX_VISIBLE_ROWS = Math.max(5, (stdout?.rows ?? 24) - 8);
1256
1881
  const start = Math.max(0, cursor - Math.floor(MAX_VISIBLE_ROWS / 2));
1257
1882
  const visible = allItems.slice(start, start + MAX_VISIBLE_ROWS);
1258
- return /* @__PURE__ */ jsxs14(Box12, { flexDirection: "column", children: [
1259
- /* @__PURE__ */ jsxs14(Box12, { marginBottom: 1, gap: 1, children: [
1260
- /* @__PURE__ */ jsx14(
1261
- Box12,
1883
+ return /* @__PURE__ */ jsxs15(Box14, { flexDirection: "column", children: [
1884
+ /* @__PURE__ */ jsxs15(Box14, { marginBottom: 1, gap: 1, children: [
1885
+ /* @__PURE__ */ jsx16(
1886
+ Box14,
1262
1887
  {
1263
1888
  borderStyle: "round",
1264
- borderColor: tab === "formulae" ? "#06B6D4" : "#6B7280",
1889
+ borderColor: tab === "formulae" ? COLORS.info : COLORS.textSecondary,
1265
1890
  paddingX: 1,
1266
- children: /* @__PURE__ */ jsxs14(Text14, { bold: tab === "formulae", color: tab === "formulae" ? "#06B6D4" : "#6B7280", children: [
1891
+ children: /* @__PURE__ */ jsxs15(Text16, { bold: tab === "formulae", color: tab === "formulae" ? COLORS.info : COLORS.textSecondary, children: [
1267
1892
  "\u{1F4E6}",
1268
1893
  " ",
1269
1894
  t("installed_formulaeCount", { count: formulae.length })
1270
1895
  ] })
1271
1896
  }
1272
1897
  ),
1273
- /* @__PURE__ */ jsx14(
1274
- Box12,
1898
+ /* @__PURE__ */ jsx16(
1899
+ Box14,
1275
1900
  {
1276
1901
  borderStyle: "round",
1277
- borderColor: tab === "casks" ? "#A855F7" : "#6B7280",
1902
+ borderColor: tab === "casks" ? COLORS.purple : COLORS.textSecondary,
1278
1903
  paddingX: 1,
1279
- children: /* @__PURE__ */ jsxs14(Text14, { bold: tab === "casks", color: tab === "casks" ? "#A855F7" : "#6B7280", children: [
1904
+ children: /* @__PURE__ */ jsxs15(Text16, { bold: tab === "casks", color: tab === "casks" ? COLORS.purple : COLORS.textSecondary, children: [
1280
1905
  "\u{1F37A}",
1281
1906
  " ",
1282
1907
  t("installed_casksCount", { count: casks.length })
@@ -1284,7 +1909,7 @@ function InstalledView() {
1284
1909
  }
1285
1910
  )
1286
1911
  ] }),
1287
- confirmUninstall && /* @__PURE__ */ jsx14(
1912
+ confirmUninstall && /* @__PURE__ */ jsx16(
1288
1913
  ConfirmDialog,
1289
1914
  {
1290
1915
  message: t("installed_confirmUninstall", { name: confirmUninstall }),
@@ -1298,53 +1923,53 @@ function InstalledView() {
1298
1923
  onCancel: () => setConfirmUninstall(null)
1299
1924
  }
1300
1925
  ),
1301
- isSearching && /* @__PURE__ */ jsx14(Box12, { marginBottom: 1, borderStyle: "round", borderColor: "#FFD700", paddingX: 1, children: /* @__PURE__ */ jsx14(SearchInput, { defaultValue: filter, onChange: setFilter, isActive: isSearching }) }),
1302
- /* @__PURE__ */ jsxs14(Box12, { gap: 1, borderStyle: "single", borderBottom: true, borderTop: false, borderLeft: false, borderRight: false, borderColor: "#4B5563", children: [
1303
- /* @__PURE__ */ jsxs14(Text14, { color: "#F9FAFB", bold: true, children: [
1926
+ isSearching && /* @__PURE__ */ jsx16(Box14, { marginBottom: 1, borderStyle: "round", borderColor: COLORS.gold, paddingX: 1, children: /* @__PURE__ */ jsx16(SearchInput, { defaultValue: filter, onChange: setFilter, isActive: isSearching }) }),
1927
+ /* @__PURE__ */ jsxs15(Box14, { gap: 1, borderStyle: "single", borderBottom: true, borderTop: false, borderLeft: false, borderRight: false, borderColor: COLORS.border, children: [
1928
+ /* @__PURE__ */ jsxs15(Text16, { color: COLORS.text, bold: true, children: [
1304
1929
  " ",
1305
- "Package".padEnd(27)
1930
+ t("installed_col_package").padEnd(nameWidth)
1306
1931
  ] }),
1307
- /* @__PURE__ */ jsx14(Text14, { color: "#F9FAFB", bold: true, children: "Version".padEnd(12) }),
1308
- /* @__PURE__ */ jsx14(Text14, { color: "#F9FAFB", bold: true, children: "Status" })
1932
+ /* @__PURE__ */ jsx16(Text16, { color: COLORS.text, bold: true, children: t("installed_col_version").padEnd(versionWidth) }),
1933
+ /* @__PURE__ */ jsx16(Text16, { color: COLORS.text, bold: true, children: t("installed_col_status") })
1309
1934
  ] }),
1310
- /* @__PURE__ */ jsxs14(Box12, { flexDirection: "column", children: [
1311
- visible.length === 0 && /* @__PURE__ */ jsx14(Box12, { paddingY: 1, justifyContent: "center", children: /* @__PURE__ */ jsx14(Text14, { color: "#6B7280", italic: true, children: t("installed_noPackages") }) }),
1312
- start > 0 && /* @__PURE__ */ jsxs14(Text14, { color: "#6B7280", dimColor: true, children: [
1935
+ /* @__PURE__ */ jsxs15(Box14, { flexDirection: "column", children: [
1936
+ visible.length === 0 && /* @__PURE__ */ jsx16(Box14, { paddingY: 1, justifyContent: "center", children: /* @__PURE__ */ jsx16(Text16, { color: COLORS.textSecondary, italic: true, children: t("installed_noPackages") }) }),
1937
+ start > 0 && /* @__PURE__ */ jsxs15(Text16, { color: COLORS.textSecondary, dimColor: true, children: [
1313
1938
  " ",
1314
1939
  t("scroll_moreAbove", { count: start })
1315
1940
  ] }),
1316
1941
  visible.map((item, i) => {
1317
1942
  const idx = start + i;
1318
1943
  const isCurrent = idx === cursor;
1319
- return /* @__PURE__ */ jsxs14(Box12, { gap: 1, children: [
1320
- /* @__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) }),
1323
- item.outdated && /* @__PURE__ */ jsx14(StatusBadge, { label: t("badge_outdated"), variant: "warning" }),
1324
- item.pinned && /* @__PURE__ */ jsx14(StatusBadge, { label: t("badge_pinned"), variant: "info" }),
1325
- item.kegOnly && /* @__PURE__ */ jsx14(StatusBadge, { label: t("badge_kegOnly"), variant: "muted" }),
1326
- item.installedAsDependency && /* @__PURE__ */ jsx14(StatusBadge, { label: t("badge_dep"), variant: "muted" }),
1327
- !item.outdated && !item.pinned && !item.kegOnly && !item.installedAsDependency && /* @__PURE__ */ jsx14(Text14, { color: "#6B7280", dimColor: true, children: truncate(item.desc, 30) })
1944
+ return /* @__PURE__ */ jsxs15(SelectableRow, { isCurrent, children: [
1945
+ /* @__PURE__ */ jsx16(Text16, { bold: isCurrent, inverse: isCurrent, color: isCurrent ? COLORS.text : COLORS.muted, children: truncate(item.name, nameWidth).padEnd(nameWidth) }),
1946
+ /* @__PURE__ */ jsx16(Text16, { color: COLORS.teal, children: item.version.padEnd(versionWidth) }),
1947
+ item.outdated && /* @__PURE__ */ jsx16(StatusBadge, { label: t("badge_outdated"), variant: "warning" }),
1948
+ item.pinned && /* @__PURE__ */ jsx16(StatusBadge, { label: t("badge_pinned"), variant: "info" }),
1949
+ item.kegOnly && /* @__PURE__ */ jsx16(StatusBadge, { label: t("badge_kegOnly"), variant: "muted" }),
1950
+ item.installedAsDependency && /* @__PURE__ */ jsx16(StatusBadge, { label: t("badge_dep"), variant: "muted" }),
1951
+ !item.outdated && !item.pinned && !item.kegOnly && !item.installedAsDependency && /* @__PURE__ */ jsx16(Text16, { color: COLORS.textSecondary, dimColor: true, children: truncate(item.desc, 30) })
1328
1952
  ] }, item.name);
1329
1953
  }),
1330
- start + MAX_VISIBLE_ROWS < allItems.length && /* @__PURE__ */ jsxs14(Text14, { color: "#6B7280", dimColor: true, children: [
1954
+ start + MAX_VISIBLE_ROWS < allItems.length && /* @__PURE__ */ jsxs15(Text16, { color: COLORS.textSecondary, dimColor: true, children: [
1331
1955
  " ",
1332
1956
  t("scroll_moreBelow", { count: allItems.length - start - MAX_VISIBLE_ROWS })
1333
1957
  ] })
1334
1958
  ] }),
1335
- /* @__PURE__ */ jsx14(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx14(Text14, { color: "#F9FAFB", bold: true, children: allItems.length > 0 ? `${cursor + 1}/${allItems.length}` : "0/0" }) })
1959
+ /* @__PURE__ */ jsx16(Box14, { marginTop: 1, children: /* @__PURE__ */ jsx16(Text16, { color: COLORS.text, bold: true, children: allItems.length > 0 ? `${cursor + 1}/${allItems.length}` : "0/0" }) })
1336
1960
  ] });
1337
1961
  }
1338
1962
 
1339
1963
  // src/views/search.tsx
1340
1964
  import { useState as useState4, useCallback as useCallback2, useEffect as useEffect6, useRef as useRef2 } from "react";
1341
- import { Box as Box13, Text as Text15, useInput as useInput4 } from "ink";
1965
+ import { Box as Box15, Text as Text17, useInput as useInput4 } from "ink";
1342
1966
  import { TextInput as TextInput2 } from "@inkjs/ui";
1343
- import { jsx as jsx15, jsxs as jsxs15 } from "react/jsx-runtime";
1967
+ import { jsx as jsx17, jsxs as jsxs16 } from "react/jsx-runtime";
1344
1968
  function SearchView() {
1345
1969
  const [query, setQuery] = useState4("");
1346
1970
  const [results, setResults] = useState4(null);
1347
1971
  const [searching, setSearching] = useState4(false);
1972
+ const [searchError, setSearchError] = useState4(null);
1348
1973
  const [cursor, setCursor] = useState4(0);
1349
1974
  const [confirmInstall, setConfirmInstall] = useState4(null);
1350
1975
  const stream = useBrewStream();
@@ -1363,15 +1988,20 @@ function SearchView() {
1363
1988
  return void 0;
1364
1989
  }, [results]);
1365
1990
  const doSearch = useCallback2(async (term) => {
1366
- if (term.length < 2) return;
1991
+ if (term.length < 2) {
1992
+ setResults(null);
1993
+ setSearchError(t("search_minChars"));
1994
+ return;
1995
+ }
1367
1996
  setSearching(true);
1997
+ setSearchError(null);
1368
1998
  try {
1369
1999
  const r = await search(term);
1370
2000
  setResults(r);
1371
2001
  setCursor(0);
1372
2002
  } catch (err) {
1373
2003
  setResults({ formulae: [], casks: [] });
1374
- void err;
2004
+ setSearchError(err instanceof Error ? err.message : t("search_failed"));
1375
2005
  } finally {
1376
2006
  setSearching(false);
1377
2007
  }
@@ -1382,28 +2012,37 @@ function SearchView() {
1382
2012
  void fetchInstalled();
1383
2013
  }
1384
2014
  }, [stream.isRunning, stream.error]);
1385
- const allResults = results ? [...results.formulae, ...results.casks] : [];
2015
+ const MAX_VISIBLE = 20;
2016
+ const visibleFormulae = results ? results.formulae.slice(0, MAX_VISIBLE) : [];
2017
+ const visibleCasks = results ? results.casks.slice(0, MAX_VISIBLE) : [];
2018
+ const allVisible = [...visibleFormulae, ...visibleCasks];
1386
2019
  useInput4((input, key) => {
1387
2020
  if (stream.isRunning) {
1388
2021
  if (key.escape) stream.cancel();
1389
2022
  return;
1390
2023
  }
2024
+ if (stream.lines.length > 0) {
2025
+ if (key.escape) {
2026
+ stream.clear();
2027
+ }
2028
+ return;
2029
+ }
1391
2030
  if (confirmInstall) return;
1392
2031
  if (key.return && !results) {
1393
2032
  void doSearch(query);
1394
2033
  return;
1395
2034
  }
1396
- if (key.return && allResults[cursor]) {
1397
- selectPackage(allResults[cursor]);
2035
+ if (key.return && allVisible[cursor]) {
2036
+ selectPackage(allVisible[cursor]);
1398
2037
  navigate("package-info");
1399
2038
  return;
1400
2039
  }
1401
- if (input === "i" && allResults[cursor]) {
1402
- setConfirmInstall(allResults[cursor]);
2040
+ if (input === "i" && allVisible[cursor]) {
2041
+ setConfirmInstall(allVisible[cursor]);
1403
2042
  return;
1404
2043
  }
1405
2044
  if (input === "j" || key.downArrow) {
1406
- setCursor((c) => Math.min(c + 1, Math.max(0, allResults.length - 1)));
2045
+ setCursor((c) => Math.min(c + 1, Math.max(0, allVisible.length - 1)));
1407
2046
  } else if (input === "k" || key.upArrow) {
1408
2047
  setCursor((c) => Math.max(c - 1, 0));
1409
2048
  } else if (key.escape) {
@@ -1412,8 +2051,8 @@ function SearchView() {
1412
2051
  }
1413
2052
  });
1414
2053
  if (stream.isRunning || stream.lines.length > 0) {
1415
- return /* @__PURE__ */ jsxs15(Box13, { flexDirection: "column", children: [
1416
- /* @__PURE__ */ jsx15(
2054
+ return /* @__PURE__ */ jsxs16(Box15, { flexDirection: "column", children: [
2055
+ /* @__PURE__ */ jsx17(
1417
2056
  ProgressLog,
1418
2057
  {
1419
2058
  lines: stream.lines,
@@ -1421,26 +2060,32 @@ function SearchView() {
1421
2060
  title: t("search_installing")
1422
2061
  }
1423
2062
  ),
1424
- stream.isRunning && /* @__PURE__ */ jsxs15(Text15, { color: "#6B7280", children: [
2063
+ stream.isRunning && /* @__PURE__ */ jsxs16(Text17, { color: COLORS.textSecondary, children: [
1425
2064
  "esc:",
1426
2065
  t("hint_cancel")
1427
2066
  ] }),
1428
- !stream.isRunning && /* @__PURE__ */ jsxs15(Box13, { flexDirection: "column", marginTop: 1, children: [
1429
- /* @__PURE__ */ jsx15(Box13, { borderStyle: "round", borderColor: stream.error ? "#EF4444" : "#22C55E", paddingX: 2, paddingY: 0, children: /* @__PURE__ */ jsx15(Text15, { color: stream.error ? "#EF4444" : "#22C55E", bold: true, children: stream.error ? `\u2718 ${stream.error}` : `\u2714 ${t("search_installComplete")}` }) }),
1430
- /* @__PURE__ */ jsxs15(Text15, { color: "#6B7280", children: [
2067
+ !stream.isRunning && /* @__PURE__ */ jsxs16(Box15, { flexDirection: "column", marginTop: 1, children: [
2068
+ /* @__PURE__ */ jsx17(
2069
+ ResultBanner,
2070
+ {
2071
+ status: stream.error ? "error" : "success",
2072
+ message: stream.error ? `\u2718 ${stream.error}` : `\u2714 ${t("search_installComplete")}`
2073
+ }
2074
+ ),
2075
+ /* @__PURE__ */ jsxs16(Text17, { color: COLORS.textSecondary, children: [
1431
2076
  "esc:",
1432
- t("hint_back")
2077
+ t("hint_clear")
1433
2078
  ] })
1434
2079
  ] })
1435
2080
  ] });
1436
2081
  }
1437
- return /* @__PURE__ */ jsxs15(Box13, { flexDirection: "column", children: [
1438
- /* @__PURE__ */ jsxs15(Box13, { marginBottom: 1, children: [
1439
- /* @__PURE__ */ jsxs15(Text15, { color: "#FFD700", children: [
2082
+ return /* @__PURE__ */ jsxs16(Box15, { flexDirection: "column", children: [
2083
+ /* @__PURE__ */ jsxs16(Box15, { marginBottom: 1, children: [
2084
+ /* @__PURE__ */ jsxs16(Text17, { color: COLORS.gold, children: [
1440
2085
  "\u{1F50D}",
1441
2086
  " "
1442
2087
  ] }),
1443
- !results ? /* @__PURE__ */ jsx15(
2088
+ !results ? /* @__PURE__ */ jsx17(
1444
2089
  TextInput2,
1445
2090
  {
1446
2091
  placeholder: t("search_placeholder"),
@@ -1448,16 +2093,17 @@ function SearchView() {
1448
2093
  onChange: setQuery,
1449
2094
  onSubmit: () => void doSearch(query)
1450
2095
  }
1451
- ) : /* @__PURE__ */ jsxs15(Text15, { children: [
2096
+ ) : /* @__PURE__ */ jsxs16(Text17, { children: [
1452
2097
  t("search_resultsFor"),
1453
2098
  ' "',
1454
- /* @__PURE__ */ jsx15(Text15, { bold: true, color: "#F9FAFB", children: query }),
2099
+ /* @__PURE__ */ jsx17(Text17, { bold: true, color: COLORS.text, children: query }),
1455
2100
  '" ',
1456
- /* @__PURE__ */ jsx15(Text15, { color: "#6B7280", children: t("search_escToClear") })
2101
+ /* @__PURE__ */ jsx17(Text17, { color: COLORS.textSecondary, children: t("search_escToClear") })
1457
2102
  ] })
1458
2103
  ] }),
1459
- searching && /* @__PURE__ */ jsx15(Loading, { message: t("loading_searching") }),
1460
- confirmInstall && /* @__PURE__ */ jsx15(
2104
+ searching && /* @__PURE__ */ jsx17(Loading, { message: t("loading_searching") }),
2105
+ searchError && /* @__PURE__ */ jsx17(Box15, { marginBottom: 1, children: /* @__PURE__ */ jsx17(Text17, { color: COLORS.error, children: searchError }) }),
2106
+ confirmInstall && /* @__PURE__ */ jsx17(
1461
2107
  ConfirmDialog,
1462
2108
  {
1463
2109
  message: t("search_confirmInstall", { name: confirmInstall }),
@@ -1470,46 +2116,40 @@ function SearchView() {
1470
2116
  onCancel: () => setConfirmInstall(null)
1471
2117
  }
1472
2118
  ),
1473
- results && !searching && !confirmInstall && /* @__PURE__ */ jsxs15(Box13, { flexDirection: "column", children: [
1474
- results.formulae.length > 0 && /* @__PURE__ */ jsxs15(Box13, { flexDirection: "column", marginBottom: 1, children: [
1475
- /* @__PURE__ */ jsx15(Text15, { bold: true, color: "#06B6D4", children: t("search_formulaeHeader", { count: results.formulae.length }) }),
1476
- results.formulae.slice(0, 20).map((name, i) => {
2119
+ results && !searching && !confirmInstall && /* @__PURE__ */ jsxs16(Box15, { flexDirection: "column", children: [
2120
+ visibleFormulae.length > 0 && /* @__PURE__ */ jsxs16(Box15, { flexDirection: "column", marginBottom: 1, children: [
2121
+ /* @__PURE__ */ jsx17(Text17, { bold: true, color: COLORS.info, children: t("search_formulaeHeader", { count: results.formulae.length }) }),
2122
+ visibleFormulae.map((name, i) => {
1477
2123
  const isCurrent = i === cursor;
1478
- return /* @__PURE__ */ jsxs15(Box13, { gap: 1, children: [
1479
- /* @__PURE__ */ jsx15(Text15, { color: isCurrent ? "#22C55E" : "#9CA3AF", children: isCurrent ? "\u25B6" : " " }),
1480
- /* @__PURE__ */ jsx15(Text15, { bold: isCurrent, inverse: isCurrent, children: name })
1481
- ] }, name);
2124
+ return /* @__PURE__ */ jsx17(SelectableRow, { isCurrent, children: /* @__PURE__ */ jsx17(Text17, { bold: isCurrent, inverse: isCurrent, children: name }) }, name);
1482
2125
  }),
1483
- results.formulae.length > 20 && /* @__PURE__ */ jsxs15(Text15, { color: "#6B7280", dimColor: true, children: [
2126
+ results.formulae.length > MAX_VISIBLE && /* @__PURE__ */ jsxs16(Text17, { color: COLORS.textSecondary, dimColor: true, children: [
1484
2127
  " ",
1485
- t("scroll_moreBelow", { count: results.formulae.length - 20 })
2128
+ t("scroll_moreBelow", { count: results.formulae.length - MAX_VISIBLE })
1486
2129
  ] })
1487
2130
  ] }),
1488
- results.casks.length > 0 && /* @__PURE__ */ jsxs15(Box13, { flexDirection: "column", children: [
1489
- /* @__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;
2131
+ visibleCasks.length > 0 && /* @__PURE__ */ jsxs16(Box15, { flexDirection: "column", children: [
2132
+ /* @__PURE__ */ jsx17(Text17, { bold: true, color: COLORS.purple, children: t("search_casksHeader", { count: results.casks.length }) }),
2133
+ visibleCasks.map((name, i) => {
2134
+ const idx = visibleFormulae.length + i;
1492
2135
  const isCurrent = idx === cursor;
1493
- return /* @__PURE__ */ jsxs15(Box13, { gap: 1, children: [
1494
- /* @__PURE__ */ jsx15(Text15, { color: isCurrent ? "#22C55E" : "#9CA3AF", children: isCurrent ? "\u25B6" : " " }),
1495
- /* @__PURE__ */ jsx15(Text15, { bold: isCurrent, inverse: isCurrent, children: name })
1496
- ] }, name);
2136
+ return /* @__PURE__ */ jsx17(SelectableRow, { isCurrent, children: /* @__PURE__ */ jsx17(Text17, { bold: isCurrent, inverse: isCurrent, children: name }) }, name);
1497
2137
  }),
1498
- results.casks.length > 20 && /* @__PURE__ */ jsxs15(Text15, { color: "#6B7280", dimColor: true, children: [
2138
+ results.casks.length > MAX_VISIBLE && /* @__PURE__ */ jsxs16(Text17, { color: COLORS.textSecondary, dimColor: true, children: [
1499
2139
  " ",
1500
- t("scroll_moreBelow", { count: results.casks.length - 20 })
2140
+ t("scroll_moreBelow", { count: results.casks.length - MAX_VISIBLE })
1501
2141
  ] })
1502
2142
  ] }),
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}` : "" }) })
2143
+ allVisible.length === 0 && /* @__PURE__ */ jsx17(Box15, { borderStyle: "round", borderColor: COLORS.textSecondary, paddingX: 2, children: /* @__PURE__ */ jsx17(Text17, { color: COLORS.textSecondary, italic: true, children: t("search_noResults") }) }),
2144
+ /* @__PURE__ */ jsx17(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx17(Text17, { color: COLORS.text, bold: true, children: allVisible.length > 0 ? `${cursor + 1}/${allVisible.length}` : "" }) })
1505
2145
  ] })
1506
2146
  ] });
1507
2147
  }
1508
2148
 
1509
2149
  // src/views/outdated.tsx
1510
2150
  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";
1512
- import { jsx as jsx16, jsxs as jsxs16 } from "react/jsx-runtime";
2151
+ import { Box as Box16, Text as Text18, useInput as useInput5, useStdout as useStdout3 } from "ink";
2152
+ import { jsx as jsx18, jsxs as jsxs17 } from "react/jsx-runtime";
1513
2153
  function OutdatedView() {
1514
2154
  const { outdated, loading, errors, fetchOutdated } = useBrewStore();
1515
2155
  const stream = useBrewStream();
@@ -1531,6 +2171,17 @@ function OutdatedView() {
1531
2171
  if (key.escape) stream.cancel();
1532
2172
  return;
1533
2173
  }
2174
+ if (stream.lines.length > 0) {
2175
+ if (key.escape) {
2176
+ stream.clear();
2177
+ return;
2178
+ }
2179
+ if (input === "r") {
2180
+ stream.clear();
2181
+ void fetchOutdated();
2182
+ }
2183
+ return;
2184
+ }
1534
2185
  if (confirmAction) return;
1535
2186
  if (input === "j" || key.downArrow) {
1536
2187
  setCursor((c) => Math.min(c + 1, Math.max(0, allOutdated.length - 1)));
@@ -1542,17 +2193,21 @@ function OutdatedView() {
1542
2193
  setConfirmAction({ type: "all" });
1543
2194
  } else if (input === "p" && allOutdated[cursor]) {
1544
2195
  const pkg = allOutdated[cursor];
1545
- void execBrew([pkg.pinned ? "unpin" : "pin", pkg.name]).then(() => void fetchOutdated());
2196
+ void (pkg.pinned ? unpinPackage(pkg.name) : pinPackage(pkg.name)).then(() => void fetchOutdated());
1546
2197
  return;
1547
2198
  } else if (input === "r") {
1548
2199
  void fetchOutdated();
1549
2200
  }
1550
2201
  });
1551
- if (loading.outdated) return /* @__PURE__ */ jsx16(Loading, { message: t("loading_outdated") });
1552
- if (errors.outdated) return /* @__PURE__ */ jsx16(ErrorMessage, { message: errors.outdated });
2202
+ const { stdout } = useStdout3();
2203
+ const MAX_VISIBLE_ROWS = Math.max(5, (stdout?.rows ?? 24) - 8);
2204
+ const start = Math.max(0, cursor - Math.floor(MAX_VISIBLE_ROWS / 2));
2205
+ const visible = allOutdated.slice(start, start + MAX_VISIBLE_ROWS);
2206
+ if (loading.outdated) return /* @__PURE__ */ jsx18(Loading, { message: t("loading_outdated") });
2207
+ if (errors.outdated) return /* @__PURE__ */ jsx18(ErrorMessage, { message: errors.outdated });
1553
2208
  if (stream.isRunning || stream.lines.length > 0) {
1554
- return /* @__PURE__ */ jsxs16(Box14, { flexDirection: "column", children: [
1555
- /* @__PURE__ */ jsx16(
2209
+ return /* @__PURE__ */ jsxs17(Box16, { flexDirection: "column", children: [
2210
+ /* @__PURE__ */ jsx18(
1556
2211
  ProgressLog,
1557
2212
  {
1558
2213
  lines: stream.lines,
@@ -1560,33 +2215,35 @@ function OutdatedView() {
1560
2215
  title: t("outdated_upgrading")
1561
2216
  }
1562
2217
  ),
1563
- stream.isRunning && /* @__PURE__ */ jsxs16(Text16, { color: "#6B7280", children: [
2218
+ stream.isRunning && /* @__PURE__ */ jsxs17(Text18, { color: COLORS.textSecondary, children: [
1564
2219
  "esc:",
1565
2220
  t("hint_cancel")
1566
2221
  ] }),
1567
- !stream.isRunning && /* @__PURE__ */ jsxs16(Box14, { flexDirection: "column", marginTop: 1, children: [
1568
- /* @__PURE__ */ jsxs16(Box14, { borderStyle: "round", borderColor: stream.error ? "#EF4444" : "#22C55E", paddingX: 2, paddingY: 0, children: [
1569
- /* @__PURE__ */ jsx16(Text16, { color: stream.error ? "#EF4444" : "#22C55E", bold: true, children: stream.error ? `\u2718 ${stream.error}` : `\u2714 ${t("outdated_upgradeComplete")}` }),
1570
- /* @__PURE__ */ jsxs16(Text16, { color: "#9CA3AF", children: [
2222
+ !stream.isRunning && /* @__PURE__ */ jsxs17(Box16, { flexDirection: "column", marginTop: 1, children: [
2223
+ /* @__PURE__ */ jsxs17(Box16, { borderStyle: "round", borderColor: stream.error ? COLORS.error : COLORS.success, paddingX: 2, paddingY: 0, children: [
2224
+ /* @__PURE__ */ jsx18(Text18, { color: stream.error ? COLORS.error : COLORS.success, bold: true, children: stream.error ? `\u2718 ${stream.error}` : `\u2714 ${t("outdated_upgradeComplete")}` }),
2225
+ /* @__PURE__ */ jsxs17(Text18, { color: COLORS.muted, children: [
1571
2226
  " ",
1572
2227
  t("outdated_pressRefresh")
1573
2228
  ] })
1574
2229
  ] }),
1575
- /* @__PURE__ */ jsxs16(Text16, { color: "#6B7280", children: [
2230
+ /* @__PURE__ */ jsxs17(Text18, { color: COLORS.textSecondary, children: [
1576
2231
  "r:",
1577
2232
  t("hint_refresh"),
1578
2233
  " esc:",
1579
- t("hint_back")
2234
+ t("hint_clear")
1580
2235
  ] })
1581
2236
  ] })
1582
2237
  ] });
1583
2238
  }
1584
- return /* @__PURE__ */ jsxs16(Box14, { flexDirection: "column", children: [
1585
- /* @__PURE__ */ jsx16(SectionHeader, { emoji: "\u{1F4E6}", title: t("outdated_title", { count: allOutdated.length }), gradient: GRADIENTS.fire }),
1586
- confirmAction && /* @__PURE__ */ jsx16(Box14, { marginY: 1, children: /* @__PURE__ */ jsx16(
2239
+ const upgradeAllMessage = confirmAction?.type === "all" ? `${t("outdated_confirmAll", { count: allOutdated.length })}
2240
+ ${t("outdated_upgradeAllList", { list: allOutdated.map((p) => p.name).join(", ") })}` : "";
2241
+ return /* @__PURE__ */ jsxs17(Box16, { flexDirection: "column", children: [
2242
+ /* @__PURE__ */ jsx18(SectionHeader, { emoji: "\u{1F4E6}", title: t("outdated_title", { count: allOutdated.length }), gradient: GRADIENTS.fire }),
2243
+ confirmAction && /* @__PURE__ */ jsx18(Box16, { marginY: 1, children: /* @__PURE__ */ jsx18(
1587
2244
  ConfirmDialog,
1588
2245
  {
1589
- message: confirmAction.type === "all" ? t("outdated_confirmAll", { count: allOutdated.length }) : t("outdated_confirmSingle", { name: confirmAction.type === "single" ? confirmAction.name : "" }),
2246
+ message: confirmAction.type === "all" ? upgradeAllMessage : t("outdated_confirmSingle", { name: confirmAction.type === "single" ? confirmAction.name : "" }),
1590
2247
  onConfirm: () => {
1591
2248
  hasRefreshed.current = false;
1592
2249
  if (confirmAction.type === "all") {
@@ -1599,22 +2256,26 @@ function OutdatedView() {
1599
2256
  onCancel: () => setConfirmAction(null)
1600
2257
  }
1601
2258
  ) }),
1602
- allOutdated.length === 0 && !confirmAction && /* @__PURE__ */ jsx16(Box14, { marginTop: 1, children: /* @__PURE__ */ jsx16(Box14, { borderStyle: "round", borderColor: "#22C55E", paddingX: 2, paddingY: 0, children: /* @__PURE__ */ jsxs16(Text16, { color: "#22C55E", bold: true, children: [
1603
- "\u2714",
1604
- " ",
1605
- t("outdated_upToDate")
1606
- ] }) }) }),
1607
- allOutdated.length > 0 && !confirmAction && /* @__PURE__ */ jsxs16(Box14, { flexDirection: "column", marginTop: 1, children: [
1608
- allOutdated.map((pkg, i) => {
1609
- const isCurrent = i === cursor;
1610
- return /* @__PURE__ */ jsxs16(Box14, { gap: 1, children: [
1611
- /* @__PURE__ */ jsx16(Text16, { color: isCurrent ? "#22C55E" : "#9CA3AF", children: isCurrent ? "\u25B6" : " " }),
1612
- /* @__PURE__ */ jsx16(Text16, { bold: isCurrent, inverse: isCurrent, color: isCurrent ? "#F9FAFB" : "#9CA3AF", children: pkg.name }),
1613
- /* @__PURE__ */ jsx16(VersionArrow, { current: pkg.installed_versions[0] ?? "", latest: pkg.current_version }),
1614
- pkg.pinned && /* @__PURE__ */ jsx16(StatusBadge, { label: t("outdated_pinned"), variant: "info" })
2259
+ allOutdated.length === 0 && !confirmAction && /* @__PURE__ */ jsx18(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx18(ResultBanner, { status: "success", message: `\u2714 ${t("outdated_upToDate")}` }) }),
2260
+ allOutdated.length > 0 && !confirmAction && /* @__PURE__ */ jsxs17(Box16, { flexDirection: "column", marginTop: 1, children: [
2261
+ start > 0 && /* @__PURE__ */ jsxs17(Text18, { color: COLORS.textSecondary, dimColor: true, children: [
2262
+ " ",
2263
+ t("scroll_moreAbove", { count: start })
2264
+ ] }),
2265
+ visible.map((pkg, i) => {
2266
+ const idx = start + i;
2267
+ const isCurrent = idx === cursor;
2268
+ return /* @__PURE__ */ jsxs17(SelectableRow, { isCurrent, children: [
2269
+ /* @__PURE__ */ jsx18(Text18, { bold: isCurrent, inverse: isCurrent, color: isCurrent ? COLORS.text : COLORS.muted, children: pkg.name }),
2270
+ /* @__PURE__ */ jsx18(VersionArrow, { current: pkg.installed_versions[0] ?? "", latest: pkg.current_version }),
2271
+ pkg.pinned && /* @__PURE__ */ jsx18(StatusBadge, { label: t("outdated_pinned"), variant: "info" })
1615
2272
  ] }, pkg.name);
1616
2273
  }),
1617
- /* @__PURE__ */ jsx16(Box14, { marginTop: 1, children: /* @__PURE__ */ jsxs16(Text16, { color: "#F9FAFB", bold: true, children: [
2274
+ start + MAX_VISIBLE_ROWS < allOutdated.length && /* @__PURE__ */ jsxs17(Text18, { color: COLORS.textSecondary, dimColor: true, children: [
2275
+ " ",
2276
+ t("scroll_moreBelow", { count: allOutdated.length - start - MAX_VISIBLE_ROWS })
2277
+ ] }),
2278
+ /* @__PURE__ */ jsx18(Box16, { marginTop: 1, children: /* @__PURE__ */ jsxs17(Text18, { color: COLORS.text, bold: true, children: [
1618
2279
  cursor + 1,
1619
2280
  "/",
1620
2281
  allOutdated.length
@@ -1625,8 +2286,8 @@ function OutdatedView() {
1625
2286
 
1626
2287
  // src/views/package-info.tsx
1627
2288
  import { useEffect as useEffect8, useRef as useRef4, useState as useState6 } from "react";
1628
- import { Box as Box15, Text as Text17, useInput as useInput6 } from "ink";
1629
- import { Fragment as Fragment4, jsx as jsx17, jsxs as jsxs17 } from "react/jsx-runtime";
2289
+ import { Box as Box17, Text as Text19, useInput as useInput6 } from "ink";
2290
+ import { Fragment as Fragment4, jsx as jsx19, jsxs as jsxs18 } from "react/jsx-runtime";
1630
2291
  var ACTION_PROGRESS_KEYS = {
1631
2292
  install: "pkgInfo_installing",
1632
2293
  uninstall: "pkgInfo_uninstalling",
@@ -1639,6 +2300,7 @@ var ACTION_CONFIRM_KEYS = {
1639
2300
  };
1640
2301
  function PackageInfoView() {
1641
2302
  const packageName = useNavigationStore((s) => s.selectedPackage);
2303
+ const packageType = useNavigationStore((s) => s.selectedPackageType);
1642
2304
  const [formula, setFormula] = useState6(null);
1643
2305
  const [loading, setLoading2] = useState6(true);
1644
2306
  const [error, setError2] = useState6(null);
@@ -1656,22 +2318,60 @@ function PackageInfoView() {
1656
2318
  useEffect8(() => {
1657
2319
  if (!packageName) return;
1658
2320
  setLoading2(true);
1659
- getFormulaInfo(packageName).then((f) => {
2321
+ const fetchInfo = async () => {
2322
+ if (packageType === "cask") {
2323
+ const caskInfo = await getCaskInfo(packageName);
2324
+ if (caskInfo && mountedRef.current) {
2325
+ const formulaLike = {
2326
+ name: caskInfo.token,
2327
+ full_name: caskInfo.full_token,
2328
+ tap: "",
2329
+ desc: caskInfo.desc,
2330
+ license: "",
2331
+ homepage: caskInfo.homepage,
2332
+ versions: { stable: caskInfo.version, head: null, bottle: false },
2333
+ dependencies: [],
2334
+ build_dependencies: [],
2335
+ installed: caskInfo.installed ? [{
2336
+ version: caskInfo.installed,
2337
+ used_options: [],
2338
+ built_as_bottle: false,
2339
+ poured_from_bottle: false,
2340
+ time: caskInfo.installed_time ?? 0,
2341
+ runtime_dependencies: [],
2342
+ installed_as_dependency: false,
2343
+ installed_on_request: true
2344
+ }] : [],
2345
+ linked_keg: null,
2346
+ pinned: false,
2347
+ outdated: caskInfo.outdated,
2348
+ deprecated: false,
2349
+ keg_only: false,
2350
+ caveats: null
2351
+ };
2352
+ setFormula(formulaLike);
2353
+ setLoading2(false);
2354
+ return;
2355
+ }
2356
+ }
2357
+ const f = await getFormulaInfo(packageName);
1660
2358
  if (mountedRef.current) {
1661
2359
  setFormula(f);
1662
2360
  setLoading2(false);
1663
2361
  }
1664
- }).catch((err) => {
2362
+ };
2363
+ fetchInfo().catch((err) => {
1665
2364
  if (mountedRef.current) {
1666
2365
  setError2(err.message);
1667
2366
  setLoading2(false);
1668
2367
  }
1669
2368
  });
1670
- }, [packageName]);
2369
+ }, [packageName, packageType]);
1671
2370
  useEffect8(() => {
1672
2371
  if (!stream.isRunning && !stream.error && stream.lines.length > 0 && !hasRefreshed.current && packageName) {
1673
2372
  hasRefreshed.current = true;
1674
- getFormulaInfo(packageName).then((f) => {
2373
+ const refreshFn = packageType === "cask" ? getCaskInfo(packageName).then((c) => c ? { ...c, installed: c.installed ? [{ version: c.installed }] : [] } : null) : getFormulaInfo(packageName);
2374
+ refreshFn.then((f) => {
1675
2375
  if (mountedRef.current) {
1676
2376
  setFormula(f);
1677
2377
  }
@@ -1696,21 +2396,21 @@ function PackageInfoView() {
1696
2396
  }
1697
2397
  });
1698
2398
  if (!packageName) {
1699
- return /* @__PURE__ */ jsx17(Text17, { color: "#6B7280", italic: true, children: t("pkgInfo_noPackage") });
2399
+ return /* @__PURE__ */ jsx19(Text19, { color: COLORS.textSecondary, italic: true, children: t("pkgInfo_noPackage") });
1700
2400
  }
1701
- if (loading) return /* @__PURE__ */ jsx17(Loading, { message: t("loading_package", { name: packageName }) });
1702
- if (error) return /* @__PURE__ */ jsx17(ErrorMessage, { message: error });
1703
- if (!formula) return /* @__PURE__ */ jsx17(ErrorMessage, { message: t("pkgInfo_notFound") });
2401
+ if (loading) return /* @__PURE__ */ jsx19(Loading, { message: t("loading_package", { name: packageName }) });
2402
+ if (error) return /* @__PURE__ */ jsx19(ErrorMessage, { message: error });
2403
+ if (!formula) return /* @__PURE__ */ jsx19(ErrorMessage, { message: t("pkgInfo_notFound") });
1704
2404
  if (stream.isRunning || stream.lines.length > 0) {
1705
- return /* @__PURE__ */ jsxs17(Box15, { flexDirection: "column", children: [
1706
- /* @__PURE__ */ jsx17(ProgressLog, { lines: stream.lines, isRunning: stream.isRunning, title: t(ACTION_PROGRESS_KEYS[activeActionRef.current] ?? ACTION_PROGRESS_KEYS["install"], { name: formula.name }) }),
1707
- stream.isRunning && /* @__PURE__ */ jsxs17(Text17, { color: "#6B7280", children: [
2405
+ return /* @__PURE__ */ jsxs18(Box17, { flexDirection: "column", children: [
2406
+ /* @__PURE__ */ jsx19(ProgressLog, { lines: stream.lines, isRunning: stream.isRunning, title: t(ACTION_PROGRESS_KEYS[activeActionRef.current] ?? ACTION_PROGRESS_KEYS["install"], { name: formula.name }) }),
2407
+ stream.isRunning && /* @__PURE__ */ jsxs18(Text19, { color: COLORS.textSecondary, children: [
1708
2408
  "esc:",
1709
2409
  t("hint_cancel")
1710
2410
  ] }),
1711
- !stream.isRunning && /* @__PURE__ */ jsxs17(Fragment4, { children: [
1712
- /* @__PURE__ */ jsx17(Text17, { color: stream.error ? "#EF4444" : "#22C55E", bold: true, children: stream.error ? `\u2718 ${stream.error}` : `\u2714 ${t("pkgInfo_done")}` }),
1713
- /* @__PURE__ */ jsxs17(Text17, { color: "#6B7280", children: [
2411
+ !stream.isRunning && /* @__PURE__ */ jsxs18(Fragment4, { children: [
2412
+ /* @__PURE__ */ jsx19(Text19, { color: stream.error ? COLORS.error : COLORS.success, bold: true, children: stream.error ? `\u2718 ${stream.error}` : `\u2714 ${t("pkgInfo_done")}` }),
2413
+ /* @__PURE__ */ jsxs18(Text19, { color: COLORS.textSecondary, children: [
1714
2414
  "esc:",
1715
2415
  t("hint_back")
1716
2416
  ] })
@@ -1719,8 +2419,8 @@ function PackageInfoView() {
1719
2419
  }
1720
2420
  const installed = formula.installed[0];
1721
2421
  const isInstalled = formula.installed.length > 0;
1722
- return /* @__PURE__ */ jsxs17(Box15, { flexDirection: "column", children: [
1723
- confirmAction && /* @__PURE__ */ jsx17(
2422
+ return /* @__PURE__ */ jsxs18(Box17, { flexDirection: "column", children: [
2423
+ confirmAction && /* @__PURE__ */ jsx19(
1724
2424
  ConfirmDialog,
1725
2425
  {
1726
2426
  message: t(ACTION_CONFIRM_KEYS[confirmAction], { name: formula.name }),
@@ -1736,72 +2436,72 @@ function PackageInfoView() {
1736
2436
  onCancel: () => setConfirmAction(null)
1737
2437
  }
1738
2438
  ),
1739
- /* @__PURE__ */ jsxs17(Box15, { gap: 2, marginBottom: 1, children: [
1740
- /* @__PURE__ */ jsx17(GradientText, { colors: GRADIENTS.gold, bold: true, children: formula.name }),
1741
- /* @__PURE__ */ jsx17(Text17, { color: "#2DD4BF", children: installed?.version ?? formula.versions.stable }),
1742
- isInstalled && /* @__PURE__ */ jsx17(StatusBadge, { label: t("badge_installed"), variant: "success" }),
1743
- formula.outdated && /* @__PURE__ */ jsx17(StatusBadge, { label: t("badge_outdated"), variant: "warning" }),
1744
- formula.pinned && /* @__PURE__ */ jsx17(StatusBadge, { label: t("badge_pinned"), variant: "info" }),
1745
- formula.keg_only && /* @__PURE__ */ jsx17(StatusBadge, { label: t("badge_kegOnly"), variant: "muted" }),
1746
- formula.deprecated && /* @__PURE__ */ jsx17(StatusBadge, { label: t("badge_deprecated"), variant: "error" })
2439
+ /* @__PURE__ */ jsxs18(Box17, { gap: 2, marginBottom: 1, children: [
2440
+ /* @__PURE__ */ jsx19(GradientText, { colors: GRADIENTS.gold, bold: true, children: formula.name }),
2441
+ /* @__PURE__ */ jsx19(Text19, { color: COLORS.teal, children: installed?.version ?? formula.versions.stable }),
2442
+ isInstalled && /* @__PURE__ */ jsx19(StatusBadge, { label: t("badge_installed"), variant: "success" }),
2443
+ formula.outdated && /* @__PURE__ */ jsx19(StatusBadge, { label: t("badge_outdated"), variant: "warning" }),
2444
+ formula.pinned && /* @__PURE__ */ jsx19(StatusBadge, { label: t("badge_pinned"), variant: "info" }),
2445
+ formula.keg_only && /* @__PURE__ */ jsx19(StatusBadge, { label: t("badge_kegOnly"), variant: "muted" }),
2446
+ formula.deprecated && /* @__PURE__ */ jsx19(StatusBadge, { label: t("badge_deprecated"), variant: "error" })
1747
2447
  ] }),
1748
- /* @__PURE__ */ jsxs17(Box15, { flexDirection: "column", gap: 1, children: [
1749
- /* @__PURE__ */ jsx17(Text17, { children: formula.desc }),
1750
- /* @__PURE__ */ jsxs17(Box15, { flexDirection: "column", children: [
1751
- /* @__PURE__ */ jsx17(SectionHeader, { emoji: "\u{1F4CB}", title: t("pkgInfo_details"), gradient: ["#F9FAFB", "#9CA3AF"] }),
1752
- /* @__PURE__ */ jsxs17(Box15, { borderStyle: "round", borderColor: "#4B5563", paddingX: 2, flexDirection: "column", children: [
1753
- /* @__PURE__ */ jsxs17(Text17, { children: [
1754
- /* @__PURE__ */ jsx17(Text17, { color: "#9CA3AF", children: t("pkgInfo_homepage") }),
2448
+ /* @__PURE__ */ jsxs18(Box17, { flexDirection: "column", gap: 1, children: [
2449
+ /* @__PURE__ */ jsx19(Text19, { children: formula.desc }),
2450
+ /* @__PURE__ */ jsxs18(Box17, { flexDirection: "column", children: [
2451
+ /* @__PURE__ */ jsx19(SectionHeader, { emoji: "\u{1F4CB}", title: t("pkgInfo_details"), gradient: [COLORS.text, COLORS.muted] }),
2452
+ /* @__PURE__ */ jsxs18(Box17, { borderStyle: "round", borderColor: COLORS.border, paddingX: 2, flexDirection: "column", children: [
2453
+ /* @__PURE__ */ jsxs18(Text19, { children: [
2454
+ /* @__PURE__ */ jsx19(Text19, { color: COLORS.muted, children: t("pkgInfo_homepage") }),
1755
2455
  " ",
1756
2456
  formula.homepage
1757
2457
  ] }),
1758
- /* @__PURE__ */ jsxs17(Text17, { children: [
1759
- /* @__PURE__ */ jsx17(Text17, { color: "#9CA3AF", children: t("pkgInfo_license") }),
2458
+ /* @__PURE__ */ jsxs18(Text19, { children: [
2459
+ /* @__PURE__ */ jsx19(Text19, { color: COLORS.muted, children: t("pkgInfo_license") }),
1760
2460
  " ",
1761
2461
  formula.license
1762
2462
  ] }),
1763
- /* @__PURE__ */ jsxs17(Text17, { children: [
1764
- /* @__PURE__ */ jsx17(Text17, { color: "#9CA3AF", children: t("pkgInfo_tap") }),
2463
+ /* @__PURE__ */ jsxs18(Text19, { children: [
2464
+ /* @__PURE__ */ jsx19(Text19, { color: COLORS.muted, children: t("pkgInfo_tap") }),
1765
2465
  " ",
1766
2466
  formula.tap
1767
2467
  ] }),
1768
- /* @__PURE__ */ jsxs17(Text17, { children: [
1769
- /* @__PURE__ */ jsx17(Text17, { color: "#9CA3AF", children: t("pkgInfo_stable") }),
2468
+ /* @__PURE__ */ jsxs18(Text19, { children: [
2469
+ /* @__PURE__ */ jsx19(Text19, { color: COLORS.muted, children: t("pkgInfo_stable") }),
1770
2470
  " ",
1771
2471
  formula.versions.stable
1772
2472
  ] }),
1773
- installed && /* @__PURE__ */ jsxs17(Fragment4, { children: [
1774
- /* @__PURE__ */ jsxs17(Text17, { children: [
1775
- /* @__PURE__ */ jsx17(Text17, { color: "#9CA3AF", children: t("pkgInfo_installed") }),
2473
+ installed && /* @__PURE__ */ jsxs18(Fragment4, { children: [
2474
+ /* @__PURE__ */ jsxs18(Text19, { children: [
2475
+ /* @__PURE__ */ jsx19(Text19, { color: COLORS.muted, children: t("pkgInfo_installed") }),
1776
2476
  " ",
1777
2477
  installed.version,
1778
2478
  " (",
1779
2479
  formatRelativeTime(installed.time),
1780
2480
  ")"
1781
2481
  ] }),
1782
- /* @__PURE__ */ jsxs17(Text17, { children: [
1783
- /* @__PURE__ */ jsx17(Text17, { color: "#9CA3AF", children: t("pkgInfo_bottle") }),
2482
+ /* @__PURE__ */ jsxs18(Text19, { children: [
2483
+ /* @__PURE__ */ jsx19(Text19, { color: COLORS.muted, children: t("pkgInfo_bottle") }),
1784
2484
  " ",
1785
2485
  installed.poured_from_bottle ? t("common_yes") : t("common_no")
1786
2486
  ] }),
1787
- /* @__PURE__ */ jsxs17(Text17, { children: [
1788
- /* @__PURE__ */ jsx17(Text17, { color: "#9CA3AF", children: t("pkgInfo_onRequest") }),
2487
+ /* @__PURE__ */ jsxs18(Text19, { children: [
2488
+ /* @__PURE__ */ jsx19(Text19, { color: COLORS.muted, children: t("pkgInfo_onRequest") }),
1789
2489
  " ",
1790
2490
  installed.installed_on_request ? t("common_yes") : t("pkgInfo_noDependency")
1791
2491
  ] })
1792
2492
  ] })
1793
2493
  ] })
1794
2494
  ] }),
1795
- formula.dependencies.length > 0 && /* @__PURE__ */ jsxs17(Box15, { flexDirection: "column", children: [
1796
- /* @__PURE__ */ jsx17(SectionHeader, { emoji: "\u{1F517}", title: t("pkgInfo_dependencies", { count: formula.dependencies.length }), gradient: GRADIENTS.ocean }),
1797
- /* @__PURE__ */ jsx17(Box15, { paddingLeft: 2, flexWrap: "wrap", columnGap: 2, children: formula.dependencies.map((dep) => /* @__PURE__ */ jsx17(Text17, { color: "#9CA3AF", children: dep }, dep)) })
2495
+ formula.dependencies.length > 0 && /* @__PURE__ */ jsxs18(Box17, { flexDirection: "column", children: [
2496
+ /* @__PURE__ */ jsx19(SectionHeader, { emoji: "\u{1F517}", title: t("pkgInfo_dependencies", { count: formula.dependencies.length }), gradient: GRADIENTS.ocean }),
2497
+ /* @__PURE__ */ jsx19(Box17, { paddingLeft: 2, flexWrap: "wrap", columnGap: 2, children: formula.dependencies.map((dep) => /* @__PURE__ */ jsx19(Text19, { color: COLORS.muted, children: dep }, dep)) })
1798
2498
  ] }),
1799
- formula.caveats && /* @__PURE__ */ jsxs17(Box15, { flexDirection: "column", children: [
1800
- /* @__PURE__ */ jsx17(SectionHeader, { emoji: "\u26A0\uFE0F", title: t("pkgInfo_caveats"), color: "#F59E0B" }),
1801
- /* @__PURE__ */ jsx17(Box15, { borderStyle: "round", borderColor: "#F59E0B", paddingX: 2, children: /* @__PURE__ */ jsx17(Text17, { color: "#F59E0B", children: formula.caveats }) })
2499
+ formula.caveats && /* @__PURE__ */ jsxs18(Box17, { flexDirection: "column", children: [
2500
+ /* @__PURE__ */ jsx19(SectionHeader, { emoji: "\u26A0\uFE0F", title: t("pkgInfo_caveats"), color: COLORS.warning }),
2501
+ /* @__PURE__ */ jsx19(Box17, { borderStyle: "round", borderColor: COLORS.warning, paddingX: 2, children: /* @__PURE__ */ jsx19(Text19, { color: COLORS.warning, children: formula.caveats }) })
1802
2502
  ] })
1803
2503
  ] }),
1804
- /* @__PURE__ */ jsx17(Box15, { marginTop: 1, children: /* @__PURE__ */ jsxs17(Text17, { color: "#6B7280", children: [
2504
+ /* @__PURE__ */ jsx19(Box17, { marginTop: 1, children: /* @__PURE__ */ jsxs18(Text19, { color: COLORS.textSecondary, children: [
1805
2505
  isInstalled ? `u:${t("hint_uninstall")}` : `i:${t("hint_install")}`,
1806
2506
  isInstalled && formula.outdated ? ` U:${t("hint_upgrade")}` : "",
1807
2507
  ` esc:${t("hint_back")}`
@@ -1811,8 +2511,8 @@ function PackageInfoView() {
1811
2511
 
1812
2512
  // src/views/services.tsx
1813
2513
  import { useEffect as useEffect9, useState as useState7 } from "react";
1814
- import { Box as Box16, Text as Text18, useInput as useInput7 } from "ink";
1815
- import { jsx as jsx18, jsxs as jsxs18 } from "react/jsx-runtime";
2514
+ import { Box as Box18, Text as Text20, useInput as useInput7, useStdout as useStdout4 } from "ink";
2515
+ import { jsx as jsx20, jsxs as jsxs19 } from "react/jsx-runtime";
1816
2516
  var STATUS_VARIANTS = {
1817
2517
  started: "success",
1818
2518
  stopped: "muted",
@@ -1824,12 +2524,21 @@ function ServicesView() {
1824
2524
  const [cursor, setCursor] = useState7(0);
1825
2525
  const [actionInProgress, setActionInProgress] = useState7(false);
1826
2526
  const [confirmAction, setConfirmAction] = useState7(null);
2527
+ const [lastError, setLastError] = useState7(null);
2528
+ const { stdout } = useStdout4();
2529
+ const cols = stdout?.columns ?? 80;
2530
+ const svcNameWidth = Math.floor(cols * 0.35);
2531
+ const svcStatusWidth = Math.floor(cols * 0.15);
2532
+ const MAX_VISIBLE_ROWS = Math.max(5, (stdout?.rows ?? 24) - 10);
1827
2533
  useEffect9(() => {
1828
2534
  fetchServices();
1829
2535
  }, []);
1830
2536
  useInput7((input, key) => {
1831
2537
  if (actionInProgress) return;
1832
2538
  if (confirmAction) return;
2539
+ if (lastError) {
2540
+ setLastError(null);
2541
+ }
1833
2542
  if (input === "j" || key.downArrow) {
1834
2543
  setCursor((c) => Math.min(c + 1, Math.max(0, services.length - 1)));
1835
2544
  } else if (input === "k" || key.upArrow) {
@@ -1841,25 +2550,31 @@ function ServicesView() {
1841
2550
  if (!svc) return;
1842
2551
  const doAction = (action) => {
1843
2552
  setActionInProgress(true);
1844
- void serviceAction2(svc.name, action).finally(() => {
2553
+ void serviceAction2(svc.name, action).catch((err) => {
2554
+ setLastError(err instanceof Error ? err.message : String(err));
2555
+ }).finally(() => {
1845
2556
  setActionInProgress(false);
2557
+ const storeError = useBrewStore.getState().errors["service-action"];
2558
+ if (storeError) setLastError(storeError);
1846
2559
  });
1847
2560
  };
1848
2561
  if (input === "s") doAction("start");
1849
2562
  else if (input === "S") setConfirmAction({ type: "stop", name: svc.name });
1850
2563
  else if (input === "R") setConfirmAction({ type: "restart", name: svc.name });
1851
2564
  });
1852
- if (loading.services) return /* @__PURE__ */ jsx18(Loading, { message: t("loading_services") });
1853
- if (errors.services) return /* @__PURE__ */ jsx18(ErrorMessage, { message: errors.services });
2565
+ if (loading.services) return /* @__PURE__ */ jsx20(Loading, { message: t("loading_services") });
2566
+ if (errors.services) return /* @__PURE__ */ jsx20(ErrorMessage, { message: errors.services });
1854
2567
  if (services.length === 0) {
1855
- return /* @__PURE__ */ jsxs18(Box16, { flexDirection: "column", children: [
1856
- /* @__PURE__ */ jsx18(SectionHeader, { emoji: "\u2699\uFE0F", title: t("services_title"), gradient: GRADIENTS.ocean }),
1857
- /* @__PURE__ */ jsx18(Text18, { color: "#6B7280", italic: true, children: t("services_noServices") })
2568
+ return /* @__PURE__ */ jsxs19(Box18, { flexDirection: "column", children: [
2569
+ /* @__PURE__ */ jsx20(SectionHeader, { emoji: "\u2699\uFE0F", title: t("services_title"), gradient: GRADIENTS.ocean }),
2570
+ /* @__PURE__ */ jsx20(Text20, { color: COLORS.textSecondary, italic: true, children: t("services_noServices") })
1858
2571
  ] });
1859
2572
  }
1860
- return /* @__PURE__ */ jsxs18(Box16, { flexDirection: "column", children: [
1861
- /* @__PURE__ */ jsx18(SectionHeader, { emoji: "\u2699\uFE0F", title: t("services_titleCount", { count: services.length }), gradient: GRADIENTS.ocean }),
1862
- confirmAction && /* @__PURE__ */ jsx18(Box16, { marginY: 1, children: /* @__PURE__ */ jsx18(
2573
+ const start = Math.max(0, cursor - Math.floor(MAX_VISIBLE_ROWS / 2));
2574
+ const visible = services.slice(start, start + MAX_VISIBLE_ROWS);
2575
+ return /* @__PURE__ */ jsxs19(Box18, { flexDirection: "column", children: [
2576
+ /* @__PURE__ */ jsx20(SectionHeader, { emoji: "\u2699\uFE0F", title: t("services_titleCount", { count: services.length }), gradient: GRADIENTS.ocean }),
2577
+ confirmAction && /* @__PURE__ */ jsx20(Box18, { marginY: 1, children: /* @__PURE__ */ jsx20(
1863
2578
  ConfirmDialog,
1864
2579
  {
1865
2580
  message: confirmAction.type === "stop" ? t("services_confirmStop", { name: confirmAction.name }) : t("services_confirmRestart", { name: confirmAction.name }),
@@ -1867,35 +2582,48 @@ function ServicesView() {
1867
2582
  const { type, name } = confirmAction;
1868
2583
  setConfirmAction(null);
1869
2584
  setActionInProgress(true);
1870
- void serviceAction2(name, type).finally(() => {
2585
+ void serviceAction2(name, type).catch((err) => {
2586
+ setLastError(err instanceof Error ? err.message : String(err));
2587
+ }).finally(() => {
1871
2588
  setActionInProgress(false);
2589
+ const storeError = useBrewStore.getState().errors["service-action"];
2590
+ if (storeError) setLastError(storeError);
1872
2591
  });
1873
2592
  },
1874
2593
  onCancel: () => setConfirmAction(null)
1875
2594
  }
1876
2595
  ) }),
1877
- /* @__PURE__ */ jsxs18(Box16, { flexDirection: "column", marginTop: 1, children: [
1878
- /* @__PURE__ */ jsxs18(Box16, { gap: 1, borderStyle: "single", borderBottom: true, borderTop: false, borderLeft: false, borderRight: false, borderColor: "#4B5563", paddingBottom: 0, children: [
1879
- /* @__PURE__ */ jsxs18(Text18, { bold: true, color: "#F9FAFB", children: [
2596
+ /* @__PURE__ */ jsxs19(Box18, { flexDirection: "column", marginTop: 1, children: [
2597
+ /* @__PURE__ */ jsxs19(Box18, { gap: 1, borderStyle: "single", borderBottom: true, borderTop: false, borderLeft: false, borderRight: false, borderColor: COLORS.border, paddingBottom: 0, children: [
2598
+ /* @__PURE__ */ jsxs19(Text20, { bold: true, color: COLORS.text, children: [
1880
2599
  " ",
1881
- t("services_name").padEnd(22)
2600
+ t("services_name").padEnd(svcNameWidth)
1882
2601
  ] }),
1883
- /* @__PURE__ */ jsx18(Text18, { bold: true, color: "#F9FAFB", children: t("services_status").padEnd(12) }),
1884
- /* @__PURE__ */ jsx18(Text18, { bold: true, color: "#F9FAFB", children: t("services_user") })
2602
+ /* @__PURE__ */ jsx20(Text20, { bold: true, color: COLORS.text, children: t("services_status").padEnd(svcStatusWidth) }),
2603
+ /* @__PURE__ */ jsx20(Text20, { bold: true, color: COLORS.text, children: t("services_user") })
1885
2604
  ] }),
1886
- services.map((svc, i) => {
1887
- const isCurrent = i === cursor;
1888
- return /* @__PURE__ */ jsxs18(Box16, { gap: 1, children: [
1889
- /* @__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) }),
1891
- /* @__PURE__ */ jsx18(StatusBadge, { label: svc.status, variant: STATUS_VARIANTS[svc.status] }),
1892
- /* @__PURE__ */ jsx18(Text18, { color: "#9CA3AF", children: svc.user ?? "-" }),
1893
- svc.exit_code != null && svc.exit_code !== 0 && /* @__PURE__ */ jsx18(Text18, { color: "#EF4444", children: t("common_exit", { code: svc.exit_code }) })
2605
+ start > 0 && /* @__PURE__ */ jsxs19(Text20, { color: COLORS.textSecondary, dimColor: true, children: [
2606
+ " ",
2607
+ t("scroll_moreAbove", { count: start })
2608
+ ] }),
2609
+ visible.map((svc, i) => {
2610
+ const idx = start + i;
2611
+ const isCurrent = idx === cursor;
2612
+ return /* @__PURE__ */ jsxs19(SelectableRow, { isCurrent, children: [
2613
+ /* @__PURE__ */ jsx20(Text20, { bold: isCurrent, inverse: isCurrent, color: isCurrent ? COLORS.text : COLORS.muted, children: svc.name.padEnd(svcNameWidth - 2) }),
2614
+ /* @__PURE__ */ jsx20(StatusBadge, { label: svc.status, variant: STATUS_VARIANTS[svc.status] }),
2615
+ /* @__PURE__ */ jsx20(Text20, { color: COLORS.muted, children: svc.user ?? "-" }),
2616
+ svc.exit_code != null && svc.exit_code !== 0 && /* @__PURE__ */ jsx20(Text20, { color: COLORS.error, children: t("common_exit", { code: svc.exit_code }) })
1894
2617
  ] }, svc.name);
1895
- })
2618
+ }),
2619
+ start + MAX_VISIBLE_ROWS < services.length && /* @__PURE__ */ jsxs19(Text20, { color: COLORS.textSecondary, dimColor: true, children: [
2620
+ " ",
2621
+ t("scroll_moreBelow", { count: services.length - start - MAX_VISIBLE_ROWS })
2622
+ ] })
1896
2623
  ] }),
1897
- actionInProgress && /* @__PURE__ */ jsx18(Text18, { color: "#38BDF8", children: t("services_processing") }),
1898
- /* @__PURE__ */ jsx18(Box16, { marginTop: 1, children: /* @__PURE__ */ jsxs18(Text18, { color: "#F9FAFB", bold: true, children: [
2624
+ actionInProgress && /* @__PURE__ */ jsx20(Text20, { color: COLORS.sky, children: t("services_processing") }),
2625
+ lastError && /* @__PURE__ */ jsx20(Box18, { marginTop: 1, children: /* @__PURE__ */ jsx20(Text20, { color: COLORS.error, children: lastError }) }),
2626
+ /* @__PURE__ */ jsx20(Box18, { marginTop: 1, children: /* @__PURE__ */ jsxs19(Text20, { color: COLORS.text, bold: true, children: [
1899
2627
  cursor + 1,
1900
2628
  "/",
1901
2629
  services.length
@@ -1904,54 +2632,62 @@ function ServicesView() {
1904
2632
  }
1905
2633
 
1906
2634
  // src/views/doctor.tsx
1907
- import { useEffect as useEffect10 } from "react";
1908
- import { Box as Box17, Text as Text19, useInput as useInput8 } from "ink";
1909
- import { jsx as jsx19, jsxs as jsxs19 } from "react/jsx-runtime";
2635
+ import { useEffect as useEffect10, useRef as useRef5 } from "react";
2636
+ import { Box as Box19, Text as Text21, useInput as useInput8 } from "ink";
2637
+ import { jsx as jsx21, jsxs as jsxs20 } from "react/jsx-runtime";
1910
2638
  function DoctorView() {
1911
2639
  const { doctorWarnings, doctorClean, loading, errors, fetchDoctor } = useBrewStore();
2640
+ const mountedRef = useRef5(true);
2641
+ useEffect10(() => {
2642
+ mountedRef.current = true;
2643
+ return () => {
2644
+ mountedRef.current = false;
2645
+ };
2646
+ }, []);
1912
2647
  useEffect10(() => {
1913
2648
  fetchDoctor();
1914
2649
  }, []);
1915
2650
  useInput8((input) => {
1916
2651
  if (input === "r") void fetchDoctor();
1917
2652
  });
1918
- if (loading.doctor) return /* @__PURE__ */ jsx19(Loading, { message: t("loading_doctor") });
1919
- if (errors.doctor) return /* @__PURE__ */ jsx19(ErrorMessage, { message: errors.doctor });
1920
- return /* @__PURE__ */ jsxs19(Box17, { flexDirection: "column", children: [
1921
- /* @__PURE__ */ jsx19(SectionHeader, { emoji: "\u{1FA7A}", title: t("doctor_title"), gradient: GRADIENTS.emerald }),
1922
- /* @__PURE__ */ jsxs19(Box17, { flexDirection: "column", marginTop: 1, children: [
1923
- doctorClean && /* @__PURE__ */ jsx19(Box17, { borderStyle: "round", borderColor: "#22C55E", paddingX: 2, paddingY: 0, children: /* @__PURE__ */ jsxs19(Text19, { color: "#22C55E", bold: true, children: [
1924
- "\u2714",
1925
- " ",
1926
- t("doctor_clean")
1927
- ] }) }),
1928
- 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))
2653
+ if (loading.doctor) return /* @__PURE__ */ jsx21(Loading, { message: t("loading_doctor") });
2654
+ if (errors.doctor) return /* @__PURE__ */ jsx21(ErrorMessage, { message: errors.doctor });
2655
+ return /* @__PURE__ */ jsxs20(Box19, { flexDirection: "column", children: [
2656
+ /* @__PURE__ */ jsx21(SectionHeader, { emoji: "\u{1FA7A}", title: t("doctor_title"), gradient: GRADIENTS.emerald }),
2657
+ /* @__PURE__ */ jsxs20(Box19, { flexDirection: "column", marginTop: 1, children: [
2658
+ doctorClean && /* @__PURE__ */ jsx21(ResultBanner, { status: "success", message: `\u2714 ${t("doctor_clean")}` }),
2659
+ doctorClean === false && doctorWarnings.length === 0 && /* @__PURE__ */ jsx21(Text21, { color: COLORS.warning, children: t("doctor_warningsNotCaptured") }),
2660
+ doctorWarnings.map((warning, i) => (
2661
+ // FE-004: Improved React key
2662
+ /* @__PURE__ */ jsx21(Box19, { flexDirection: "column", marginBottom: 1, borderStyle: "single", borderColor: COLORS.warning, paddingX: 1, children: warning.split("\n").map((line, j) => /* @__PURE__ */ jsx21(Text21, { color: j === 0 ? COLORS.warning : COLORS.muted, children: line }, `warning-${i}-${j}-${line.slice(0, 20)}`)) }, `warning-${i}-${warning.slice(0, 20)}`)
2663
+ ))
1930
2664
  ] }),
1931
- /* @__PURE__ */ jsx19(Box17, { marginTop: 1, children: /* @__PURE__ */ jsx19(Text19, { color: "#F9FAFB", bold: true, children: doctorWarnings.length > 0 ? tp("plural_warnings", doctorWarnings.length) : "" }) })
2665
+ /* @__PURE__ */ jsx21(Box19, { marginTop: 1, children: /* @__PURE__ */ jsx21(Text21, { color: COLORS.text, bold: true, children: doctorWarnings.length > 0 ? tp("plural_warnings", doctorWarnings.length) : "" }) })
1932
2666
  ] });
1933
2667
  }
1934
2668
 
1935
2669
  // src/views/profiles.tsx
1936
- import { useEffect as useEffect11, useRef as useRef5, useState as useState8 } from "react";
1937
- import { Box as Box18, Text as Text20, useInput as useInput9 } from "ink";
1938
- import { TextInput as TextInput3 } from "@inkjs/ui";
2670
+ import { useEffect as useEffect11, useRef as useRef6, useState as useState8 } from "react";
2671
+ import { Box as Box24, useInput as useInput9 } from "ink";
1939
2672
 
1940
2673
  // src/stores/profile-store.ts
1941
- import { create as create4 } from "zustand";
2674
+ import { create as create5 } from "zustand";
1942
2675
 
1943
2676
  // src/lib/profiles/profile-manager.ts
1944
- import { readFile, writeFile, readdir, rm } from "fs/promises";
1945
- import { join, basename } from "path";
2677
+ import { readFile as readFile3, writeFile as writeFile3, readdir, rm as rm2, rename as rename2 } from "fs/promises";
2678
+ import { join as join2, basename } from "path";
1946
2679
 
1947
2680
  // src/lib/license/watermark.ts
1948
- function getWatermark() {
1949
- const license = useLicenseStore.getState().license;
2681
+ function getWatermark(license, consent = true) {
2682
+ if (!consent) return "";
1950
2683
  if (!license?.customerEmail) return "";
1951
2684
  return `Licensed to: ${license.customerEmail}`;
1952
2685
  }
1953
2686
 
1954
2687
  // src/lib/profiles/profile-manager.ts
2688
+ function proCheck(isPro) {
2689
+ if (!isPro) throw new Error("Pro license required");
2690
+ }
1955
2691
  var MAX_PROFILE_NAME_LENGTH = 100;
1956
2692
  function validateProfileName(name) {
1957
2693
  if (!name || name.trim().length === 0) {
@@ -1966,10 +2702,10 @@ function validateProfileName(name) {
1966
2702
  }
1967
2703
  function profilePath(name) {
1968
2704
  validateProfileName(name);
1969
- return join(PROFILES_DIR, `${basename(name)}.json`);
2705
+ return join2(PROFILES_DIR, `${basename(name)}.json`);
1970
2706
  }
1971
- async function listProfiles() {
1972
- requirePro();
2707
+ async function listProfiles(isPro) {
2708
+ proCheck(isPro);
1973
2709
  await ensureDataDirs();
1974
2710
  try {
1975
2711
  const files = await readdir(PROFILES_DIR);
@@ -1978,35 +2714,45 @@ async function listProfiles() {
1978
2714
  return [];
1979
2715
  }
1980
2716
  }
1981
- async function loadProfile(name) {
1982
- requirePro();
1983
- const raw = await readFile(profilePath(name), "utf-8");
2717
+ async function loadProfile(isPro, name) {
2718
+ proCheck(isPro);
2719
+ const raw = await readFile3(profilePath(name), "utf-8");
1984
2720
  let file;
1985
2721
  try {
1986
2722
  file = JSON.parse(raw);
1987
2723
  } catch (err) {
1988
2724
  throw new Error(`Profile "${name}" is corrupted: ${err instanceof Error ? err.message : String(err)}`);
1989
2725
  }
2726
+ if (file.version !== 1) {
2727
+ throw new Error("Unsupported data version");
2728
+ }
1990
2729
  if (!file.profile) {
1991
2730
  throw new Error(`Profile "${name}" is missing required data`);
1992
2731
  }
1993
2732
  return file.profile;
1994
2733
  }
1995
- async function saveProfile(profile) {
1996
- requirePro();
2734
+ async function saveProfile(isPro, profile) {
2735
+ proCheck(isPro);
1997
2736
  await ensureDataDirs();
2737
+ const filePath = profilePath(profile.name);
2738
+ const tmpPath = filePath + ".tmp";
1998
2739
  const file = { version: 1, profile };
1999
- await writeFile(profilePath(profile.name), JSON.stringify(file, null, 2), "utf-8");
2740
+ await writeFile3(tmpPath, JSON.stringify(file, null, 2), { encoding: "utf-8", mode: 384 });
2741
+ await rename2(tmpPath, filePath);
2000
2742
  }
2001
- async function deleteProfile(name) {
2002
- requirePro();
2743
+ async function deleteProfile(isPro, name) {
2744
+ proCheck(isPro);
2003
2745
  try {
2004
- await rm(profilePath(name));
2746
+ await rm2(profilePath(name));
2005
2747
  } catch {
2006
2748
  }
2007
2749
  }
2008
- async function exportCurrentSetup(name, description) {
2009
- requirePro();
2750
+ async function exportCurrentSetup(isPro, name, description, license = null, consent = true) {
2751
+ proCheck(isPro);
2752
+ const existingNames = await listProfiles(isPro);
2753
+ if (existingNames.includes(name)) {
2754
+ throw new Error("Profile already exists: " + name);
2755
+ }
2010
2756
  const [installed, leaves, tapsRaw] = await Promise.all([
2011
2757
  getInstalled(),
2012
2758
  getLeaves(),
@@ -2023,17 +2769,17 @@ async function exportCurrentSetup(name, description) {
2023
2769
  formulae: leaves,
2024
2770
  casks,
2025
2771
  taps,
2026
- exportedBy: getWatermark()
2027
- // Layer 16: Watermark who exported this profile
2772
+ exportedBy: consent ? getWatermark(license) : ""
2773
+ // SEG-003: Only embed watermark with consent
2028
2774
  };
2029
- await saveProfile(profile);
2775
+ await saveProfile(isPro, profile);
2030
2776
  return profile;
2031
2777
  }
2032
- async function updateProfile(oldName, newName, newDescription) {
2033
- requirePro();
2034
- const profile = await loadProfile(oldName);
2778
+ async function updateProfile(isPro, oldName, newName, newDescription) {
2779
+ proCheck(isPro);
2780
+ const profile = await loadProfile(isPro, oldName);
2035
2781
  if (oldName !== newName) {
2036
- await deleteProfile(oldName);
2782
+ await deleteProfile(isPro, oldName);
2037
2783
  }
2038
2784
  const updated = {
2039
2785
  ...profile,
@@ -2041,14 +2787,20 @@ async function updateProfile(oldName, newName, newDescription) {
2041
2787
  description: newDescription,
2042
2788
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2043
2789
  };
2044
- await saveProfile(updated);
2790
+ await saveProfile(isPro, updated);
2045
2791
  }
2046
- async function* importProfile(profile) {
2047
- requirePro();
2792
+ var TAP_PATTERN = /^[a-z0-9][-a-z0-9]*\/[a-z0-9][-a-z0-9]*$/;
2793
+ var PKG_PATTERN2 = /^[a-z0-9][-a-z0-9_.@+]*$/;
2794
+ async function* importProfile(isPro, profile) {
2795
+ proCheck(isPro);
2048
2796
  const installed = await getInstalled();
2049
2797
  const installedFormulae = new Set(installed.formulae.map((f) => f.name));
2050
2798
  const installedCasks = new Set(installed.casks.filter((c) => c.installed).map((c) => c.token));
2051
2799
  for (const tap of profile.taps) {
2800
+ if (!TAP_PATTERN.test(tap)) {
2801
+ yield `Skipping invalid tap name: ${tap}`;
2802
+ continue;
2803
+ }
2052
2804
  yield t("profileMgr_tapping", { name: tap });
2053
2805
  try {
2054
2806
  await execBrew(["tap", tap]);
@@ -2057,6 +2809,10 @@ async function* importProfile(profile) {
2057
2809
  }
2058
2810
  const missingFormulae = profile.formulae.filter((f) => !installedFormulae.has(f));
2059
2811
  for (const name of missingFormulae) {
2812
+ if (!PKG_PATTERN2.test(name)) {
2813
+ yield `Skipping invalid formula name: ${name}`;
2814
+ continue;
2815
+ }
2060
2816
  yield t("profileMgr_installing", { name });
2061
2817
  for await (const line of streamBrew(["install", name])) {
2062
2818
  yield line;
@@ -2064,6 +2820,10 @@ async function* importProfile(profile) {
2064
2820
  }
2065
2821
  const missingCasks = profile.casks.filter((c) => !installedCasks.has(c));
2066
2822
  for (const name of missingCasks) {
2823
+ if (!PKG_PATTERN2.test(name)) {
2824
+ yield `Skipping invalid cask name: ${name}`;
2825
+ continue;
2826
+ }
2067
2827
  yield t("profileMgr_installingCask", { name });
2068
2828
  for await (const line of streamBrew(["install", "--cask", name])) {
2069
2829
  yield line;
@@ -2074,20 +2834,23 @@ async function* importProfile(profile) {
2074
2834
  }
2075
2835
 
2076
2836
  // src/stores/profile-store.ts
2077
- var useProfileStore = create4((set) => ({
2837
+ function getIsPro() {
2838
+ return useLicenseStore.getState().isPro();
2839
+ }
2840
+ var useProfileStore = create5((set) => ({
2078
2841
  profileNames: [],
2079
2842
  selectedProfile: null,
2080
2843
  loading: false,
2081
2844
  loadError: null,
2082
2845
  fetchProfiles: async () => {
2083
2846
  set({ loading: true });
2084
- const names = await listProfiles();
2847
+ const names = await listProfiles(getIsPro());
2085
2848
  set({ profileNames: names, loading: false });
2086
2849
  },
2087
2850
  loadProfile: async (name) => {
2088
2851
  set({ loadError: null });
2089
2852
  try {
2090
- const profile = await loadProfile(name);
2853
+ const profile = await loadProfile(getIsPro(), name);
2091
2854
  set({ selectedProfile: profile });
2092
2855
  } catch (err) {
2093
2856
  set({ loadError: err instanceof Error ? err.message : String(err) });
@@ -2096,8 +2859,9 @@ var useProfileStore = create4((set) => ({
2096
2859
  exportCurrent: async (name, description) => {
2097
2860
  set({ loading: true, loadError: null });
2098
2861
  try {
2099
- await exportCurrentSetup(name, description);
2100
- const names = await listProfiles();
2862
+ const license = useLicenseStore.getState().license;
2863
+ await exportCurrentSetup(getIsPro(), name, description, license);
2864
+ const names = await listProfiles(getIsPro());
2101
2865
  set({ profileNames: names, loading: false });
2102
2866
  } catch (err) {
2103
2867
  set({ loading: false, loadError: err instanceof Error ? err.message : String(err) });
@@ -2105,16 +2869,16 @@ var useProfileStore = create4((set) => ({
2105
2869
  }
2106
2870
  },
2107
2871
  deleteProfile: async (name) => {
2108
- await deleteProfile(name);
2109
- const names = await listProfiles();
2872
+ await deleteProfile(getIsPro(), name);
2873
+ const names = await listProfiles(getIsPro());
2110
2874
  set({ profileNames: names, selectedProfile: null });
2111
2875
  },
2112
2876
  updateProfile: async (oldName, newName, newDescription) => {
2113
2877
  set({ loadError: null });
2114
2878
  try {
2115
- await updateProfile(oldName, newName, newDescription);
2116
- const names = await listProfiles();
2117
- const updated = await loadProfile(newName);
2879
+ await updateProfile(getIsPro(), oldName, newName, newDescription);
2880
+ const names = await listProfiles(getIsPro());
2881
+ const updated = await loadProfile(getIsPro(), newName);
2118
2882
  set({ profileNames: names, selectedProfile: updated });
2119
2883
  } catch (err) {
2120
2884
  set({ loadError: err instanceof Error ? err.message : String(err) });
@@ -2122,8 +2886,141 @@ var useProfileStore = create4((set) => ({
2122
2886
  }
2123
2887
  }));
2124
2888
 
2889
+ // src/views/profiles/profile-list-mode.tsx
2890
+ import { Box as Box20, Text as Text22 } from "ink";
2891
+ import { jsx as jsx22, jsxs as jsxs21 } from "react/jsx-runtime";
2892
+ function ProfileListMode({ profileNames, cursor, confirmDelete, loadError, onConfirmDelete, onCancelDelete }) {
2893
+ return /* @__PURE__ */ jsxs21(Box20, { flexDirection: "column", children: [
2894
+ /* @__PURE__ */ jsx22(SectionHeader, { emoji: "\u{1F4C1}", title: t("profiles_title", { count: profileNames.length }), gradient: GRADIENTS.gold }),
2895
+ loadError && /* @__PURE__ */ jsx22(Box20, { marginY: 1, children: /* @__PURE__ */ jsx22(Text22, { color: COLORS.error, children: loadError }) }),
2896
+ confirmDelete && profileNames[cursor] && /* @__PURE__ */ jsx22(Box20, { marginY: 1, children: /* @__PURE__ */ jsx22(
2897
+ ConfirmDialog,
2898
+ {
2899
+ message: t("profiles_confirmDelete", { name: profileNames[cursor] }),
2900
+ onConfirm: onConfirmDelete,
2901
+ onCancel: onCancelDelete
2902
+ }
2903
+ ) }),
2904
+ profileNames.length === 0 && !confirmDelete && /* @__PURE__ */ jsx22(Box20, { marginTop: 1, borderStyle: "round", borderColor: COLORS.textSecondary, paddingX: 2, paddingY: 0, children: /* @__PURE__ */ jsxs21(Box20, { flexDirection: "column", children: [
2905
+ /* @__PURE__ */ jsx22(Text22, { color: COLORS.textSecondary, italic: true, children: t("profiles_noProfiles") }),
2906
+ /* @__PURE__ */ jsxs21(Text22, { color: COLORS.muted, children: [
2907
+ t("profiles_press"),
2908
+ " ",
2909
+ /* @__PURE__ */ jsx22(Text22, { color: COLORS.gold, bold: true, children: "n" }),
2910
+ " ",
2911
+ t("profiles_exportHint")
2912
+ ] })
2913
+ ] }) }),
2914
+ profileNames.length > 0 && !confirmDelete && /* @__PURE__ */ jsxs21(Box20, { flexDirection: "column", marginTop: 1, children: [
2915
+ profileNames.map((name, i) => {
2916
+ const isCurrent = i === cursor;
2917
+ return /* @__PURE__ */ jsx22(SelectableRow, { isCurrent, children: /* @__PURE__ */ jsx22(Text22, { bold: isCurrent, inverse: isCurrent, children: name }) }, name);
2918
+ }),
2919
+ /* @__PURE__ */ jsx22(Box20, { marginTop: 1, children: /* @__PURE__ */ jsxs21(Text22, { color: COLORS.text, bold: true, children: [
2920
+ cursor + 1,
2921
+ "/",
2922
+ profileNames.length
2923
+ ] }) })
2924
+ ] })
2925
+ ] });
2926
+ }
2927
+
2928
+ // src/views/profiles/profile-detail-mode.tsx
2929
+ import { Box as Box21, Text as Text23 } from "ink";
2930
+ import { jsx as jsx23, jsxs as jsxs22 } from "react/jsx-runtime";
2931
+ function ProfileDetailMode({ profile }) {
2932
+ return /* @__PURE__ */ jsxs22(Box21, { flexDirection: "column", children: [
2933
+ /* @__PURE__ */ jsx23(Text23, { bold: true, color: COLORS.gold, children: profile.name }),
2934
+ /* @__PURE__ */ jsx23(Text23, { color: COLORS.muted, children: profile.description }),
2935
+ /* @__PURE__ */ jsx23(Text23, { color: COLORS.muted, children: t("profiles_created", { date: formatDate(profile.createdAt) }) }),
2936
+ /* @__PURE__ */ jsxs22(Box21, { marginTop: 1, flexDirection: "column", children: [
2937
+ /* @__PURE__ */ jsx23(Text23, { bold: true, children: t("profiles_formulaeCount", { count: profile.formulae.length }) }),
2938
+ /* @__PURE__ */ jsxs22(Box21, { paddingLeft: 2, flexDirection: "column", children: [
2939
+ profile.formulae.slice(0, 30).map((f) => /* @__PURE__ */ jsx23(Text23, { color: COLORS.muted, children: f }, f)),
2940
+ profile.formulae.length > 30 && /* @__PURE__ */ jsx23(Text23, { color: COLORS.textSecondary, italic: true, children: t("common_andMore", { count: profile.formulae.length - 30 }) })
2941
+ ] }),
2942
+ /* @__PURE__ */ jsx23(Text23, { bold: true, children: t("profiles_casksCount", { count: profile.casks.length }) }),
2943
+ /* @__PURE__ */ jsx23(Box21, { paddingLeft: 2, flexDirection: "column", children: profile.casks.map((c) => /* @__PURE__ */ jsx23(Text23, { color: COLORS.muted, children: c }, c)) })
2944
+ ] }),
2945
+ /* @__PURE__ */ jsx23(Box21, { marginTop: 1, children: /* @__PURE__ */ jsxs22(Text23, { color: COLORS.textSecondary, children: [
2946
+ "esc:",
2947
+ t("hint_back"),
2948
+ " e:",
2949
+ t("hint_edit"),
2950
+ " i:",
2951
+ t("hint_importProfile")
2952
+ ] }) })
2953
+ ] });
2954
+ }
2955
+
2956
+ // src/views/profiles/profile-create-flow.tsx
2957
+ import { Box as Box22, Text as Text24 } from "ink";
2958
+ import { TextInput as TextInput3 } from "@inkjs/ui";
2959
+ import { jsx as jsx24, jsxs as jsxs23 } from "react/jsx-runtime";
2960
+ function ProfileCreateName({ onSubmit }) {
2961
+ return /* @__PURE__ */ jsxs23(Box22, { flexDirection: "column", children: [
2962
+ /* @__PURE__ */ jsx24(Text24, { bold: true, children: t("profiles_createName") }),
2963
+ /* @__PURE__ */ jsx24(
2964
+ TextInput3,
2965
+ {
2966
+ placeholder: t("profiles_namePlaceholder"),
2967
+ onSubmit
2968
+ }
2969
+ )
2970
+ ] });
2971
+ }
2972
+ function ProfileCreateDesc({ name, loadError, onSubmit }) {
2973
+ return /* @__PURE__ */ jsxs23(Box22, { flexDirection: "column", children: [
2974
+ /* @__PURE__ */ jsx24(Text24, { bold: true, children: t("profiles_createDesc", { name }) }),
2975
+ loadError && /* @__PURE__ */ jsxs23(Text24, { color: COLORS.error, children: [
2976
+ t("error_prefix"),
2977
+ loadError
2978
+ ] }),
2979
+ /* @__PURE__ */ jsx24(
2980
+ TextInput3,
2981
+ {
2982
+ placeholder: t("profiles_descPlaceholder"),
2983
+ onSubmit
2984
+ }
2985
+ )
2986
+ ] });
2987
+ }
2988
+
2989
+ // src/views/profiles/profile-edit-flow.tsx
2990
+ import { Box as Box23, Text as Text25 } from "ink";
2991
+ import { TextInput as TextInput4 } from "@inkjs/ui";
2992
+ import { jsx as jsx25, jsxs as jsxs24 } from "react/jsx-runtime";
2993
+ function ProfileEditName({ defaultName, onSubmit }) {
2994
+ return /* @__PURE__ */ jsxs24(Box23, { flexDirection: "column", children: [
2995
+ /* @__PURE__ */ jsx25(Text25, { bold: true, children: t("profiles_editName") }),
2996
+ /* @__PURE__ */ jsx25(
2997
+ TextInput4,
2998
+ {
2999
+ defaultValue: defaultName,
3000
+ onSubmit
3001
+ }
3002
+ )
3003
+ ] });
3004
+ }
3005
+ function ProfileEditDesc({ name, defaultDesc, loadError, onSubmit }) {
3006
+ return /* @__PURE__ */ jsxs24(Box23, { flexDirection: "column", children: [
3007
+ /* @__PURE__ */ jsx25(Text25, { bold: true, children: t("profiles_editDesc", { name }) }),
3008
+ loadError && /* @__PURE__ */ jsxs24(Text25, { color: COLORS.error, children: [
3009
+ t("error_prefix"),
3010
+ loadError
3011
+ ] }),
3012
+ /* @__PURE__ */ jsx25(
3013
+ TextInput4,
3014
+ {
3015
+ defaultValue: defaultDesc,
3016
+ onSubmit
3017
+ }
3018
+ )
3019
+ ] });
3020
+ }
3021
+
2125
3022
  // src/views/profiles.tsx
2126
- import { jsx as jsx20, jsxs as jsxs20 } from "react/jsx-runtime";
3023
+ import { jsx as jsx26, jsxs as jsxs25 } from "react/jsx-runtime";
2127
3024
  function ProfilesView() {
2128
3025
  const { profileNames, selectedProfile, loading, loadError, fetchProfiles, loadProfile: loadProfile2, exportCurrent, deleteProfile: deleteProfile2, updateProfile: updateProfile2 } = useProfileStore();
2129
3026
  const [cursor, setCursor] = useState8(0);
@@ -2134,9 +3031,11 @@ function ProfilesView() {
2134
3031
  const [editDesc, setEditDesc] = useState8("");
2135
3032
  const [importLines, setImportLines] = useState8([]);
2136
3033
  const [importRunning, setImportRunning] = useState8(false);
3034
+ const [importHadError, setImportHadError] = useState8(false);
3035
+ const [importProfile2, setImportProfile] = useState8(null);
2137
3036
  const { openModal, closeModal } = useModalStore();
2138
- const importGenRef = useRef5(null);
2139
- const mountedRef = useRef5(true);
3037
+ const importGenRef = useRef6(null);
3038
+ const mountedRef = useRef6(true);
2140
3039
  useEffect11(() => {
2141
3040
  fetchProfiles();
2142
3041
  }, []);
@@ -2149,7 +3048,7 @@ function ProfilesView() {
2149
3048
  };
2150
3049
  }, []);
2151
3050
  useEffect11(() => {
2152
- if (mode === "detail" || mode === "create-name" || mode === "create-desc" || mode === "importing" || mode === "edit-name" || mode === "edit-desc") {
3051
+ if (mode !== "list") {
2153
3052
  openModal();
2154
3053
  return () => {
2155
3054
  closeModal();
@@ -2173,7 +3072,7 @@ function ProfilesView() {
2173
3072
  return;
2174
3073
  }
2175
3074
  if (input === "i" && profileNames[cursor]) {
2176
- void startImport(profileNames[cursor]);
3075
+ void prepareImport(profileNames[cursor]);
2177
3076
  return;
2178
3077
  }
2179
3078
  if (input === "j" || key.downArrow) setCursor((c) => Math.min(c + 1, Math.max(0, profileNames.length - 1)));
@@ -2190,13 +3089,29 @@ function ProfilesView() {
2190
3089
  setMode("edit-name");
2191
3090
  }
2192
3091
  }, { isActive: mode === "detail" });
2193
- const startImport = async (name) => {
3092
+ useInput9(() => {
3093
+ setMode("list");
3094
+ }, { isActive: mode === "importing" && !importRunning });
3095
+ const prepareImport = async (name) => {
3096
+ try {
3097
+ const isPro = useLicenseStore.getState().isPro();
3098
+ const profile = await loadProfile(isPro, name);
3099
+ setImportProfile(profile);
3100
+ setMode("confirm-import");
3101
+ } catch (err) {
3102
+ setImportLines([`${t("error_prefix")}${err instanceof Error ? err.message : err}`]);
3103
+ setMode("importing");
3104
+ setImportRunning(false);
3105
+ }
3106
+ };
3107
+ const startImport = async (profile) => {
2194
3108
  setMode("importing");
2195
3109
  setImportLines([]);
2196
3110
  setImportRunning(true);
3111
+ setImportHadError(false);
2197
3112
  try {
2198
- const profile = await loadProfile(name);
2199
- const gen = importProfile(profile);
3113
+ const isPro = useLicenseStore.getState().isPro();
3114
+ const gen = importProfile(isPro, profile);
2200
3115
  importGenRef.current = gen;
2201
3116
  for await (const line of gen) {
2202
3117
  if (!mountedRef.current) break;
@@ -2204,6 +3119,7 @@ function ProfilesView() {
2204
3119
  }
2205
3120
  } catch (err) {
2206
3121
  if (mountedRef.current) {
3122
+ setImportHadError(true);
2207
3123
  setImportLines((prev) => [...prev, `${t("error_prefix")}${err instanceof Error ? err.message : err}`]);
2208
3124
  }
2209
3125
  } finally {
@@ -2213,163 +3129,105 @@ function ProfilesView() {
2213
3129
  }
2214
3130
  }
2215
3131
  };
2216
- if (loading) return /* @__PURE__ */ jsx20(Loading, { message: t("loading_profiles") });
3132
+ if (loading) return /* @__PURE__ */ jsx26(Loading, { message: t("loading_profiles") });
3133
+ if (mode === "confirm-import" && importProfile2) {
3134
+ return /* @__PURE__ */ jsx26(Box24, { flexDirection: "column", children: /* @__PURE__ */ jsx26(
3135
+ ConfirmDialog,
3136
+ {
3137
+ message: t("profiles_importSummary", {
3138
+ formulae: String(importProfile2.formulae.length),
3139
+ casks: String(importProfile2.casks.length)
3140
+ }),
3141
+ onConfirm: () => {
3142
+ const profile = importProfile2;
3143
+ setImportProfile(null);
3144
+ void startImport(profile);
3145
+ },
3146
+ onCancel: () => {
3147
+ setImportProfile(null);
3148
+ setMode("list");
3149
+ }
3150
+ }
3151
+ ) });
3152
+ }
2217
3153
  if (mode === "importing") {
2218
- return /* @__PURE__ */ jsxs20(Box18, { flexDirection: "column", children: [
2219
- /* @__PURE__ */ jsx20(ProgressLog, { lines: importLines, isRunning: importRunning, title: t("profiles_importTitle") }),
2220
- !importRunning && /* @__PURE__ */ jsx20(Box18, { marginTop: 1, children: /* @__PURE__ */ jsx20(Box18, { borderStyle: "round", borderColor: "#22C55E", paddingX: 2, paddingY: 0, children: /* @__PURE__ */ jsxs20(Text20, { color: "#22C55E", bold: true, children: [
2221
- "\u2714",
2222
- " ",
2223
- t("profiles_importComplete")
2224
- ] }) }) })
3154
+ return /* @__PURE__ */ jsxs25(Box24, { flexDirection: "column", children: [
3155
+ /* @__PURE__ */ jsx26(ProgressLog, { lines: importLines, isRunning: importRunning, title: t("profiles_importTitle") }),
3156
+ !importRunning && /* @__PURE__ */ jsx26(Box24, { marginTop: 1, children: /* @__PURE__ */ jsx26(ResultBanner, { status: importHadError ? "error" : "success", message: importHadError ? t("profiles_importPartial") : `\u2714 ${t("profiles_importComplete")}` }) })
2225
3157
  ] });
2226
3158
  }
2227
3159
  if (mode === "create-name") {
2228
- return /* @__PURE__ */ jsxs20(Box18, { flexDirection: "column", children: [
2229
- /* @__PURE__ */ jsx20(Text20, { bold: true, children: t("profiles_createName") }),
2230
- /* @__PURE__ */ jsx20(
2231
- TextInput3,
2232
- {
2233
- placeholder: t("profiles_namePlaceholder"),
2234
- onSubmit: (val) => {
2235
- setNewName(val);
2236
- setMode("create-desc");
2237
- }
2238
- }
2239
- )
2240
- ] });
3160
+ return /* @__PURE__ */ jsx26(ProfileCreateName, { onSubmit: (val) => {
3161
+ setNewName(val);
3162
+ setMode("create-desc");
3163
+ } });
2241
3164
  }
2242
3165
  if (mode === "create-desc") {
2243
- return /* @__PURE__ */ jsxs20(Box18, { flexDirection: "column", children: [
2244
- /* @__PURE__ */ jsx20(Text20, { bold: true, children: t("profiles_createDesc", { name: newName }) }),
2245
- loadError && /* @__PURE__ */ jsxs20(Text20, { color: "#EF4444", children: [
2246
- t("error_prefix"),
2247
- loadError
2248
- ] }),
2249
- /* @__PURE__ */ jsx20(
2250
- TextInput3,
2251
- {
2252
- placeholder: t("profiles_descPlaceholder"),
2253
- onSubmit: async (val) => {
2254
- try {
2255
- await exportCurrent(newName, val);
2256
- } finally {
2257
- setMode("list");
2258
- setNewName("");
2259
- }
3166
+ return /* @__PURE__ */ jsx26(
3167
+ ProfileCreateDesc,
3168
+ {
3169
+ name: newName,
3170
+ loadError,
3171
+ onSubmit: async (val) => {
3172
+ try {
3173
+ await exportCurrent(newName, val);
3174
+ } finally {
3175
+ setMode("list");
3176
+ setNewName("");
2260
3177
  }
2261
3178
  }
2262
- )
2263
- ] });
3179
+ }
3180
+ );
2264
3181
  }
2265
3182
  if (mode === "edit-name") {
2266
- return /* @__PURE__ */ jsxs20(Box18, { flexDirection: "column", children: [
2267
- /* @__PURE__ */ jsx20(Text20, { bold: true, children: t("profiles_editName") }),
2268
- /* @__PURE__ */ jsx20(
2269
- TextInput3,
2270
- {
2271
- defaultValue: editName,
2272
- onSubmit: (val) => {
2273
- setEditName(val);
2274
- setMode("edit-desc");
2275
- }
2276
- }
2277
- )
2278
- ] });
3183
+ return /* @__PURE__ */ jsx26(ProfileEditName, { defaultName: editName, onSubmit: (val) => {
3184
+ setEditName(val);
3185
+ setMode("edit-desc");
3186
+ } });
2279
3187
  }
2280
3188
  if (mode === "edit-desc") {
2281
- return /* @__PURE__ */ jsxs20(Box18, { flexDirection: "column", children: [
2282
- /* @__PURE__ */ jsx20(Text20, { bold: true, children: t("profiles_editDesc", { name: editName }) }),
2283
- loadError && /* @__PURE__ */ jsxs20(Text20, { color: "#EF4444", children: [
2284
- t("error_prefix"),
2285
- loadError
2286
- ] }),
2287
- /* @__PURE__ */ jsx20(
2288
- TextInput3,
2289
- {
2290
- defaultValue: editDesc,
2291
- onSubmit: async (val) => {
2292
- if (selectedProfile) {
2293
- await updateProfile2(selectedProfile.name, editName, val);
2294
- }
2295
- setMode("detail");
2296
- setEditName("");
2297
- setEditDesc("");
3189
+ return /* @__PURE__ */ jsx26(
3190
+ ProfileEditDesc,
3191
+ {
3192
+ name: editName,
3193
+ defaultDesc: editDesc,
3194
+ loadError,
3195
+ onSubmit: async (val) => {
3196
+ if (selectedProfile) {
3197
+ await updateProfile2(selectedProfile.name, editName, val);
2298
3198
  }
3199
+ setMode("detail");
3200
+ setEditName("");
3201
+ setEditDesc("");
2299
3202
  }
2300
- )
2301
- ] });
3203
+ }
3204
+ );
2302
3205
  }
2303
3206
  if (mode === "detail" && selectedProfile) {
2304
- return /* @__PURE__ */ jsxs20(Box18, { flexDirection: "column", children: [
2305
- /* @__PURE__ */ jsx20(Text20, { bold: true, color: "#FFD700", children: selectedProfile.name }),
2306
- /* @__PURE__ */ jsx20(Text20, { color: "#9CA3AF", children: selectedProfile.description }),
2307
- /* @__PURE__ */ jsx20(Text20, { color: "#9CA3AF", children: t("profiles_created", { date: new Date(selectedProfile.createdAt).toLocaleDateString() }) }),
2308
- /* @__PURE__ */ jsxs20(Box18, { marginTop: 1, flexDirection: "column", children: [
2309
- /* @__PURE__ */ jsx20(Text20, { bold: true, children: t("profiles_formulaeCount", { count: selectedProfile.formulae.length }) }),
2310
- /* @__PURE__ */ jsxs20(Box18, { paddingLeft: 2, flexDirection: "column", children: [
2311
- selectedProfile.formulae.slice(0, 30).map((f) => /* @__PURE__ */ jsx20(Text20, { color: "#9CA3AF", children: f }, f)),
2312
- selectedProfile.formulae.length > 30 && /* @__PURE__ */ jsx20(Text20, { color: "#6B7280", italic: true, children: t("common_andMore", { count: selectedProfile.formulae.length - 30 }) })
2313
- ] }),
2314
- /* @__PURE__ */ jsx20(Text20, { bold: true, children: t("profiles_casksCount", { count: selectedProfile.casks.length }) }),
2315
- /* @__PURE__ */ jsx20(Box18, { paddingLeft: 2, flexDirection: "column", children: selectedProfile.casks.map((c) => /* @__PURE__ */ jsx20(Text20, { color: "#9CA3AF", children: c }, c)) })
2316
- ] }),
2317
- /* @__PURE__ */ jsx20(Box18, { marginTop: 1, children: /* @__PURE__ */ jsxs20(Text20, { color: "#6B7280", children: [
2318
- "esc:",
2319
- t("hint_back"),
2320
- " e:",
2321
- t("hint_edit"),
2322
- " i:",
2323
- t("hint_importProfile")
2324
- ] }) })
2325
- ] });
3207
+ return /* @__PURE__ */ jsx26(ProfileDetailMode, { profile: selectedProfile });
2326
3208
  }
2327
- return /* @__PURE__ */ jsxs20(Box18, { flexDirection: "column", children: [
2328
- /* @__PURE__ */ jsx20(SectionHeader, { emoji: "\u{1F4C1}", title: t("profiles_title", { count: profileNames.length }), gradient: GRADIENTS.gold }),
2329
- confirmDelete && profileNames[cursor] && /* @__PURE__ */ jsx20(Box18, { marginY: 1, children: /* @__PURE__ */ jsx20(
2330
- ConfirmDialog,
2331
- {
2332
- message: t("profiles_confirmDelete", { name: profileNames[cursor] }),
2333
- onConfirm: () => {
2334
- void deleteProfile2(profileNames[cursor]);
2335
- setConfirmDelete(false);
2336
- },
2337
- onCancel: () => setConfirmDelete(false)
2338
- }
2339
- ) }),
2340
- profileNames.length === 0 && !confirmDelete && /* @__PURE__ */ jsx20(Box18, { marginTop: 1, borderStyle: "round", borderColor: "#6B7280", paddingX: 2, paddingY: 0, children: /* @__PURE__ */ jsxs20(Box18, { flexDirection: "column", children: [
2341
- /* @__PURE__ */ jsx20(Text20, { color: "#6B7280", italic: true, children: t("profiles_noProfiles") }),
2342
- /* @__PURE__ */ jsxs20(Text20, { color: "#9CA3AF", children: [
2343
- t("profiles_press"),
2344
- " ",
2345
- /* @__PURE__ */ jsx20(Text20, { color: "#FFD700", bold: true, children: "n" }),
2346
- " ",
2347
- t("profiles_exportHint")
2348
- ] })
2349
- ] }) }),
2350
- profileNames.length > 0 && !confirmDelete && /* @__PURE__ */ jsxs20(Box18, { flexDirection: "column", marginTop: 1, children: [
2351
- profileNames.map((name, i) => {
2352
- const isCurrent = i === cursor;
2353
- return /* @__PURE__ */ jsxs20(Box18, { gap: 1, children: [
2354
- /* @__PURE__ */ jsx20(Text20, { color: isCurrent ? "#22C55E" : "#9CA3AF", children: isCurrent ? "\u25B6" : " " }),
2355
- /* @__PURE__ */ jsx20(Text20, { bold: isCurrent, inverse: isCurrent, children: name })
2356
- ] }, name);
2357
- }),
2358
- /* @__PURE__ */ jsx20(Box18, { marginTop: 1, children: /* @__PURE__ */ jsxs20(Text20, { color: "#F9FAFB", bold: true, children: [
2359
- cursor + 1,
2360
- "/",
2361
- profileNames.length
2362
- ] }) })
2363
- ] })
2364
- ] });
3209
+ return /* @__PURE__ */ jsx26(
3210
+ ProfileListMode,
3211
+ {
3212
+ profileNames,
3213
+ cursor,
3214
+ confirmDelete,
3215
+ loadError,
3216
+ onConfirmDelete: () => {
3217
+ void deleteProfile2(profileNames[cursor]);
3218
+ setConfirmDelete(false);
3219
+ },
3220
+ onCancelDelete: () => setConfirmDelete(false)
3221
+ }
3222
+ );
2365
3223
  }
2366
3224
 
2367
3225
  // src/views/smart-cleanup.tsx
2368
- import { useEffect as useEffect12, useRef as useRef6, useState as useState9 } from "react";
2369
- import { Box as Box19, Text as Text21, useInput as useInput10 } from "ink";
3226
+ import { useEffect as useEffect12, useRef as useRef7, useState as useState9 } from "react";
3227
+ import { Box as Box25, Text as Text26, useInput as useInput10 } from "ink";
2370
3228
 
2371
3229
  // src/stores/cleanup-store.ts
2372
- import { create as create5 } from "zustand";
3230
+ import { create as create6 } from "zustand";
2373
3231
 
2374
3232
  // src/lib/cleanup/cleanup-analyzer.ts
2375
3233
  import { execFile } from "child_process";
@@ -2392,8 +3250,8 @@ async function getCellarPath(name) {
2392
3250
  return null;
2393
3251
  }
2394
3252
  }
2395
- async function analyzeCleanup(formulae, leaves) {
2396
- requirePro();
3253
+ async function analyzeCleanup(isPro, formulae, leaves) {
3254
+ if (!isPro) throw new Error("Pro license required");
2397
3255
  const leavesSet = new Set(leaves);
2398
3256
  const reverseDeps = /* @__PURE__ */ new Map();
2399
3257
  for (const f of formulae) {
@@ -2450,7 +3308,7 @@ async function analyzeCleanup(formulae, leaves) {
2450
3308
  }
2451
3309
 
2452
3310
  // src/stores/cleanup-store.ts
2453
- var useCleanupStore = create5((set, get) => ({
3311
+ var useCleanupStore = create6((set, get) => ({
2454
3312
  summary: null,
2455
3313
  selected: /* @__PURE__ */ new Set(),
2456
3314
  loading: false,
@@ -2464,7 +3322,8 @@ var useCleanupStore = create5((set, get) => ({
2464
3322
  await brewState.fetchLeaves();
2465
3323
  }
2466
3324
  const { formulae, leaves } = useBrewStore.getState();
2467
- const summary = await analyzeCleanup(formulae, leaves);
3325
+ const isPro = useLicenseStore.getState().isPro();
3326
+ const summary = await analyzeCleanup(isPro, formulae, leaves);
2468
3327
  set({ summary, selected: /* @__PURE__ */ new Set(), loading: false });
2469
3328
  } catch (err) {
2470
3329
  set({ error: err instanceof Error ? err.message : String(err), loading: false });
@@ -2484,7 +3343,7 @@ var useCleanupStore = create5((set, get) => ({
2484
3343
  }));
2485
3344
 
2486
3345
  // src/views/smart-cleanup.tsx
2487
- import { jsx as jsx21, jsxs as jsxs21 } from "react/jsx-runtime";
3346
+ import { jsx as jsx27, jsxs as jsxs26 } from "react/jsx-runtime";
2488
3347
  function SmartCleanupView() {
2489
3348
  const { summary, selected, loading, error, analyze, toggleSelect, selectAll } = useCleanupStore();
2490
3349
  const [cursor, setCursor] = useState9(0);
@@ -2492,7 +3351,7 @@ function SmartCleanupView() {
2492
3351
  const [confirmForce, setConfirmForce] = useState9(false);
2493
3352
  const [failedNames, setFailedNames] = useState9([]);
2494
3353
  const stream = useBrewStream();
2495
- const hasRefreshed = useRef6(false);
3354
+ const hasRefreshed = useRef7(false);
2496
3355
  useEffect12(() => {
2497
3356
  analyze();
2498
3357
  }, []);
@@ -2535,34 +3394,26 @@ function SmartCleanupView() {
2535
3394
  if (input === "j" || key.downArrow) setCursor((c) => Math.min(c + 1, Math.max(0, candidates.length - 1)));
2536
3395
  else if (input === "k" || key.upArrow) setCursor((c) => Math.max(c - 1, 0));
2537
3396
  });
2538
- if (loading) return /* @__PURE__ */ jsx21(Loading, { message: t("loading_cleanup") });
2539
- if (error) return /* @__PURE__ */ jsx21(ErrorMessage, { message: error });
3397
+ if (loading) return /* @__PURE__ */ jsx27(Loading, { message: t("loading_cleanup") });
3398
+ if (error) return /* @__PURE__ */ jsx27(ErrorMessage, { message: error });
2540
3399
  if (stream.isRunning || stream.lines.length > 0) {
2541
- return /* @__PURE__ */ jsxs21(Box19, { flexDirection: "column", children: [
2542
- /* @__PURE__ */ jsx21(ProgressLog, { lines: stream.lines, isRunning: stream.isRunning, title: t("cleanup_cleaning") }),
2543
- stream.isRunning && /* @__PURE__ */ jsxs21(Text21, { color: "#6B7280", children: [
3400
+ return /* @__PURE__ */ jsxs26(Box25, { flexDirection: "column", children: [
3401
+ /* @__PURE__ */ jsx27(ProgressLog, { lines: stream.lines, isRunning: stream.isRunning, title: t("cleanup_cleaning") }),
3402
+ stream.isRunning && /* @__PURE__ */ jsxs26(Text26, { color: COLORS.muted, children: [
2544
3403
  "esc:",
2545
3404
  t("hint_cancel")
2546
3405
  ] }),
2547
- !stream.isRunning && !stream.error && /* @__PURE__ */ jsx21(Box19, { borderStyle: "round", borderColor: "#22C55E", paddingX: 2, paddingY: 0, children: /* @__PURE__ */ jsxs21(Text21, { color: "#22C55E", bold: true, children: [
2548
- "\u2714",
2549
- " ",
2550
- t("cleanup_complete")
2551
- ] }) }),
2552
- !stream.isRunning && stream.error && /* @__PURE__ */ jsxs21(Box19, { flexDirection: "column", gap: 1, children: [
2553
- /* @__PURE__ */ jsx21(Box19, { borderStyle: "round", borderColor: "#EF4444", paddingX: 2, paddingY: 0, children: /* @__PURE__ */ jsxs21(Text21, { color: "#EF4444", bold: true, children: [
2554
- "\u2718",
2555
- " ",
2556
- t("cleanup_depError")
2557
- ] }) }),
2558
- isDependencyError && failedNames.length > 0 && /* @__PURE__ */ jsxs21(Text21, { color: "#F59E0B", children: [
3406
+ !stream.isRunning && !stream.error && /* @__PURE__ */ jsx27(ResultBanner, { status: "success", message: `\u2714 ${t("cleanup_complete")}` }),
3407
+ !stream.isRunning && stream.error && /* @__PURE__ */ jsxs26(Box25, { flexDirection: "column", gap: 1, children: [
3408
+ /* @__PURE__ */ jsx27(ResultBanner, { status: "error", message: `\u2718 ${t("cleanup_depError")}` }),
3409
+ isDependencyError && failedNames.length > 0 && /* @__PURE__ */ jsxs26(Text26, { color: COLORS.warning, children: [
2559
3410
  "F:",
2560
3411
  t("hint_force"),
2561
3412
  " r:",
2562
3413
  t("hint_refresh")
2563
3414
  ] })
2564
3415
  ] }),
2565
- confirmForce && /* @__PURE__ */ jsx21(Box19, { marginY: 1, children: /* @__PURE__ */ jsx21(
3416
+ confirmForce && /* @__PURE__ */ jsx27(Box25, { marginY: 1, children: /* @__PURE__ */ jsx27(
2566
3417
  ConfirmDialog,
2567
3418
  {
2568
3419
  message: t("cleanup_confirmForce", { count: failedNames.length }),
@@ -2577,49 +3428,47 @@ function SmartCleanupView() {
2577
3428
  ) })
2578
3429
  ] });
2579
3430
  }
2580
- return /* @__PURE__ */ jsxs21(Box19, { flexDirection: "column", children: [
2581
- /* @__PURE__ */ jsx21(SectionHeader, { emoji: "\u{1F9F9}", title: t("cleanup_title"), gradient: GRADIENTS.emerald }),
2582
- summary && /* @__PURE__ */ jsxs21(Box19, { gap: 1, marginY: 1, children: [
2583
- /* @__PURE__ */ jsx21(StatCard, { label: t("cleanup_orphans"), value: candidates.length, color: candidates.length > 0 ? "#F59E0B" : "#22C55E" }),
2584
- /* @__PURE__ */ jsx21(StatCard, { label: t("cleanup_reclaimable"), value: summary.totalReclaimableFormatted, color: "#38BDF8" }),
2585
- /* @__PURE__ */ jsx21(StatCard, { label: t("cleanup_selected"), value: selected.size, color: selected.size > 0 ? "#22C55E" : "#6B7280" })
3431
+ return /* @__PURE__ */ jsxs26(Box25, { flexDirection: "column", children: [
3432
+ /* @__PURE__ */ jsx27(SectionHeader, { emoji: "\u{1F9F9}", title: t("cleanup_title"), gradient: GRADIENTS.emerald }),
3433
+ summary && /* @__PURE__ */ jsxs26(Box25, { gap: 1, marginY: 1, children: [
3434
+ /* @__PURE__ */ jsx27(StatCard, { label: t("cleanup_orphans"), value: candidates.length, color: candidates.length > 0 ? COLORS.warning : COLORS.success }),
3435
+ /* @__PURE__ */ jsx27(StatCard, { label: t("cleanup_reclaimable"), value: summary.totalReclaimableFormatted, color: COLORS.sky }),
3436
+ /* @__PURE__ */ jsx27(StatCard, { label: t("cleanup_selected"), value: selected.size, color: selected.size > 0 ? COLORS.success : COLORS.muted })
2586
3437
  ] }),
2587
- confirmClean && /* @__PURE__ */ jsx21(Box19, { marginY: 1, children: /* @__PURE__ */ jsx21(
2588
- ConfirmDialog,
2589
- {
2590
- message: t("cleanup_confirmUninstall", { count: selected.size }),
2591
- onConfirm: () => {
2592
- setConfirmClean(false);
2593
- hasRefreshed.current = false;
2594
- const names = Array.from(selected);
2595
- setFailedNames(names);
2596
- void stream.run(["uninstall", ...names]);
2597
- },
2598
- onCancel: () => setConfirmClean(false)
2599
- }
2600
- ) }),
2601
- candidates.length === 0 && !confirmClean && /* @__PURE__ */ jsx21(Box19, { borderStyle: "round", borderColor: "#22C55E", paddingX: 2, paddingY: 0, children: /* @__PURE__ */ jsxs21(Text21, { color: "#22C55E", bold: true, children: [
2602
- "\u2714",
2603
- " ",
2604
- t("cleanup_systemClean")
2605
- ] }) }),
2606
- candidates.length > 0 && !confirmClean && /* @__PURE__ */ jsxs21(Box19, { flexDirection: "column", children: [
3438
+ confirmClean && /* @__PURE__ */ jsxs26(Box25, { flexDirection: "column", marginY: 1, gap: 1, children: [
3439
+ /* @__PURE__ */ jsx27(Box25, { borderStyle: "round", borderColor: COLORS.warning, paddingX: 2, paddingY: 0, children: /* @__PURE__ */ jsx27(Text26, { color: COLORS.warning, children: t("cleanup_warning_system_tools") }) }),
3440
+ /* @__PURE__ */ jsx27(
3441
+ ConfirmDialog,
3442
+ {
3443
+ message: t("cleanup_confirmUninstall", { count: selected.size }),
3444
+ onConfirm: () => {
3445
+ setConfirmClean(false);
3446
+ hasRefreshed.current = false;
3447
+ const names = Array.from(selected);
3448
+ setFailedNames(names);
3449
+ void stream.run(["uninstall", ...names]);
3450
+ },
3451
+ onCancel: () => setConfirmClean(false)
3452
+ }
3453
+ )
3454
+ ] }),
3455
+ candidates.length === 0 && !confirmClean && /* @__PURE__ */ jsx27(ResultBanner, { status: "success", message: `\u2714 ${t("cleanup_systemClean")}` }),
3456
+ candidates.length > 0 && !confirmClean && /* @__PURE__ */ jsxs26(Box25, { flexDirection: "column", children: [
2607
3457
  candidates.map((c, i) => {
2608
3458
  const isCurrent = i === cursor;
2609
3459
  const isSelected = selected.has(c.name);
2610
- return /* @__PURE__ */ jsxs21(Box19, { gap: 1, children: [
2611
- /* @__PURE__ */ jsx21(Text21, { color: isCurrent ? "#22C55E" : "#9CA3AF", children: isCurrent ? "\u25B6" : " " }),
2612
- /* @__PURE__ */ jsx21(Text21, { color: isSelected ? "#22C55E" : "#9CA3AF", children: isSelected ? "\u2611" : "\u2610" }),
2613
- /* @__PURE__ */ jsx21(Text21, { bold: isCurrent, inverse: isCurrent, color: isCurrent ? "#F9FAFB" : "#9CA3AF", children: c.name }),
2614
- /* @__PURE__ */ jsx21(Text21, { color: "#F59E0B", children: c.diskUsageFormatted }),
2615
- /* @__PURE__ */ jsxs21(Text21, { color: "#6B7280", children: [
3460
+ return /* @__PURE__ */ jsxs26(SelectableRow, { isCurrent, children: [
3461
+ /* @__PURE__ */ jsx27(Text26, { color: isSelected ? COLORS.success : COLORS.muted, children: isSelected ? "\u2611" : "\u2610" }),
3462
+ /* @__PURE__ */ jsx27(Text26, { bold: isCurrent, inverse: isCurrent, color: isCurrent ? COLORS.text : COLORS.muted, children: c.name }),
3463
+ /* @__PURE__ */ jsx27(Text26, { color: COLORS.warning, children: c.diskUsageFormatted }),
3464
+ /* @__PURE__ */ jsxs26(Text26, { color: COLORS.textSecondary, children: [
2616
3465
  "[",
2617
3466
  c.reason,
2618
3467
  "]"
2619
3468
  ] })
2620
3469
  ] }, c.name);
2621
3470
  }),
2622
- /* @__PURE__ */ jsx21(Box19, { marginTop: 1, children: /* @__PURE__ */ jsxs21(Text21, { color: "#F9FAFB", bold: true, children: [
3471
+ /* @__PURE__ */ jsx27(Box25, { marginTop: 1, children: /* @__PURE__ */ jsxs26(Text26, { color: COLORS.text, bold: true, children: [
2623
3472
  cursor + 1,
2624
3473
  "/",
2625
3474
  candidates.length
@@ -2629,42 +3478,45 @@ function SmartCleanupView() {
2629
3478
  }
2630
3479
 
2631
3480
  // 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";
3481
+ import { useEffect as useEffect13, useState as useState10, useMemo as useMemo4 } from "react";
3482
+ import { Box as Box26, Text as Text27, useInput as useInput11, useStdout as useStdout5 } from "ink";
2634
3483
 
2635
3484
  // src/stores/history-store.ts
2636
- import { create as create6 } from "zustand";
2637
- var useHistoryStore = create6((set) => ({
3485
+ import { create as create7 } from "zustand";
3486
+ var useHistoryStore = create7((set) => ({
2638
3487
  entries: [],
2639
3488
  loading: false,
2640
3489
  error: null,
2641
3490
  fetchHistory: async () => {
2642
3491
  set({ loading: true, error: null });
2643
3492
  try {
2644
- const entries = await loadHistory();
3493
+ const isPro = useLicenseStore.getState().isPro();
3494
+ const entries = await loadHistory(isPro);
2645
3495
  set({ entries, loading: false });
2646
3496
  } catch (err) {
2647
3497
  set({ loading: false, error: err instanceof Error ? err.message : String(err) });
2648
3498
  }
2649
3499
  },
2650
3500
  logAction: async (action, packageName, success, error = null) => {
2651
- await appendEntry(action, packageName, success, error);
2652
- const entries = await loadHistory();
3501
+ const isPro = useLicenseStore.getState().isPro();
3502
+ await appendEntry(isPro, action, packageName, success, error);
3503
+ const entries = await loadHistory(isPro);
2653
3504
  set({ entries });
2654
3505
  },
2655
3506
  clearHistory: async () => {
2656
- await clearHistory();
3507
+ const isPro = useLicenseStore.getState().isPro();
3508
+ await clearHistory(isPro);
2657
3509
  set({ entries: [] });
2658
3510
  }
2659
3511
  }));
2660
3512
 
2661
3513
  // src/views/history.tsx
2662
- import { jsx as jsx22, jsxs as jsxs22 } from "react/jsx-runtime";
3514
+ import { jsx as jsx28, jsxs as jsxs27 } from "react/jsx-runtime";
2663
3515
  var ACTION_ICONS = {
2664
- install: { icon: "+", color: "#22C55E" },
2665
- uninstall: { icon: "-", color: "#EF4444" },
2666
- upgrade: { icon: "\u2191", color: "#06B6D4" },
2667
- "upgrade-all": { icon: "\u21C8", color: "#06B6D4" }
3516
+ install: { icon: "+", color: COLORS.success },
3517
+ uninstall: { icon: "-", color: COLORS.error },
3518
+ upgrade: { icon: "\u2191", color: COLORS.info },
3519
+ "upgrade-all": { icon: "\u21C8", color: COLORS.info }
2668
3520
  };
2669
3521
  var ACTION_LABEL_KEYS = {
2670
3522
  install: "history_actionInstall",
@@ -2696,7 +3548,7 @@ function HistoryView() {
2696
3548
  }
2697
3549
  return void 0;
2698
3550
  }, [isSearching]);
2699
- const filtered = useMemo3(() => {
3551
+ const filtered = useMemo4(() => {
2700
3552
  let result = entries;
2701
3553
  if (filter !== "all") {
2702
3554
  result = result.filter((e) => e.action === filter);
@@ -2737,18 +3589,19 @@ function HistoryView() {
2737
3589
  if (input === "j" || key.downArrow) setCursor((c) => Math.min(c + 1, Math.max(0, filtered.length - 1)));
2738
3590
  else if (input === "k" || key.upArrow) setCursor((c) => Math.max(c - 1, 0));
2739
3591
  });
2740
- if (loading) return /* @__PURE__ */ jsx22(Loading, { message: t("loading_history") });
2741
- if (error) return /* @__PURE__ */ jsx22(ErrorMessage, { message: error });
2742
- const MAX_VISIBLE_ROWS = 20;
3592
+ if (loading) return /* @__PURE__ */ jsx28(Loading, { message: t("loading_history") });
3593
+ if (error) return /* @__PURE__ */ jsx28(ErrorMessage, { message: error });
3594
+ const { stdout } = useStdout5();
3595
+ const MAX_VISIBLE_ROWS = Math.max(5, (stdout?.rows ?? 24) - 8);
2743
3596
  const start = Math.max(0, cursor - Math.floor(MAX_VISIBLE_ROWS / 2));
2744
3597
  const visible = filtered.slice(start, start + MAX_VISIBLE_ROWS);
2745
- return /* @__PURE__ */ jsxs22(Box20, { flexDirection: "column", children: [
2746
- /* @__PURE__ */ jsxs22(Box20, { gap: 2, marginBottom: 1, children: [
2747
- /* @__PURE__ */ jsx22(SectionHeader, { emoji: "\u{1F4DC}", title: t("history_title", { count: filtered.length }), gradient: GRADIENTS.gold }),
2748
- /* @__PURE__ */ jsx22(Text22, { color: filter === "all" ? "#F9FAFB" : "#FFD700", children: t("history_filterLabel", { filter }) })
3598
+ return /* @__PURE__ */ jsxs27(Box26, { flexDirection: "column", children: [
3599
+ /* @__PURE__ */ jsxs27(Box26, { gap: 2, marginBottom: 1, children: [
3600
+ /* @__PURE__ */ jsx28(SectionHeader, { emoji: "\u{1F4DC}", title: t("history_title", { count: filtered.length }), gradient: GRADIENTS.gold }),
3601
+ /* @__PURE__ */ jsx28(Text27, { color: filter === "all" ? COLORS.text : COLORS.gold, children: t("history_filterLabel", { filter }) })
2749
3602
  ] }),
2750
- isSearching && /* @__PURE__ */ jsx22(Box20, { marginBottom: 1, children: /* @__PURE__ */ jsx22(SearchInput, { defaultValue: searchQuery, onChange: setSearchQuery, placeholder: t("history_searchPlaceholder"), isActive: true }) }),
2751
- confirmClear && /* @__PURE__ */ jsx22(
3603
+ isSearching && /* @__PURE__ */ jsx28(Box26, { marginBottom: 1, children: /* @__PURE__ */ jsx28(SearchInput, { defaultValue: searchQuery, onChange: setSearchQuery, placeholder: t("history_searchPlaceholder"), isActive: true }) }),
3604
+ confirmClear && /* @__PURE__ */ jsx28(
2752
3605
  ConfirmDialog,
2753
3606
  {
2754
3607
  message: t("history_confirmClear", { count: entries.length }),
@@ -2759,10 +3612,10 @@ function HistoryView() {
2759
3612
  onCancel: () => setConfirmClear(false)
2760
3613
  }
2761
3614
  ),
2762
- confirmReplay && /* @__PURE__ */ jsx22(
3615
+ confirmReplay && /* @__PURE__ */ jsx28(
2763
3616
  ConfirmDialog,
2764
3617
  {
2765
- message: confirmReplay.action === "upgrade-all" ? t("history_replayAll") : t("history_confirmReplay", { action: t(ACTION_LABEL_KEYS[confirmReplay.action]), name: confirmReplay.packageName ?? "" }),
3618
+ message: confirmReplay.action === "upgrade-all" ? t("history_replayAll") + "\n" + t("upgrade_all_warning") : t("history_confirmReplay", { action: t(ACTION_LABEL_KEYS[confirmReplay.action]), name: confirmReplay.packageName ?? "" }),
2766
3619
  onConfirm: async () => {
2767
3620
  const entry = confirmReplay;
2768
3621
  setConfirmReplay(null);
@@ -2787,10 +3640,10 @@ function HistoryView() {
2787
3640
  onCancel: () => setConfirmReplay(null)
2788
3641
  }
2789
3642
  ),
2790
- (stream.isRunning || stream.lines.length > 0) && /* @__PURE__ */ jsx22(Box20, { marginY: 1, children: /* @__PURE__ */ jsx22(ProgressLog, { lines: stream.lines, isRunning: stream.isRunning, title: t("hint_replay") }) }),
2791
- filtered.length === 0 && !confirmClear && /* @__PURE__ */ jsx22(Text22, { color: "#6B7280", italic: true, children: filter !== "all" ? t("history_noEntriesFor", { filter }) : t("history_noEntries") }),
2792
- filtered.length > 0 && !confirmClear && /* @__PURE__ */ jsxs22(Box20, { flexDirection: "column", children: [
2793
- start > 0 && /* @__PURE__ */ jsxs22(Text22, { color: "#6B7280", dimColor: true, children: [
3643
+ (stream.isRunning || stream.lines.length > 0) && /* @__PURE__ */ jsx28(Box26, { marginY: 1, children: /* @__PURE__ */ jsx28(ProgressLog, { lines: stream.lines, isRunning: stream.isRunning, title: t("hint_replay") }) }),
3644
+ filtered.length === 0 && !confirmClear && /* @__PURE__ */ jsx28(Text27, { color: COLORS.textSecondary, italic: true, children: filter !== "all" ? t("history_noEntriesFor", { filter }) : t("history_noEntries") }),
3645
+ filtered.length > 0 && !confirmClear && /* @__PURE__ */ jsxs27(Box26, { flexDirection: "column", children: [
3646
+ start > 0 && /* @__PURE__ */ jsxs27(Text27, { color: COLORS.textSecondary, dimColor: true, children: [
2794
3647
  " ",
2795
3648
  t("scroll_moreAbove", { count: start })
2796
3649
  ] }),
@@ -2799,20 +3652,19 @@ function HistoryView() {
2799
3652
  const isCurrent = idx === cursor;
2800
3653
  const { icon, color } = ACTION_ICONS[entry.action];
2801
3654
  const ts = new Date(entry.timestamp).getTime() / 1e3;
2802
- return /* @__PURE__ */ jsxs22(Box20, { gap: 1, children: [
2803
- /* @__PURE__ */ jsx22(Text22, { color: isCurrent ? "#22C55E" : "#9CA3AF", children: isCurrent ? "\u25B6" : " " }),
2804
- /* @__PURE__ */ jsx22(Text22, { color, bold: true, children: icon }),
2805
- /* @__PURE__ */ jsx22(Text22, { bold: isCurrent, inverse: isCurrent, color: isCurrent ? "#F9FAFB" : "#9CA3AF", children: t(ACTION_LABEL_KEYS[entry.action]).padEnd(12) }),
2806
- /* @__PURE__ */ jsx22(Text22, { color: "#F9FAFB", children: entry.packageName ?? t("history_all") }),
2807
- entry.success ? /* @__PURE__ */ jsx22(StatusBadge, { label: t("badge_ok"), variant: "success" }) : /* @__PURE__ */ jsx22(StatusBadge, { label: t("badge_fail"), variant: "error" }),
2808
- /* @__PURE__ */ jsx22(Text22, { color: "#9CA3AF", children: formatRelativeTime(ts) })
3655
+ return /* @__PURE__ */ jsxs27(SelectableRow, { isCurrent, children: [
3656
+ /* @__PURE__ */ jsx28(Text27, { color, bold: true, children: icon }),
3657
+ /* @__PURE__ */ jsx28(Text27, { bold: isCurrent, inverse: isCurrent, color: isCurrent ? COLORS.text : COLORS.muted, children: t(ACTION_LABEL_KEYS[entry.action]).padEnd(12) }),
3658
+ /* @__PURE__ */ jsx28(Text27, { color: COLORS.text, children: entry.packageName ?? t("history_all") }),
3659
+ entry.success ? /* @__PURE__ */ jsx28(StatusBadge, { label: t("badge_ok"), variant: "success" }) : /* @__PURE__ */ jsx28(StatusBadge, { label: t("badge_fail"), variant: "error" }),
3660
+ /* @__PURE__ */ jsx28(Text27, { color: COLORS.muted, children: formatRelativeTime(ts) })
2809
3661
  ] }, entry.id);
2810
3662
  }),
2811
- start + MAX_VISIBLE_ROWS < filtered.length && /* @__PURE__ */ jsxs22(Text22, { color: "#6B7280", dimColor: true, children: [
3663
+ start + MAX_VISIBLE_ROWS < filtered.length && /* @__PURE__ */ jsxs27(Text27, { color: COLORS.textSecondary, dimColor: true, children: [
2812
3664
  " ",
2813
3665
  t("scroll_moreBelow", { count: filtered.length - start - MAX_VISIBLE_ROWS })
2814
3666
  ] }),
2815
- /* @__PURE__ */ jsx22(Box20, { marginTop: 1, children: /* @__PURE__ */ jsxs22(Text22, { color: "#F9FAFB", bold: true, children: [
3667
+ /* @__PURE__ */ jsx28(Box26, { marginTop: 1, children: /* @__PURE__ */ jsxs27(Text27, { color: COLORS.text, bold: true, children: [
2816
3668
  cursor + 1,
2817
3669
  "/",
2818
3670
  filtered.length
@@ -2823,10 +3675,10 @@ function HistoryView() {
2823
3675
 
2824
3676
  // src/views/security-audit.tsx
2825
3677
  import { useEffect as useEffect14, useState as useState11 } from "react";
2826
- import { Box as Box21, Text as Text23, useInput as useInput12 } from "ink";
3678
+ import { Box as Box27, Text as Text28, useInput as useInput12 } from "ink";
2827
3679
 
2828
3680
  // src/stores/security-store.ts
2829
- import { create as create7 } from "zustand";
3681
+ import { create as create8 } from "zustand";
2830
3682
 
2831
3683
  // src/lib/security/osv-api.ts
2832
3684
  var OSV_BATCH_URL = "https://api.osv.dev/v1/querybatch";
@@ -2857,11 +3709,11 @@ function getFixedVersion(vuln) {
2857
3709
  }
2858
3710
  var BATCH_SIZE = 100;
2859
3711
  async function queryBatch(packages, queries) {
2860
- const res = await fetch(OSV_BATCH_URL, {
3712
+ const res = await fetchWithTimeout(OSV_BATCH_URL, {
2861
3713
  method: "POST",
2862
3714
  headers: { "Content-Type": "application/json" },
2863
3715
  body: JSON.stringify({ queries })
2864
- });
3716
+ }, 15e3);
2865
3717
  if (!res.ok) {
2866
3718
  if (res.status === 400 && queries.length > 1) {
2867
3719
  return queryOneByOne(packages);
@@ -2869,6 +3721,12 @@ async function queryBatch(packages, queries) {
2869
3721
  throw new Error(`OSV API error: ${res.status} ${res.statusText}`);
2870
3722
  }
2871
3723
  const data = await res.json();
3724
+ if (!data || !Array.isArray(data.results)) {
3725
+ throw new Error("Invalid OSV API response: missing results array");
3726
+ }
3727
+ if (data.results.length !== packages.length) {
3728
+ throw new Error(`OSV API response mismatch: expected ${packages.length} results, got ${data.results.length}`);
3729
+ }
2872
3730
  const result = /* @__PURE__ */ new Map();
2873
3731
  for (let i = 0; i < packages.length; i++) {
2874
3732
  const vulns = data.results[i]?.vulns;
@@ -2888,6 +3746,7 @@ async function queryBatch(packages, queries) {
2888
3746
  }
2889
3747
  async function queryOneByOne(packages) {
2890
3748
  const result = /* @__PURE__ */ new Map();
3749
+ const errors = [];
2891
3750
  for (const pkg of packages) {
2892
3751
  try {
2893
3752
  const partial = await queryBatch(
@@ -2895,15 +3754,43 @@ async function queryOneByOne(packages) {
2895
3754
  [{ package: { name: pkg.name, ecosystem: "Homebrew" }, version: pkg.version }]
2896
3755
  );
2897
3756
  for (const [k, v] of partial) result.set(k, v);
2898
- } catch {
3757
+ } catch (err) {
3758
+ const msg = err instanceof Error ? err.message : String(err);
3759
+ if (msg.includes("400")) {
3760
+ errors.push(`Skipped ${pkg.name}: ${msg}`);
3761
+ continue;
3762
+ }
3763
+ if (msg.includes("429")) {
3764
+ logger.warn(`Rate limited by OSV API, backing off`, { package: pkg.name });
3765
+ await new Promise((r) => setTimeout(r, 2e3));
3766
+ try {
3767
+ const retryResult = await queryBatch(
3768
+ [pkg],
3769
+ [{ package: { name: pkg.name, ecosystem: "Homebrew" }, version: pkg.version }]
3770
+ );
3771
+ for (const [k, v] of retryResult) result.set(k, v);
3772
+ } catch {
3773
+ errors.push(`Failed after retry ${pkg.name}: ${msg}`);
3774
+ }
3775
+ continue;
3776
+ }
3777
+ logger.error(`OSV query failed for ${pkg.name}: ${msg}`);
3778
+ errors.push(`${pkg.name}: ${msg}`);
2899
3779
  }
3780
+ await new Promise((r) => setTimeout(r, 75));
3781
+ }
3782
+ if (errors.length > 0) {
3783
+ logger.warn(`OSV query errors for ${errors.length} packages`, { errors: errors.slice(0, 5) });
2900
3784
  }
2901
3785
  return result;
2902
3786
  }
2903
3787
  async function queryVulnerabilities(packages) {
3788
+ const validPackages = packages.filter(
3789
+ (p) => p.version && typeof p.version === "string" && p.version.trim().length > 0
3790
+ );
2904
3791
  const result = /* @__PURE__ */ new Map();
2905
- for (let i = 0; i < packages.length; i += BATCH_SIZE) {
2906
- const batch = packages.slice(i, i + BATCH_SIZE);
3792
+ for (let i = 0; i < validPackages.length; i += BATCH_SIZE) {
3793
+ const batch = validPackages.slice(i, i + BATCH_SIZE);
2907
3794
  const queries = batch.map((p) => ({
2908
3795
  package: { name: p.name, ecosystem: "Homebrew" },
2909
3796
  version: p.version
@@ -2922,8 +3809,8 @@ var SEVERITY_ORDER = {
2922
3809
  LOW: 1,
2923
3810
  UNKNOWN: 0
2924
3811
  };
2925
- async function runSecurityAudit(formulae, casks) {
2926
- requirePro();
3812
+ async function runSecurityAudit(isPro, formulae, casks) {
3813
+ if (!isPro) throw new Error("Pro license required");
2927
3814
  const packages = [];
2928
3815
  for (const f of formulae) {
2929
3816
  const version = f.installed[0]?.version ?? f.versions.stable;
@@ -2974,11 +3861,17 @@ async function runSecurityAudit(formulae, casks) {
2974
3861
  }
2975
3862
 
2976
3863
  // src/stores/security-store.ts
2977
- var useSecurityStore = create7((set) => ({
3864
+ var CACHE_TTL_MS = 30 * 60 * 1e3;
3865
+ var useSecurityStore = create8((set, get) => ({
2978
3866
  summary: null,
2979
3867
  loading: false,
2980
3868
  error: null,
2981
- scan: async () => {
3869
+ cachedAt: null,
3870
+ scan: async (forceRefresh = false) => {
3871
+ const { summary, cachedAt } = get();
3872
+ if (!forceRefresh && summary && cachedAt && Date.now() - cachedAt < CACHE_TTL_MS) {
3873
+ return;
3874
+ }
2982
3875
  set({ loading: true, error: null });
2983
3876
  try {
2984
3877
  const brewState = useBrewStore.getState();
@@ -2986,8 +3879,9 @@ var useSecurityStore = create7((set) => ({
2986
3879
  await brewState.fetchInstalled();
2987
3880
  }
2988
3881
  const { formulae, casks } = useBrewStore.getState();
2989
- const summary = await runSecurityAudit(formulae, casks);
2990
- set({ summary, loading: false });
3882
+ const isPro = useLicenseStore.getState().isPro();
3883
+ const result = await runSecurityAudit(isPro, formulae, casks);
3884
+ set({ summary: result, loading: false, cachedAt: Date.now() });
2991
3885
  } catch (err) {
2992
3886
  set({ error: err instanceof Error ? err.message : String(err), loading: false });
2993
3887
  }
@@ -2995,13 +3889,13 @@ var useSecurityStore = create7((set) => ({
2995
3889
  }));
2996
3890
 
2997
3891
  // src/views/security-audit.tsx
2998
- import { jsx as jsx23, jsxs as jsxs23 } from "react/jsx-runtime";
3892
+ import { jsx as jsx29, jsxs as jsxs28 } from "react/jsx-runtime";
2999
3893
  var SEVERITY_COLORS = {
3000
- CRITICAL: "#EF4444",
3001
- HIGH: "#EF4444",
3002
- MEDIUM: "#F59E0B",
3003
- LOW: "#6B7280",
3004
- UNKNOWN: "#6B7280"
3894
+ CRITICAL: COLORS.error,
3895
+ HIGH: COLORS.error,
3896
+ MEDIUM: COLORS.warning,
3897
+ LOW: COLORS.textSecondary,
3898
+ UNKNOWN: COLORS.textSecondary
3005
3899
  };
3006
3900
  var SEVERITY_BADGE = {
3007
3901
  CRITICAL: "error",
@@ -3010,8 +3904,11 @@ var SEVERITY_BADGE = {
3010
3904
  LOW: "muted",
3011
3905
  UNKNOWN: "muted"
3012
3906
  };
3907
+ function isNetworkError2(msg) {
3908
+ return /fetch failed|ECONNREFUSED|ENOTFOUND|ETIMEDOUT|network|abort/i.test(msg);
3909
+ }
3013
3910
  function SecurityAuditView() {
3014
- const { summary, loading, error, scan } = useSecurityStore();
3911
+ const { summary, loading, error, scan, cachedAt } = useSecurityStore();
3015
3912
  const [cursor, setCursor] = useState11(0);
3016
3913
  const [expandedPkg, setExpandedPkg] = useState11(null);
3017
3914
  const [confirmUpgrade, setConfirmUpgrade] = useState11(null);
@@ -3023,7 +3920,7 @@ function SecurityAuditView() {
3023
3920
  useInput12((input, key) => {
3024
3921
  if (confirmUpgrade || stream.isRunning) return;
3025
3922
  if (input === "r") {
3026
- void scan();
3923
+ void scan(true);
3027
3924
  return;
3028
3925
  }
3029
3926
  if (input === "u" && results[cursor]) {
@@ -3036,30 +3933,31 @@ function SecurityAuditView() {
3036
3933
  setExpandedPkg(expandedPkg === results[cursor].packageName ? null : results[cursor].packageName);
3037
3934
  }
3038
3935
  });
3039
- if (loading) return /* @__PURE__ */ jsx23(Loading, { message: t("loading_security") });
3040
- if (error) return /* @__PURE__ */ jsx23(ErrorMessage, { message: error });
3041
- return /* @__PURE__ */ jsxs23(Box21, { flexDirection: "column", children: [
3042
- /* @__PURE__ */ jsx23(SectionHeader, { emoji: "\u{1F6E1}\uFE0F", title: t("security_title"), gradient: GRADIENTS.ocean }),
3043
- summary && /* @__PURE__ */ jsxs23(Box21, { gap: 1, marginY: 1, children: [
3044
- /* @__PURE__ */ jsx23(StatCard, { label: t("security_scanned"), value: summary.totalPackages, color: "#06B6D4" }),
3045
- /* @__PURE__ */ jsx23(
3936
+ if (loading) return /* @__PURE__ */ jsx29(Loading, { message: t("loading_security") });
3937
+ if (error) {
3938
+ const displayError = isNetworkError2(error) ? t("security_networkError") : error;
3939
+ return /* @__PURE__ */ jsx29(ErrorMessage, { message: displayError });
3940
+ }
3941
+ const cacheAge = cachedAt ? formatRelativeTime(cachedAt / 1e3) : null;
3942
+ return /* @__PURE__ */ jsxs28(Box27, { flexDirection: "column", children: [
3943
+ /* @__PURE__ */ jsx29(SectionHeader, { emoji: "\u{1F6E1}\uFE0F", title: t("security_title"), gradient: GRADIENTS.ocean }),
3944
+ summary && /* @__PURE__ */ jsxs28(Box27, { gap: 1, marginY: 1, children: [
3945
+ /* @__PURE__ */ jsx29(StatCard, { label: t("security_scanned"), value: summary.totalPackages, color: COLORS.info }),
3946
+ /* @__PURE__ */ jsx29(
3046
3947
  StatCard,
3047
3948
  {
3048
3949
  label: t("security_vulnerable"),
3049
3950
  value: summary.vulnerablePackages,
3050
- color: summary.vulnerablePackages > 0 ? "#EF4444" : "#22C55E"
3951
+ color: summary.vulnerablePackages > 0 ? COLORS.error : COLORS.success
3051
3952
  }
3052
3953
  ),
3053
- summary.criticalCount > 0 && /* @__PURE__ */ jsx23(StatCard, { label: t("security_critical"), value: summary.criticalCount, color: "#EF4444" }),
3054
- summary.highCount > 0 && /* @__PURE__ */ jsx23(StatCard, { label: t("security_high"), value: summary.highCount, color: "#EF4444" }),
3055
- summary.mediumCount > 0 && /* @__PURE__ */ jsx23(StatCard, { label: t("security_medium"), value: summary.mediumCount, color: "#F59E0B" })
3954
+ summary.criticalCount > 0 && /* @__PURE__ */ jsx29(StatCard, { label: t("security_critical"), value: summary.criticalCount, color: COLORS.error }),
3955
+ summary.highCount > 0 && /* @__PURE__ */ jsx29(StatCard, { label: t("security_high"), value: summary.highCount, color: COLORS.error }),
3956
+ summary.mediumCount > 0 && /* @__PURE__ */ jsx29(StatCard, { label: t("security_medium"), value: summary.mediumCount, color: COLORS.warning })
3056
3957
  ] }),
3057
- results.length === 0 && summary && /* @__PURE__ */ jsx23(Box21, { marginTop: 1, children: /* @__PURE__ */ jsx23(Box21, { borderStyle: "round", borderColor: "#22C55E", paddingX: 2, paddingY: 0, children: /* @__PURE__ */ jsxs23(Text23, { color: "#22C55E", bold: true, children: [
3058
- "\u2714",
3059
- " ",
3060
- t("security_noVulns")
3061
- ] }) }) }),
3062
- confirmUpgrade && /* @__PURE__ */ jsx23(Box21, { marginY: 1, children: /* @__PURE__ */ jsx23(
3958
+ cacheAge && /* @__PURE__ */ jsx29(Text28, { color: COLORS.muted, children: t("security_cachedResults", { time: cacheAge }) }),
3959
+ results.length === 0 && summary && /* @__PURE__ */ jsx29(Box27, { marginTop: 1, children: /* @__PURE__ */ jsx29(ResultBanner, { status: "success", message: `\u2714 ${t("security_noVulns")}` }) }),
3960
+ confirmUpgrade && /* @__PURE__ */ jsx29(Box27, { marginY: 1, children: /* @__PURE__ */ jsx29(
3063
3961
  ConfirmDialog,
3064
3962
  {
3065
3963
  message: t("security_confirmUpgrade", { name: confirmUpgrade }),
@@ -3067,40 +3965,39 @@ function SecurityAuditView() {
3067
3965
  const name = confirmUpgrade;
3068
3966
  setConfirmUpgrade(null);
3069
3967
  await stream.run(["upgrade", name]);
3070
- void scan();
3968
+ void scan(true);
3071
3969
  },
3072
3970
  onCancel: () => setConfirmUpgrade(null)
3073
3971
  }
3074
3972
  ) }),
3075
- (stream.isRunning || stream.lines.length > 0) && /* @__PURE__ */ jsx23(Box21, { marginY: 1, children: /* @__PURE__ */ jsx23(ProgressLog, { lines: stream.lines, isRunning: stream.isRunning, title: t("hint_upgrade") }) }),
3076
- results.length > 0 && /* @__PURE__ */ jsxs23(Box21, { flexDirection: "column", marginTop: 1, children: [
3973
+ (stream.isRunning || stream.lines.length > 0) && /* @__PURE__ */ jsx29(Box27, { marginY: 1, children: /* @__PURE__ */ jsx29(ProgressLog, { lines: stream.lines, isRunning: stream.isRunning, title: t("hint_upgrade") }) }),
3974
+ results.length > 0 && /* @__PURE__ */ jsxs28(Box27, { flexDirection: "column", marginTop: 1, children: [
3077
3975
  results.map((pkg, i) => {
3078
3976
  const isCurrent = i === cursor;
3079
3977
  const isExpanded = expandedPkg === pkg.packageName;
3080
- return /* @__PURE__ */ jsxs23(Box21, { flexDirection: "column", children: [
3081
- /* @__PURE__ */ jsxs23(Box21, { gap: 1, children: [
3082
- /* @__PURE__ */ jsx23(Text23, { color: isCurrent ? "#22C55E" : "#9CA3AF", children: isCurrent ? "\u25B6" : " " }),
3083
- /* @__PURE__ */ jsx23(StatusBadge, { label: pkg.maxSeverity, variant: SEVERITY_BADGE[pkg.maxSeverity] }),
3084
- /* @__PURE__ */ jsx23(Text23, { bold: isCurrent, inverse: isCurrent, color: isCurrent ? "#F9FAFB" : "#9CA3AF", children: pkg.packageName }),
3085
- /* @__PURE__ */ jsx23(Text23, { color: "#9CA3AF", children: pkg.installedVersion }),
3086
- /* @__PURE__ */ jsx23(Text23, { color: "#9CA3AF", children: tp("plural_vulns", pkg.vulnerabilities.length) }),
3087
- /* @__PURE__ */ jsx23(Text23, { color: "#9CA3AF", children: isExpanded ? "\u25BC" : "\u25B6" })
3978
+ return /* @__PURE__ */ jsxs28(Box27, { flexDirection: "column", children: [
3979
+ /* @__PURE__ */ jsxs28(SelectableRow, { isCurrent, children: [
3980
+ /* @__PURE__ */ jsx29(StatusBadge, { label: pkg.maxSeverity, variant: SEVERITY_BADGE[pkg.maxSeverity] }),
3981
+ /* @__PURE__ */ jsx29(Text28, { bold: isCurrent, inverse: isCurrent, color: isCurrent ? COLORS.text : COLORS.muted, children: pkg.packageName }),
3982
+ /* @__PURE__ */ jsx29(Text28, { color: COLORS.muted, children: pkg.installedVersion }),
3983
+ /* @__PURE__ */ jsx29(Text28, { color: COLORS.muted, children: tp("plural_vulns", pkg.vulnerabilities.length) }),
3984
+ /* @__PURE__ */ jsx29(Text28, { color: COLORS.muted, children: isExpanded ? "\u25BC" : "\u25B6" })
3088
3985
  ] }),
3089
- isExpanded && /* @__PURE__ */ jsx23(Box21, { flexDirection: "column", paddingLeft: 4, marginBottom: 1, children: pkg.vulnerabilities.map((vuln) => /* @__PURE__ */ jsxs23(Box21, { flexDirection: "column", marginBottom: 1, children: [
3090
- /* @__PURE__ */ jsxs23(Box21, { gap: 1, children: [
3091
- /* @__PURE__ */ jsx23(Text23, { color: SEVERITY_COLORS[vuln.severity], bold: true, children: vuln.id }),
3092
- /* @__PURE__ */ jsxs23(Text23, { color: "#9CA3AF", children: [
3986
+ isExpanded && /* @__PURE__ */ jsx29(Box27, { flexDirection: "column", paddingLeft: 4, marginBottom: 1, children: pkg.vulnerabilities.map((vuln) => /* @__PURE__ */ jsxs28(Box27, { flexDirection: "column", marginBottom: 1, children: [
3987
+ /* @__PURE__ */ jsxs28(Box27, { gap: 1, children: [
3988
+ /* @__PURE__ */ jsx29(Text28, { color: SEVERITY_COLORS[vuln.severity], bold: true, children: vuln.id }),
3989
+ /* @__PURE__ */ jsxs28(Text28, { color: COLORS.muted, children: [
3093
3990
  "[",
3094
3991
  vuln.severity,
3095
3992
  "]"
3096
3993
  ] })
3097
3994
  ] }),
3098
- /* @__PURE__ */ jsx23(Text23, { color: "#9CA3AF", wrap: "wrap", children: vuln.summary }),
3099
- vuln.fixedVersion && /* @__PURE__ */ jsx23(Text23, { color: "#22C55E", children: t("security_fixedIn", { version: vuln.fixedVersion }) })
3995
+ /* @__PURE__ */ jsx29(Text28, { color: COLORS.muted, wrap: "wrap", children: vuln.summary }),
3996
+ vuln.fixedVersion && /* @__PURE__ */ jsx29(Text28, { color: COLORS.success, children: t("security_fixedIn", { version: vuln.fixedVersion }) })
3100
3997
  ] }, vuln.id)) })
3101
3998
  ] }, pkg.packageName);
3102
3999
  }),
3103
- /* @__PURE__ */ jsx23(Box21, { marginTop: 1, children: /* @__PURE__ */ jsxs23(Text23, { color: "#F9FAFB", bold: true, children: [
4000
+ /* @__PURE__ */ jsx29(Box27, { marginTop: 1, children: /* @__PURE__ */ jsxs28(Text28, { color: COLORS.text, bold: true, children: [
3104
4001
  cursor + 1,
3105
4002
  "/",
3106
4003
  results.length
@@ -3111,12 +4008,13 @@ function SecurityAuditView() {
3111
4008
 
3112
4009
  // src/views/account.tsx
3113
4010
  import { useState as useState12 } from "react";
3114
- import { Box as Box22, Text as Text24, useInput as useInput13 } from "ink";
3115
- import { Fragment as Fragment5, jsx as jsx24, jsxs as jsxs24 } from "react/jsx-runtime";
4011
+ import { Box as Box28, Text as Text29, useInput as useInput13 } from "ink";
4012
+ import { Fragment as Fragment5, jsx as jsx30, jsxs as jsxs29 } from "react/jsx-runtime";
3116
4013
  function AccountView() {
3117
4014
  const { status, license, deactivate: deactivate2, degradation } = useLicenseStore();
3118
4015
  const [confirmDeactivate, setConfirmDeactivate] = useState12(false);
3119
4016
  const [deactivating, setDeactivating] = useState12(false);
4017
+ const [deactivateError, setDeactivateError] = useState12(null);
3120
4018
  useInput13((input) => {
3121
4019
  if (confirmDeactivate || deactivating) return;
3122
4020
  if (input === "d" && status === "pro") {
@@ -3127,132 +4025,152 @@ function AccountView() {
3127
4025
  if (key.length <= 8) return key;
3128
4026
  return key.slice(0, 4) + "-****-****-" + key.slice(-4);
3129
4027
  };
3130
- return /* @__PURE__ */ jsxs24(Box22, { flexDirection: "column", children: [
3131
- /* @__PURE__ */ jsx24(SectionHeader, { emoji: "\u{1F464}", title: t("account_title"), gradient: GRADIENTS.gold }),
3132
- confirmDeactivate && /* @__PURE__ */ jsx24(Box22, { marginY: 1, children: /* @__PURE__ */ jsx24(
4028
+ if (status === "validating") {
4029
+ return /* @__PURE__ */ jsx30(Loading, { message: t("account_loading") });
4030
+ }
4031
+ return /* @__PURE__ */ jsxs29(Box28, { flexDirection: "column", children: [
4032
+ /* @__PURE__ */ jsx30(SectionHeader, { emoji: "\u{1F464}", title: t("account_title"), gradient: GRADIENTS.gold }),
4033
+ confirmDeactivate && /* @__PURE__ */ jsx30(Box28, { marginY: 1, children: /* @__PURE__ */ jsx30(
3133
4034
  ConfirmDialog,
3134
4035
  {
3135
4036
  message: t("account_confirmDeactivate"),
3136
4037
  onConfirm: async () => {
3137
4038
  setConfirmDeactivate(false);
3138
4039
  setDeactivating(true);
3139
- await deactivate2();
3140
- setDeactivating(false);
4040
+ setDeactivateError(null);
4041
+ try {
4042
+ await deactivate2();
4043
+ } catch (err) {
4044
+ setDeactivateError(t("deactivate_failed") + ": " + String(err));
4045
+ } finally {
4046
+ setDeactivating(false);
4047
+ }
3141
4048
  },
3142
4049
  onCancel: () => setConfirmDeactivate(false)
3143
4050
  }
3144
4051
  ) }),
3145
- /* @__PURE__ */ jsxs24(Box22, { flexDirection: "column", marginTop: 1, paddingLeft: 2, children: [
3146
- /* @__PURE__ */ jsxs24(Box22, { gap: 1, children: [
3147
- /* @__PURE__ */ jsx24(Text24, { color: "#9CA3AF", children: t("account_statusLabel") }),
3148
- status === "pro" && /* @__PURE__ */ jsx24(Text24, { color: "#22C55E", bold: true, children: t("account_pro") }),
3149
- status === "free" && /* @__PURE__ */ jsx24(Text24, { color: "#9CA3AF", children: t("account_free") }),
3150
- status === "expired" && /* @__PURE__ */ jsx24(Text24, { color: "#EF4444", children: t("account_expired") }),
3151
- status === "validating" && /* @__PURE__ */ jsx24(Text24, { color: "#38BDF8", children: t("account_validating") })
4052
+ /* @__PURE__ */ jsxs29(Box28, { flexDirection: "column", marginTop: 1, paddingLeft: 2, children: [
4053
+ /* @__PURE__ */ jsxs29(Box28, { gap: 1, children: [
4054
+ /* @__PURE__ */ jsx30(Text29, { color: COLORS.muted, children: t("account_statusLabel") }),
4055
+ status === "pro" && /* @__PURE__ */ jsx30(Text29, { color: COLORS.success, bold: true, children: t("account_pro") }),
4056
+ status === "free" && /* @__PURE__ */ jsx30(Text29, { color: COLORS.muted, children: t("account_free") }),
4057
+ status === "expired" && /* @__PURE__ */ jsx30(Text29, { color: COLORS.error, children: t("account_expired") })
3152
4058
  ] }),
3153
- (degradation === "warning" || degradation === "limited") && license && /* @__PURE__ */ jsx24(Box22, { marginTop: 1, borderStyle: "round", borderColor: "#F59E0B", paddingX: 2, paddingY: 0, children: /* @__PURE__ */ jsx24(Text24, { color: "#F59E0B", children: t("license_offlineWarning", {
4059
+ (degradation === "warning" || degradation === "limited") && license && /* @__PURE__ */ jsx30(Box28, { marginTop: 1, borderStyle: "round", borderColor: COLORS.warning, paddingX: 2, paddingY: 0, children: /* @__PURE__ */ jsx30(Text29, { color: COLORS.warning, children: t("license_offlineWarning", {
3154
4060
  days: Math.floor((Date.now() - new Date(license.lastValidatedAt).getTime()) / (24 * 60 * 60 * 1e3))
3155
4061
  }) }) }),
3156
- license && /* @__PURE__ */ jsxs24(Fragment5, { children: [
3157
- /* @__PURE__ */ jsxs24(Box22, { gap: 1, children: [
3158
- /* @__PURE__ */ jsx24(Text24, { color: "#9CA3AF", children: t("account_emailLabel") }),
3159
- /* @__PURE__ */ jsx24(Text24, { children: license.customerEmail })
4062
+ license && /* @__PURE__ */ jsxs29(Fragment5, { children: [
4063
+ /* @__PURE__ */ jsxs29(Box28, { gap: 1, children: [
4064
+ /* @__PURE__ */ jsx30(Text29, { color: COLORS.muted, children: t("account_emailLabel") }),
4065
+ /* @__PURE__ */ jsx30(Text29, { children: license.customerEmail })
3160
4066
  ] }),
3161
- /* @__PURE__ */ jsxs24(Box22, { gap: 1, children: [
3162
- /* @__PURE__ */ jsx24(Text24, { color: "#9CA3AF", children: t("account_nameLabel") }),
3163
- /* @__PURE__ */ jsx24(Text24, { children: license.customerName })
4067
+ /* @__PURE__ */ jsxs29(Box28, { gap: 1, children: [
4068
+ /* @__PURE__ */ jsx30(Text29, { color: COLORS.muted, children: t("account_nameLabel") }),
4069
+ /* @__PURE__ */ jsx30(Text29, { children: license.customerName })
3164
4070
  ] }),
3165
- /* @__PURE__ */ jsxs24(Box22, { gap: 1, children: [
3166
- /* @__PURE__ */ jsx24(Text24, { color: "#9CA3AF", children: t("account_planLabel") }),
3167
- /* @__PURE__ */ jsx24(Text24, { color: "#22C55E", bold: true, children: "Pro" })
4071
+ /* @__PURE__ */ jsxs29(Box28, { gap: 1, children: [
4072
+ /* @__PURE__ */ jsx30(Text29, { color: COLORS.muted, children: t("account_planLabel") }),
4073
+ /* @__PURE__ */ jsx30(Text29, { color: COLORS.success, bold: true, children: "Pro" })
3168
4074
  ] }),
3169
- /* @__PURE__ */ jsxs24(Box22, { gap: 1, children: [
3170
- /* @__PURE__ */ jsx24(Text24, { color: "#9CA3AF", children: t("account_keyLabel") }),
3171
- /* @__PURE__ */ jsx24(Text24, { children: maskKey(license.key) })
4075
+ /* @__PURE__ */ jsxs29(Box28, { gap: 1, children: [
4076
+ /* @__PURE__ */ jsx30(Text29, { color: COLORS.muted, children: t("account_keyLabel") }),
4077
+ /* @__PURE__ */ jsx30(Text29, { children: maskKey(license.key) })
3172
4078
  ] }),
3173
- license.expiresAt && /* @__PURE__ */ jsxs24(Box22, { gap: 1, children: [
3174
- /* @__PURE__ */ jsx24(Text24, { color: "#9CA3AF", children: t("account_expiresLabel") }),
3175
- /* @__PURE__ */ jsx24(Text24, { children: new Date(license.expiresAt).toLocaleDateString() })
4079
+ license.expiresAt && /* @__PURE__ */ jsxs29(Box28, { gap: 1, children: [
4080
+ /* @__PURE__ */ jsx30(Text29, { color: COLORS.muted, children: t("account_expiresLabel") }),
4081
+ /* @__PURE__ */ jsx30(Text29, { children: formatDate(license.expiresAt) })
3176
4082
  ] }),
3177
- /* @__PURE__ */ jsxs24(Box22, { gap: 1, children: [
3178
- /* @__PURE__ */ jsx24(Text24, { color: "#9CA3AF", children: t("account_activatedLabel") }),
3179
- /* @__PURE__ */ jsx24(Text24, { children: new Date(license.activatedAt).toLocaleDateString() })
4083
+ /* @__PURE__ */ jsxs29(Box28, { gap: 1, children: [
4084
+ /* @__PURE__ */ jsx30(Text29, { color: COLORS.muted, children: t("account_activatedLabel") }),
4085
+ /* @__PURE__ */ jsx30(Text29, { children: formatDate(license.activatedAt) })
3180
4086
  ] })
3181
4087
  ] }),
3182
- status === "free" && /* @__PURE__ */ jsxs24(Box22, { flexDirection: "column", marginTop: 2, borderStyle: "round", borderColor: "#FF6B2B", paddingX: 2, paddingY: 1, children: [
3183
- /* @__PURE__ */ jsxs24(Text24, { bold: true, color: "#FF6B2B", children: [
4088
+ status === "free" && /* @__PURE__ */ jsxs29(Box28, { flexDirection: "column", marginTop: 2, borderStyle: "round", borderColor: COLORS.brand, paddingX: 2, paddingY: 1, children: [
4089
+ /* @__PURE__ */ jsxs29(Text29, { bold: true, color: COLORS.brand, children: [
3184
4090
  "\u2B50",
3185
4091
  " ",
3186
4092
  t("account_upgradeTitle")
3187
4093
  ] }),
3188
- /* @__PURE__ */ jsx24(Text24, { children: " " }),
3189
- /* @__PURE__ */ jsx24(Text24, { children: t("account_unlockDesc") }),
3190
- /* @__PURE__ */ jsx24(Text24, { color: "#06B6D4", bold: true, children: t("account_pricing") }),
3191
- /* @__PURE__ */ jsx24(Text24, { children: " " }),
3192
- /* @__PURE__ */ jsxs24(Text24, { color: "#9CA3AF", children: [
4094
+ /* @__PURE__ */ jsx30(Text29, { children: " " }),
4095
+ /* @__PURE__ */ jsx30(Text29, { children: t("account_unlockDesc") }),
4096
+ /* @__PURE__ */ jsx30(Text29, { color: COLORS.info, bold: true, children: t("account_pricing") }),
4097
+ /* @__PURE__ */ jsx30(Text29, { children: " " }),
4098
+ /* @__PURE__ */ jsxs29(Text29, { color: COLORS.muted, children: [
4099
+ t("upgrade_buyAt"),
4100
+ " ",
4101
+ /* @__PURE__ */ jsx30(Text29, { color: COLORS.sky, bold: true, children: t("upgrade_buyUrl") })
4102
+ ] }),
4103
+ /* @__PURE__ */ jsxs29(Text29, { color: COLORS.muted, children: [
3193
4104
  t("account_runActivate"),
3194
4105
  " ",
3195
- /* @__PURE__ */ jsx24(Text24, { color: "#22C55E", bold: true, children: t("account_activateCmd") })
4106
+ /* @__PURE__ */ jsx30(Text29, { color: COLORS.success, bold: true, children: t("account_activateCmd") })
3196
4107
  ] })
3197
4108
  ] }),
3198
- status === "expired" && /* @__PURE__ */ jsx24(Box22, { marginTop: 1, children: /* @__PURE__ */ jsx24(Box22, { borderStyle: "round", borderColor: "#EF4444", paddingX: 2, paddingY: 0, children: /* @__PURE__ */ jsx24(Text24, { color: "#EF4444", children: t("account_licenseExpired") }) }) }),
3199
- deactivating && /* @__PURE__ */ jsx24(Text24, { color: "#38BDF8", children: t("account_deactivating") })
4109
+ status === "expired" && /* @__PURE__ */ jsx30(Box28, { marginTop: 1, children: /* @__PURE__ */ jsx30(Box28, { borderStyle: "round", borderColor: COLORS.error, paddingX: 2, paddingY: 0, children: /* @__PURE__ */ jsx30(Text29, { color: COLORS.error, children: t("account_licenseExpired") }) }) }),
4110
+ deactivating && /* @__PURE__ */ jsx30(Text29, { color: COLORS.sky, children: t("account_deactivating") }),
4111
+ deactivateError && /* @__PURE__ */ jsx30(Text29, { color: COLORS.error, children: deactivateError })
3200
4112
  ] }),
3201
- /* @__PURE__ */ jsx24(Box22, { marginTop: 2, children: /* @__PURE__ */ jsxs24(Text24, { color: "#6B7280", children: [
4113
+ /* @__PURE__ */ jsx30(Box28, { marginTop: 2, children: /* @__PURE__ */ jsxs29(Text29, { color: COLORS.textSecondary, children: [
3202
4114
  status === "pro" ? `d:${t("hint_deactivate")}` : "",
3203
4115
  " ",
3204
- t("app_version", { version: "0.1.0" })
4116
+ t("app_version", { version: "0.3.0" })
3205
4117
  ] }) })
3206
4118
  ] });
3207
4119
  }
3208
4120
 
3209
4121
  // src/app.tsx
3210
- import { jsx as jsx25 } from "react/jsx-runtime";
3211
- function App() {
3212
- const { exit } = useApp();
3213
- const currentView = useNavigationStore((s) => s.currentView);
3214
- const isPro = useLicenseStore((s) => s.isPro);
4122
+ import { jsx as jsx31, jsxs as jsxs30 } from "react/jsx-runtime";
4123
+ function LicenseInitializer() {
3215
4124
  const initLicense = useLicenseStore((s) => s.initialize);
3216
4125
  useEffect15(() => {
3217
4126
  initLicense();
3218
4127
  }, []);
4128
+ return null;
4129
+ }
4130
+ function ViewRouter({ currentView }) {
4131
+ const isPro = useLicenseStore((s) => s.isPro);
4132
+ if (isProView(currentView) && !isPro()) {
4133
+ return /* @__PURE__ */ jsx31(UpgradePrompt, { viewId: currentView });
4134
+ }
4135
+ switch (currentView) {
4136
+ case "dashboard":
4137
+ return /* @__PURE__ */ jsx31(DashboardView, {});
4138
+ case "installed":
4139
+ return /* @__PURE__ */ jsx31(InstalledView, {});
4140
+ case "search":
4141
+ return /* @__PURE__ */ jsx31(SearchView, {});
4142
+ case "outdated":
4143
+ return /* @__PURE__ */ jsx31(OutdatedView, {});
4144
+ case "package-info":
4145
+ return /* @__PURE__ */ jsx31(PackageInfoView, {});
4146
+ case "services":
4147
+ return /* @__PURE__ */ jsx31(ServicesView, {});
4148
+ case "doctor":
4149
+ return /* @__PURE__ */ jsx31(DoctorView, {});
4150
+ case "profiles":
4151
+ return /* @__PURE__ */ jsx31(ProfilesView, {});
4152
+ case "smart-cleanup":
4153
+ return /* @__PURE__ */ jsx31(SmartCleanupView, {});
4154
+ case "history":
4155
+ return /* @__PURE__ */ jsx31(HistoryView, {});
4156
+ case "security-audit":
4157
+ return /* @__PURE__ */ jsx31(SecurityAuditView, {});
4158
+ case "account":
4159
+ return /* @__PURE__ */ jsx31(AccountView, {});
4160
+ }
4161
+ }
4162
+ function App() {
4163
+ const { exit } = useApp();
4164
+ const currentView = useNavigationStore((s) => s.currentView);
3219
4165
  useGlobalKeyboard({ onQuit: exit });
3220
- const renderView = () => {
3221
- if (isProView(currentView) && !isPro()) {
3222
- return /* @__PURE__ */ jsx25(UpgradePrompt, { viewId: currentView });
3223
- }
3224
- switch (currentView) {
3225
- case "dashboard":
3226
- return /* @__PURE__ */ jsx25(DashboardView, {});
3227
- case "installed":
3228
- return /* @__PURE__ */ jsx25(InstalledView, {});
3229
- case "search":
3230
- return /* @__PURE__ */ jsx25(SearchView, {});
3231
- case "outdated":
3232
- return /* @__PURE__ */ jsx25(OutdatedView, {});
3233
- case "package-info":
3234
- return /* @__PURE__ */ jsx25(PackageInfoView, {});
3235
- case "services":
3236
- return /* @__PURE__ */ jsx25(ServicesView, {});
3237
- case "doctor":
3238
- return /* @__PURE__ */ jsx25(DoctorView, {});
3239
- case "profiles":
3240
- return /* @__PURE__ */ jsx25(ProfilesView, {});
3241
- case "smart-cleanup":
3242
- return /* @__PURE__ */ jsx25(SmartCleanupView, {});
3243
- case "history":
3244
- return /* @__PURE__ */ jsx25(HistoryView, {});
3245
- case "security-audit":
3246
- return /* @__PURE__ */ jsx25(SecurityAuditView, {});
3247
- case "account":
3248
- return /* @__PURE__ */ jsx25(AccountView, {});
3249
- }
3250
- };
3251
- return /* @__PURE__ */ jsx25(AppLayout, { children: renderView() });
4166
+ return /* @__PURE__ */ jsxs30(AppLayout, { children: [
4167
+ /* @__PURE__ */ jsx31(LicenseInitializer, {}),
4168
+ /* @__PURE__ */ jsx31(ViewRouter, { currentView })
4169
+ ] });
3252
4170
  }
3253
4171
 
3254
4172
  // src/index.tsx
3255
- import { jsx as jsx26 } from "react/jsx-runtime";
4173
+ import { jsx as jsx32 } from "react/jsx-runtime";
3256
4174
  var [, , command, arg] = process.argv;
3257
4175
  async function runCli() {
3258
4176
  await ensureDataDirs();
@@ -3267,7 +4185,7 @@ async function runCli() {
3267
4185
  console.log(t("cli_activated", { email: license.customerEmail }));
3268
4186
  console.log(t("cli_planPro"));
3269
4187
  if (license.expiresAt) {
3270
- console.log(t("cli_expires", { date: new Date(license.expiresAt).toLocaleDateString() }));
4188
+ console.log(t("cli_expires", { date: formatDate(license.expiresAt) }));
3271
4189
  }
3272
4190
  } catch (err) {
3273
4191
  console.error(t("cli_activationFailed", { error: err instanceof Error ? err.message : String(err) }));
@@ -3288,29 +4206,69 @@ async function runCli() {
3288
4206
  console.log(t("cli_deactivateCancelled"));
3289
4207
  return;
3290
4208
  }
3291
- await deactivate(license);
4209
+ const { remoteSuccess } = await deactivate(license);
3292
4210
  console.log(t("cli_deactivated"));
4211
+ if (!remoteSuccess) {
4212
+ console.warn(t("cli_deactivateRemoteFailed"));
4213
+ }
3293
4214
  return;
3294
4215
  }
3295
- if (command === "status") {
4216
+ if (command === "revalidate") {
3296
4217
  const license = await loadLicense();
3297
4218
  if (!license) {
4219
+ console.log(t("cli_noLicense"));
4220
+ process.exit(1);
4221
+ }
4222
+ const result = await revalidate(license);
4223
+ if (result === "expired") {
4224
+ console.error(t("cli_revalidateFailed"));
4225
+ process.exit(1);
4226
+ }
4227
+ const updated = await loadLicense();
4228
+ if (result === "grace") {
4229
+ console.warn(t("cli_revalidateGrace"));
4230
+ } else {
4231
+ console.log(t("cli_revalidated"));
4232
+ }
4233
+ if (updated?.expiresAt) {
4234
+ console.log(t("cli_expires", { date: formatDate(updated.expiresAt) }));
4235
+ }
4236
+ return;
4237
+ }
4238
+ if (command === "status") {
4239
+ await useLicenseStore.getState().initialize();
4240
+ const { status, license, degradation } = useLicenseStore.getState();
4241
+ if (status === "free") {
3298
4242
  console.log(t("cli_planFree"));
3299
4243
  console.log(t("cli_upgradeHint"));
3300
4244
  } else {
3301
- console.log(t("cli_planPro"));
3302
- console.log(t("cli_email", { email: license.customerEmail }));
3303
- console.log(t("cli_status", { status: license.status }));
3304
- if (license.expiresAt) {
3305
- console.log(t("cli_expires", { date: new Date(license.expiresAt).toLocaleDateString() }));
4245
+ const planLabel = status === "expired" ? t("cli_planExpired") : t("cli_planPro");
4246
+ console.log(planLabel);
4247
+ if (license) {
4248
+ console.log(t("cli_email", { email: license.customerEmail }));
4249
+ }
4250
+ const statusText = status === "pro" ? degradation === "none" ? license?.status ?? "active" : degradation : status;
4251
+ console.log(t("cli_status", { status: statusText }));
4252
+ if (license?.expiresAt) {
4253
+ console.log(t("cli_expires", { date: formatDate(license.expiresAt) }));
4254
+ }
4255
+ if (status === "expired") {
4256
+ console.log(t("cli_revalidateHint"));
4257
+ }
4258
+ if (status === "pro" && degradation !== "none" && license) {
4259
+ const days = Math.floor((Date.now() - new Date(license.lastValidatedAt).getTime()) / (24 * 60 * 60 * 1e3));
4260
+ console.log(t("license_offlineWarning", { days }));
4261
+ console.log(t("cli_revalidateHint"));
3306
4262
  }
3307
4263
  }
3308
4264
  return;
3309
4265
  }
3310
4266
  if (command === "install-brewbar") {
3311
- const { installBrewBar } = await import("./brewbar-installer-CPCOE3MI.js");
4267
+ await useLicenseStore.getState().initialize();
4268
+ const isPro = useLicenseStore.getState().isPro();
4269
+ const { installBrewBar } = await import("./brewbar-installer-H5MLNNTD.js");
3312
4270
  try {
3313
- await installBrewBar(arg === "--force");
4271
+ await installBrewBar(isPro, arg === "--force");
3314
4272
  console.log(t("cli_brewbarInstalled"));
3315
4273
  } catch (err) {
3316
4274
  console.error(err instanceof Error ? err.message : String(err));
@@ -3319,7 +4277,7 @@ async function runCli() {
3319
4277
  return;
3320
4278
  }
3321
4279
  if (command === "uninstall-brewbar") {
3322
- const { uninstallBrewBar } = await import("./brewbar-installer-CPCOE3MI.js");
4280
+ const { uninstallBrewBar } = await import("./brewbar-installer-H5MLNNTD.js");
3323
4281
  try {
3324
4282
  await uninstallBrewBar();
3325
4283
  console.log(t("cli_brewbarUninstalled"));
@@ -3329,7 +4287,19 @@ async function runCli() {
3329
4287
  }
3330
4288
  return;
3331
4289
  }
3332
- render(/* @__PURE__ */ jsx26(App, {}));
4290
+ if (command === "delete-account") {
4291
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
4292
+ const answer = await rl.question(t("delete_account_confirm") + " (y/N): ");
4293
+ rl.close();
4294
+ if (answer.toLowerCase() !== "y" && answer.toLowerCase() !== "s") {
4295
+ console.log(t("cli_deactivateCancelled"));
4296
+ return;
4297
+ }
4298
+ await rm3(DATA_DIR, { recursive: true, force: true });
4299
+ console.log(t("delete_account_success"));
4300
+ return;
4301
+ }
4302
+ render(/* @__PURE__ */ jsx32(App, {}));
3333
4303
  }
3334
4304
  runCli().catch((err) => {
3335
4305
  console.error(err instanceof Error ? err.message : String(err));