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/README.md +7 -4
- package/build/brewbar-installer-H5MLNNTD.js +118 -0
- package/build/brewbar-installer-H5MLNNTD.js.map +1 -0
- package/build/chunk-65YZJX2E.js +103 -0
- package/build/chunk-65YZJX2E.js.map +1 -0
- package/build/{chunk-P6PTN4HR.js → chunk-PTLSNG2N.js} +125 -501
- package/build/chunk-PTLSNG2N.js.map +1 -0
- package/build/history-logger-2PGYSPFL.js +13 -0
- package/build/index.js +1784 -814
- package/build/index.js.map +1 -1
- package/package.json +2 -2
- package/build/brewbar-installer-CPCOE3MI.js +0 -64
- package/build/brewbar-installer-CPCOE3MI.js.map +0 -1
- package/build/chunk-3BK3B53S.js +0 -55
- package/build/chunk-3BK3B53S.js.map +0 -1
- package/build/chunk-P6PTN4HR.js.map +0 -1
- package/build/history-logger-LQT622M2.js +0 -12
- /package/build/{history-logger-LQT622M2.js.map → history-logger-2PGYSPFL.js.map} +0 -0
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-
|
|
10
|
+
} from "./chunk-65YZJX2E.js";
|
|
6
11
|
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
61
|
-
viewHistory: [...viewHistory.slice(-19), view]
|
|
62
|
+
viewHistory: [...viewHistory.slice(-19), currentView]
|
|
62
63
|
});
|
|
63
64
|
},
|
|
64
65
|
goBack: () => {
|
|
65
|
-
const {
|
|
66
|
-
if (
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
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
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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:
|
|
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(
|
|
212
|
-
i > 0 && /* @__PURE__ */ jsxs(Text2, { color:
|
|
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 ?
|
|
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:
|
|
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
|
|
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:
|
|
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:
|
|
257
|
-
/* @__PURE__ */ jsx3(Text3, { color:
|
|
258
|
-
/* @__PURE__ */ jsx3(Text3, { color:
|
|
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:
|
|
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(
|
|
269
|
-
i > 0 && /* @__PURE__ */ jsxs2(Text3, { color:
|
|
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:
|
|
301
|
+
/* @__PURE__ */ jsxs2(Text3, { color: COLORS.border, children: [
|
|
278
302
|
" ",
|
|
279
303
|
"\u2502",
|
|
280
304
|
" "
|
|
281
305
|
] }),
|
|
282
|
-
/* @__PURE__ */ jsx3(Text3, { color:
|
|
283
|
-
/* @__PURE__ */ jsx3(Text3, { color:
|
|
284
|
-
/* @__PURE__ */ jsxs2(Text3, { color:
|
|
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
|
|
308
|
-
var useModalStore =
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
405
|
-
/* @__PURE__ */ jsxs4(Text4, { color:
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
757
|
-
|
|
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
|
-
|
|
775
|
-
|
|
776
|
-
}
|
|
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 =
|
|
779
|
-
|
|
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
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
858
|
-
warning: { icon: "\u25CF", color:
|
|
859
|
-
error: { icon: "\u2718", color:
|
|
860
|
-
info: { icon: "\u25C6", color:
|
|
861
|
-
muted: { icon: "\u25CB", color:
|
|
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 =
|
|
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:
|
|
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__ */
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
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
|
-
|
|
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 =
|
|
1500
|
+
const errorServiceList = useMemo2(
|
|
910
1501
|
() => services.filter((s) => s.status === "error"),
|
|
911
1502
|
[services]
|
|
912
1503
|
);
|
|
913
|
-
const runningServices =
|
|
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:
|
|
924
|
-
/* @__PURE__ */ jsx10(StatCard, { label: t("dashboard_casks"), value: casks.length, color:
|
|
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:
|
|
930
|
-
color:
|
|
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:
|
|
938
|
-
color: errorServices > 0 ?
|
|
1537
|
+
value: servicesValue,
|
|
1538
|
+
color: errors.services || errorServices > 0 ? COLORS.error : COLORS.success
|
|
939
1539
|
}
|
|
940
1540
|
)
|
|
941
1541
|
] }),
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
/* @__PURE__ */
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
985
|
-
import { Box as
|
|
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
|
|
1019
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
1123
|
-
/* @__PURE__ */ jsx12(Text12, { bold: true, color:
|
|
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:
|
|
1722
|
+
/* @__PURE__ */ jsx12(Text12, { color: COLORS.success, children: t("confirm_yes") }),
|
|
1126
1723
|
/* @__PURE__ */ jsx12(Text12, { children: " / " }),
|
|
1127
|
-
/* @__PURE__ */ jsx12(Text12, { color:
|
|
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:
|
|
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:
|
|
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:
|
|
1147
|
-
lines.length === 0 && !isRunning && /* @__PURE__ */ jsx13(Text13, { color:
|
|
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/
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
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
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
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
|
|
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 =
|
|
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__ */
|
|
1241
|
-
if (errors.installed) return /* @__PURE__ */
|
|
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__ */
|
|
1244
|
-
/* @__PURE__ */
|
|
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
|
-
|
|
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 =
|
|
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__ */
|
|
1259
|
-
/* @__PURE__ */
|
|
1260
|
-
/* @__PURE__ */
|
|
1261
|
-
|
|
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" ?
|
|
1889
|
+
borderColor: tab === "formulae" ? COLORS.info : COLORS.textSecondary,
|
|
1265
1890
|
paddingX: 1,
|
|
1266
|
-
children: /* @__PURE__ */
|
|
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__ */
|
|
1274
|
-
|
|
1898
|
+
/* @__PURE__ */ jsx16(
|
|
1899
|
+
Box14,
|
|
1275
1900
|
{
|
|
1276
1901
|
borderStyle: "round",
|
|
1277
|
-
borderColor: tab === "casks" ?
|
|
1902
|
+
borderColor: tab === "casks" ? COLORS.purple : COLORS.textSecondary,
|
|
1278
1903
|
paddingX: 1,
|
|
1279
|
-
children: /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
1302
|
-
/* @__PURE__ */
|
|
1303
|
-
/* @__PURE__ */
|
|
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
|
-
"
|
|
1930
|
+
t("installed_col_package").padEnd(nameWidth)
|
|
1306
1931
|
] }),
|
|
1307
|
-
/* @__PURE__ */
|
|
1308
|
-
/* @__PURE__ */
|
|
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__ */
|
|
1311
|
-
visible.length === 0 && /* @__PURE__ */
|
|
1312
|
-
start > 0 && /* @__PURE__ */
|
|
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__ */
|
|
1320
|
-
/* @__PURE__ */
|
|
1321
|
-
/* @__PURE__ */
|
|
1322
|
-
/* @__PURE__ */
|
|
1323
|
-
item.
|
|
1324
|
-
item.
|
|
1325
|
-
item.
|
|
1326
|
-
item.installedAsDependency && /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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
|
|
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
|
|
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)
|
|
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
|
-
|
|
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
|
|
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 &&
|
|
1397
|
-
selectPackage(
|
|
2035
|
+
if (key.return && allVisible[cursor]) {
|
|
2036
|
+
selectPackage(allVisible[cursor]);
|
|
1398
2037
|
navigate("package-info");
|
|
1399
2038
|
return;
|
|
1400
2039
|
}
|
|
1401
|
-
if (input === "i" &&
|
|
1402
|
-
setConfirmInstall(
|
|
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,
|
|
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__ */
|
|
1416
|
-
/* @__PURE__ */
|
|
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__ */
|
|
2063
|
+
stream.isRunning && /* @__PURE__ */ jsxs16(Text17, { color: COLORS.textSecondary, children: [
|
|
1425
2064
|
"esc:",
|
|
1426
2065
|
t("hint_cancel")
|
|
1427
2066
|
] }),
|
|
1428
|
-
!stream.isRunning && /* @__PURE__ */
|
|
1429
|
-
/* @__PURE__ */
|
|
1430
|
-
|
|
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("
|
|
2077
|
+
t("hint_clear")
|
|
1433
2078
|
] })
|
|
1434
2079
|
] })
|
|
1435
2080
|
] });
|
|
1436
2081
|
}
|
|
1437
|
-
return /* @__PURE__ */
|
|
1438
|
-
/* @__PURE__ */
|
|
1439
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
2096
|
+
) : /* @__PURE__ */ jsxs16(Text17, { children: [
|
|
1452
2097
|
t("search_resultsFor"),
|
|
1453
2098
|
' "',
|
|
1454
|
-
/* @__PURE__ */
|
|
2099
|
+
/* @__PURE__ */ jsx17(Text17, { bold: true, color: COLORS.text, children: query }),
|
|
1455
2100
|
'" ',
|
|
1456
|
-
/* @__PURE__ */
|
|
2101
|
+
/* @__PURE__ */ jsx17(Text17, { color: COLORS.textSecondary, children: t("search_escToClear") })
|
|
1457
2102
|
] })
|
|
1458
2103
|
] }),
|
|
1459
|
-
searching && /* @__PURE__ */
|
|
1460
|
-
|
|
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__ */
|
|
1474
|
-
|
|
1475
|
-
/* @__PURE__ */
|
|
1476
|
-
|
|
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__ */
|
|
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 >
|
|
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 -
|
|
2128
|
+
t("scroll_moreBelow", { count: results.formulae.length - MAX_VISIBLE })
|
|
1486
2129
|
] })
|
|
1487
2130
|
] }),
|
|
1488
|
-
|
|
1489
|
-
/* @__PURE__ */
|
|
1490
|
-
|
|
1491
|
-
const idx =
|
|
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__ */
|
|
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 >
|
|
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 -
|
|
2140
|
+
t("scroll_moreBelow", { count: results.casks.length - MAX_VISIBLE })
|
|
1501
2141
|
] })
|
|
1502
2142
|
] }),
|
|
1503
|
-
|
|
1504
|
-
/* @__PURE__ */
|
|
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
|
|
1512
|
-
import { jsx as
|
|
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
|
|
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
|
-
|
|
1552
|
-
|
|
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__ */
|
|
1555
|
-
/* @__PURE__ */
|
|
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__ */
|
|
2218
|
+
stream.isRunning && /* @__PURE__ */ jsxs17(Text18, { color: COLORS.textSecondary, children: [
|
|
1564
2219
|
"esc:",
|
|
1565
2220
|
t("hint_cancel")
|
|
1566
2221
|
] }),
|
|
1567
|
-
!stream.isRunning && /* @__PURE__ */
|
|
1568
|
-
/* @__PURE__ */
|
|
1569
|
-
/* @__PURE__ */
|
|
1570
|
-
/* @__PURE__ */
|
|
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__ */
|
|
2230
|
+
/* @__PURE__ */ jsxs17(Text18, { color: COLORS.textSecondary, children: [
|
|
1576
2231
|
"r:",
|
|
1577
2232
|
t("hint_refresh"),
|
|
1578
2233
|
" esc:",
|
|
1579
|
-
t("
|
|
2234
|
+
t("hint_clear")
|
|
1580
2235
|
] })
|
|
1581
2236
|
] })
|
|
1582
2237
|
] });
|
|
1583
2238
|
}
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
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" ?
|
|
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__ */
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
const
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
/* @__PURE__ */
|
|
1613
|
-
/* @__PURE__ */
|
|
1614
|
-
pkg.pinned && /* @__PURE__ */
|
|
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
|
-
|
|
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
|
|
1629
|
-
import { Fragment as Fragment4, jsx as
|
|
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
|
-
|
|
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
|
-
}
|
|
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
|
-
|
|
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__ */
|
|
2399
|
+
return /* @__PURE__ */ jsx19(Text19, { color: COLORS.textSecondary, italic: true, children: t("pkgInfo_noPackage") });
|
|
1700
2400
|
}
|
|
1701
|
-
if (loading) return /* @__PURE__ */
|
|
1702
|
-
if (error) return /* @__PURE__ */
|
|
1703
|
-
if (!formula) return /* @__PURE__ */
|
|
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__ */
|
|
1706
|
-
/* @__PURE__ */
|
|
1707
|
-
stream.isRunning && /* @__PURE__ */
|
|
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__ */
|
|
1712
|
-
/* @__PURE__ */
|
|
1713
|
-
/* @__PURE__ */
|
|
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__ */
|
|
1723
|
-
confirmAction && /* @__PURE__ */
|
|
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__ */
|
|
1740
|
-
/* @__PURE__ */
|
|
1741
|
-
/* @__PURE__ */
|
|
1742
|
-
isInstalled && /* @__PURE__ */
|
|
1743
|
-
formula.outdated && /* @__PURE__ */
|
|
1744
|
-
formula.pinned && /* @__PURE__ */
|
|
1745
|
-
formula.keg_only && /* @__PURE__ */
|
|
1746
|
-
formula.deprecated && /* @__PURE__ */
|
|
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__ */
|
|
1749
|
-
/* @__PURE__ */
|
|
1750
|
-
/* @__PURE__ */
|
|
1751
|
-
/* @__PURE__ */
|
|
1752
|
-
/* @__PURE__ */
|
|
1753
|
-
/* @__PURE__ */
|
|
1754
|
-
/* @__PURE__ */
|
|
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__ */
|
|
1759
|
-
/* @__PURE__ */
|
|
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__ */
|
|
1764
|
-
/* @__PURE__ */
|
|
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__ */
|
|
1769
|
-
/* @__PURE__ */
|
|
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__ */
|
|
1774
|
-
/* @__PURE__ */
|
|
1775
|
-
/* @__PURE__ */
|
|
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__ */
|
|
1783
|
-
/* @__PURE__ */
|
|
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__ */
|
|
1788
|
-
/* @__PURE__ */
|
|
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__ */
|
|
1796
|
-
/* @__PURE__ */
|
|
1797
|
-
/* @__PURE__ */
|
|
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__ */
|
|
1800
|
-
/* @__PURE__ */
|
|
1801
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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
|
|
1815
|
-
import { jsx as
|
|
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).
|
|
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__ */
|
|
1853
|
-
if (errors.services) return /* @__PURE__ */
|
|
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__ */
|
|
1856
|
-
/* @__PURE__ */
|
|
1857
|
-
/* @__PURE__ */
|
|
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
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
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).
|
|
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__ */
|
|
1878
|
-
/* @__PURE__ */
|
|
1879
|
-
/* @__PURE__ */
|
|
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(
|
|
2600
|
+
t("services_name").padEnd(svcNameWidth)
|
|
1882
2601
|
] }),
|
|
1883
|
-
/* @__PURE__ */
|
|
1884
|
-
/* @__PURE__ */
|
|
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
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
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__ */
|
|
1898
|
-
/* @__PURE__ */
|
|
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
|
|
1909
|
-
import { jsx as
|
|
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__ */
|
|
1919
|
-
if (errors.doctor) return /* @__PURE__ */
|
|
1920
|
-
return /* @__PURE__ */
|
|
1921
|
-
/* @__PURE__ */
|
|
1922
|
-
/* @__PURE__ */
|
|
1923
|
-
doctorClean && /* @__PURE__ */
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
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__ */
|
|
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
|
|
1937
|
-
import { Box as
|
|
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
|
|
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
|
-
|
|
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
|
|
2705
|
+
return join2(PROFILES_DIR, `${basename(name)}.json`);
|
|
1970
2706
|
}
|
|
1971
|
-
async function listProfiles() {
|
|
1972
|
-
|
|
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
|
-
|
|
1983
|
-
const raw = await
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
2743
|
+
async function deleteProfile(isPro, name) {
|
|
2744
|
+
proCheck(isPro);
|
|
2003
2745
|
try {
|
|
2004
|
-
await
|
|
2746
|
+
await rm2(profilePath(name));
|
|
2005
2747
|
} catch {
|
|
2006
2748
|
}
|
|
2007
2749
|
}
|
|
2008
|
-
async function exportCurrentSetup(name, description) {
|
|
2009
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
2047
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2100
|
-
|
|
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
|
|
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 =
|
|
2139
|
-
const mountedRef =
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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__ */
|
|
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__ */
|
|
2219
|
-
/* @__PURE__ */
|
|
2220
|
-
!importRunning && /* @__PURE__ */
|
|
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__ */
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
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__ */
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
loadError
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
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__ */
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
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__ */
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
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__ */
|
|
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__ */
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
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
|
|
2369
|
-
import { Box as
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
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__ */
|
|
2539
|
-
if (error) return /* @__PURE__ */
|
|
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__ */
|
|
2542
|
-
/* @__PURE__ */
|
|
2543
|
-
stream.isRunning && /* @__PURE__ */
|
|
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__ */
|
|
2548
|
-
|
|
2549
|
-
" ",
|
|
2550
|
-
|
|
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__ */
|
|
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__ */
|
|
2581
|
-
/* @__PURE__ */
|
|
2582
|
-
summary && /* @__PURE__ */
|
|
2583
|
-
/* @__PURE__ */
|
|
2584
|
-
/* @__PURE__ */
|
|
2585
|
-
/* @__PURE__ */
|
|
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__ */
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
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__ */
|
|
2611
|
-
/* @__PURE__ */
|
|
2612
|
-
/* @__PURE__ */
|
|
2613
|
-
/* @__PURE__ */
|
|
2614
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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
|
|
2633
|
-
import { Box as
|
|
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
|
|
2637
|
-
var useHistoryStore =
|
|
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
|
|
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
|
-
|
|
2652
|
-
|
|
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
|
-
|
|
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
|
|
3514
|
+
import { jsx as jsx28, jsxs as jsxs27 } from "react/jsx-runtime";
|
|
2663
3515
|
var ACTION_ICONS = {
|
|
2664
|
-
install: { icon: "+", color:
|
|
2665
|
-
uninstall: { icon: "-", color:
|
|
2666
|
-
upgrade: { icon: "\u2191", color:
|
|
2667
|
-
"upgrade-all": { icon: "\u21C8", color:
|
|
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 =
|
|
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__ */
|
|
2741
|
-
if (error) return /* @__PURE__ */
|
|
2742
|
-
const
|
|
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__ */
|
|
2746
|
-
/* @__PURE__ */
|
|
2747
|
-
/* @__PURE__ */
|
|
2748
|
-
/* @__PURE__ */
|
|
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__ */
|
|
2751
|
-
confirmClear && /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
2791
|
-
filtered.length === 0 && !confirmClear && /* @__PURE__ */
|
|
2792
|
-
filtered.length > 0 && !confirmClear && /* @__PURE__ */
|
|
2793
|
-
start > 0 && /* @__PURE__ */
|
|
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__ */
|
|
2803
|
-
/* @__PURE__ */
|
|
2804
|
-
/* @__PURE__ */
|
|
2805
|
-
/* @__PURE__ */
|
|
2806
|
-
/* @__PURE__ */
|
|
2807
|
-
|
|
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__ */
|
|
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__ */
|
|
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
|
|
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
|
|
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
|
|
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 <
|
|
2906
|
-
const batch =
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
2990
|
-
|
|
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
|
|
3892
|
+
import { jsx as jsx29, jsxs as jsxs28 } from "react/jsx-runtime";
|
|
2999
3893
|
var SEVERITY_COLORS = {
|
|
3000
|
-
CRITICAL:
|
|
3001
|
-
HIGH:
|
|
3002
|
-
MEDIUM:
|
|
3003
|
-
LOW:
|
|
3004
|
-
UNKNOWN:
|
|
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__ */
|
|
3040
|
-
if (error)
|
|
3041
|
-
|
|
3042
|
-
/* @__PURE__ */
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
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 ?
|
|
3951
|
+
color: summary.vulnerablePackages > 0 ? COLORS.error : COLORS.success
|
|
3051
3952
|
}
|
|
3052
3953
|
),
|
|
3053
|
-
summary.criticalCount > 0 && /* @__PURE__ */
|
|
3054
|
-
summary.highCount > 0 && /* @__PURE__ */
|
|
3055
|
-
summary.mediumCount > 0 && /* @__PURE__ */
|
|
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
|
-
|
|
3058
|
-
|
|
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__ */
|
|
3076
|
-
results.length > 0 && /* @__PURE__ */
|
|
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__ */
|
|
3081
|
-
/* @__PURE__ */
|
|
3082
|
-
/* @__PURE__ */
|
|
3083
|
-
/* @__PURE__ */
|
|
3084
|
-
/* @__PURE__ */
|
|
3085
|
-
/* @__PURE__ */
|
|
3086
|
-
/* @__PURE__ */
|
|
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__ */
|
|
3090
|
-
/* @__PURE__ */
|
|
3091
|
-
/* @__PURE__ */
|
|
3092
|
-
/* @__PURE__ */
|
|
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__ */
|
|
3099
|
-
vuln.fixedVersion && /* @__PURE__ */
|
|
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__ */
|
|
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
|
|
3115
|
-
import { Fragment as Fragment5, jsx as
|
|
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
|
-
|
|
3131
|
-
/* @__PURE__ */
|
|
3132
|
-
|
|
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
|
-
|
|
3140
|
-
|
|
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__ */
|
|
3146
|
-
/* @__PURE__ */
|
|
3147
|
-
/* @__PURE__ */
|
|
3148
|
-
status === "pro" && /* @__PURE__ */
|
|
3149
|
-
status === "free" && /* @__PURE__ */
|
|
3150
|
-
status === "expired" && /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
3157
|
-
/* @__PURE__ */
|
|
3158
|
-
/* @__PURE__ */
|
|
3159
|
-
/* @__PURE__ */
|
|
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__ */
|
|
3162
|
-
/* @__PURE__ */
|
|
3163
|
-
/* @__PURE__ */
|
|
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__ */
|
|
3166
|
-
/* @__PURE__ */
|
|
3167
|
-
/* @__PURE__ */
|
|
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__ */
|
|
3170
|
-
/* @__PURE__ */
|
|
3171
|
-
/* @__PURE__ */
|
|
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__ */
|
|
3174
|
-
/* @__PURE__ */
|
|
3175
|
-
/* @__PURE__ */
|
|
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__ */
|
|
3178
|
-
/* @__PURE__ */
|
|
3179
|
-
/* @__PURE__ */
|
|
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__ */
|
|
3183
|
-
/* @__PURE__ */
|
|
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__ */
|
|
3189
|
-
/* @__PURE__ */
|
|
3190
|
-
/* @__PURE__ */
|
|
3191
|
-
/* @__PURE__ */
|
|
3192
|
-
/* @__PURE__ */
|
|
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__ */
|
|
4106
|
+
/* @__PURE__ */ jsx30(Text29, { color: COLORS.success, bold: true, children: t("account_activateCmd") })
|
|
3196
4107
|
] })
|
|
3197
4108
|
] }),
|
|
3198
|
-
status === "expired" && /* @__PURE__ */
|
|
3199
|
-
deactivating && /* @__PURE__ */
|
|
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__ */
|
|
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.
|
|
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
|
|
3211
|
-
function
|
|
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
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
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
|
|
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:
|
|
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 === "
|
|
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
|
-
|
|
3302
|
-
console.log(
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
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
|
-
|
|
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-
|
|
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
|
-
|
|
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));
|