brew-tui 0.2.0 → 0.3.1
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 +141 -50
- package/build/{brewbar-installer-4Z2WE57I.js → brewbar-installer-H5MLNNTD.js} +52 -20
- 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-KXDTKY3E.js → chunk-PTLSNG2N.js} +107 -521
- package/build/chunk-PTLSNG2N.js.map +1 -0
- package/build/{history-logger-65UF2R6F.js → history-logger-2PGYSPFL.js} +2 -2
- package/build/history-logger-2PGYSPFL.js.map +1 -0
- package/build/index.js +1647 -769
- package/build/index.js.map +1 -0
- package/package.json +2 -2
- package/build/chunk-UBHTQL7T.js +0 -76
package/build/index.js
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
import {
|
|
2
|
+
DATA_DIR,
|
|
3
|
+
LICENSE_PATH,
|
|
4
|
+
PROFILES_DIR,
|
|
2
5
|
appendEntry,
|
|
3
6
|
clearHistory,
|
|
4
7
|
detectAction,
|
|
8
|
+
ensureDataDirs,
|
|
5
9
|
loadHistory
|
|
6
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-65YZJX2E.js";
|
|
7
11
|
import {
|
|
8
|
-
PROFILES_DIR,
|
|
9
|
-
activate,
|
|
10
|
-
deactivate,
|
|
11
|
-
ensureDataDirs,
|
|
12
12
|
fetchWithTimeout,
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
getLocale,
|
|
14
|
+
logger,
|
|
15
15
|
t,
|
|
16
16
|
tp,
|
|
17
|
-
useLicenseStore,
|
|
18
17
|
useLocaleStore
|
|
19
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-PTLSNG2N.js";
|
|
20
19
|
|
|
21
20
|
// src/index.tsx
|
|
22
21
|
import { createInterface } from "readline/promises";
|
|
22
|
+
import { rm as rm3 } from "fs/promises";
|
|
23
23
|
import { render } from "ink";
|
|
24
24
|
|
|
25
25
|
// src/app.tsx
|
|
@@ -51,15 +51,14 @@ var VIEWS = [
|
|
|
51
51
|
];
|
|
52
52
|
var useNavigationStore = create((set, get) => ({
|
|
53
53
|
currentView: "dashboard",
|
|
54
|
-
previousView: null,
|
|
55
54
|
selectedPackage: null,
|
|
55
|
+
selectedPackageType: null,
|
|
56
56
|
viewHistory: [],
|
|
57
57
|
navigate: (view) => {
|
|
58
58
|
const { currentView, viewHistory } = get();
|
|
59
59
|
if (view === currentView) return;
|
|
60
60
|
set({
|
|
61
61
|
currentView: view,
|
|
62
|
-
previousView: currentView,
|
|
63
62
|
viewHistory: [...viewHistory.slice(-19), currentView]
|
|
64
63
|
});
|
|
65
64
|
},
|
|
@@ -69,11 +68,10 @@ var useNavigationStore = create((set, get) => ({
|
|
|
69
68
|
const prev = viewHistory[viewHistory.length - 1];
|
|
70
69
|
set({
|
|
71
70
|
currentView: prev,
|
|
72
|
-
previousView: get().currentView,
|
|
73
71
|
viewHistory: viewHistory.slice(0, -1)
|
|
74
72
|
});
|
|
75
73
|
},
|
|
76
|
-
selectPackage: (name) => set({ selectedPackage: name })
|
|
74
|
+
selectPackage: (name, type = null) => set({ selectedPackage: name, selectedPackageType: type })
|
|
77
75
|
}));
|
|
78
76
|
function getNextView(current) {
|
|
79
77
|
const idx = VIEWS.indexOf(current);
|
|
@@ -95,6 +93,24 @@ function isProView(viewId) {
|
|
|
95
93
|
return PRO_VIEWS.has(viewId);
|
|
96
94
|
}
|
|
97
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
|
+
|
|
98
114
|
// src/utils/gradient.tsx
|
|
99
115
|
import React, { useMemo } from "react";
|
|
100
116
|
import { Text } from "ink";
|
|
@@ -194,6 +210,7 @@ var TAB_VIEWS = [
|
|
|
194
210
|
"installed",
|
|
195
211
|
"search",
|
|
196
212
|
"outdated",
|
|
213
|
+
"package-info",
|
|
197
214
|
"services",
|
|
198
215
|
"doctor",
|
|
199
216
|
"profiles",
|
|
@@ -210,13 +227,13 @@ function Header() {
|
|
|
210
227
|
/* @__PURE__ */ jsx2(GradientText, { colors: GRADIENTS.gold, children: brew }),
|
|
211
228
|
/* @__PURE__ */ jsx2(GradientText, { colors: ["#B8860B", "#8B6914", "#6B4F10"], children: LOGO_TUI[i] })
|
|
212
229
|
] }, i)) }),
|
|
213
|
-
/* @__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) => {
|
|
214
231
|
const key = VIEW_KEYS[view];
|
|
215
232
|
const viewLabel = t(VIEW_LABEL_KEYS[view]);
|
|
216
233
|
const label = key ? `${key}:${viewLabel}` : viewLabel;
|
|
217
234
|
const isPro = isProView(view);
|
|
218
235
|
return /* @__PURE__ */ jsxs(React2.Fragment, { children: [
|
|
219
|
-
i > 0 && /* @__PURE__ */ jsxs(Text2, { color:
|
|
236
|
+
i > 0 && /* @__PURE__ */ jsxs(Text2, { color: COLORS.border, children: [
|
|
220
237
|
" ",
|
|
221
238
|
"\u2502",
|
|
222
239
|
" "
|
|
@@ -225,12 +242,12 @@ function Header() {
|
|
|
225
242
|
Text2,
|
|
226
243
|
{
|
|
227
244
|
bold: view === currentView,
|
|
228
|
-
color: view === currentView ?
|
|
245
|
+
color: view === currentView ? COLORS.success : COLORS.textSecondary,
|
|
229
246
|
underline: view === currentView,
|
|
230
247
|
children: view === currentView ? `\u25CF ${label}` : label
|
|
231
248
|
}
|
|
232
249
|
),
|
|
233
|
-
isPro && /* @__PURE__ */ jsxs(Text2, { color:
|
|
250
|
+
isPro && /* @__PURE__ */ jsxs(Text2, { color: COLORS.brand, bold: true, children: [
|
|
234
251
|
" ",
|
|
235
252
|
t("pro_badge")
|
|
236
253
|
] })
|
|
@@ -258,22 +275,22 @@ var VIEW_HINT_DEFS = {
|
|
|
258
275
|
account: [["d", "hint_deactivate"], ["q", "hint_quit"]]
|
|
259
276
|
};
|
|
260
277
|
function HintItem({ def }) {
|
|
261
|
-
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]) });
|
|
262
279
|
return /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
263
|
-
/* @__PURE__ */ jsx3(Text3, { color:
|
|
264
|
-
/* @__PURE__ */ jsx3(Text3, { color:
|
|
265
|
-
/* @__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]) })
|
|
266
283
|
] });
|
|
267
284
|
}
|
|
268
285
|
function Footer() {
|
|
269
286
|
const currentView = useNavigationStore((s) => s.currentView);
|
|
270
287
|
const locale = useLocaleStore((s) => s.locale);
|
|
271
288
|
const defs = VIEW_HINT_DEFS[currentView] ?? [];
|
|
272
|
-
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: [
|
|
273
290
|
defs.map((def, i) => {
|
|
274
291
|
const key = def.length === 1 ? def[0] : `${def[0]}:${def[1]}`;
|
|
275
292
|
return /* @__PURE__ */ jsxs2(React3.Fragment, { children: [
|
|
276
|
-
i > 0 && /* @__PURE__ */ jsxs2(Text3, { color:
|
|
293
|
+
i > 0 && /* @__PURE__ */ jsxs2(Text3, { color: COLORS.border, children: [
|
|
277
294
|
" ",
|
|
278
295
|
"\u2502",
|
|
279
296
|
" "
|
|
@@ -281,14 +298,14 @@ function Footer() {
|
|
|
281
298
|
/* @__PURE__ */ jsx3(HintItem, { def })
|
|
282
299
|
] }, key);
|
|
283
300
|
}),
|
|
284
|
-
/* @__PURE__ */ jsxs2(Text3, { color:
|
|
301
|
+
/* @__PURE__ */ jsxs2(Text3, { color: COLORS.border, children: [
|
|
285
302
|
" ",
|
|
286
303
|
"\u2502",
|
|
287
304
|
" "
|
|
288
305
|
] }),
|
|
289
|
-
/* @__PURE__ */ jsx3(Text3, { color:
|
|
290
|
-
/* @__PURE__ */ jsx3(Text3, { color:
|
|
291
|
-
/* @__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: [
|
|
292
309
|
t("hint_lang"),
|
|
293
310
|
"(",
|
|
294
311
|
locale,
|
|
@@ -307,12 +324,469 @@ function AppLayout({ children }) {
|
|
|
307
324
|
] });
|
|
308
325
|
}
|
|
309
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
|
+
|
|
310
784
|
// src/hooks/use-keyboard.ts
|
|
311
785
|
import { useInput } from "ink";
|
|
312
786
|
|
|
313
787
|
// src/stores/modal-store.ts
|
|
314
|
-
import { create as
|
|
315
|
-
var useModalStore =
|
|
788
|
+
import { create as create3 } from "zustand";
|
|
789
|
+
var useModalStore = create3((set) => ({
|
|
316
790
|
_count: 0,
|
|
317
791
|
isOpen: false,
|
|
318
792
|
openModal: () => set((s) => {
|
|
@@ -390,37 +864,37 @@ function UpgradePrompt({ viewId }) {
|
|
|
390
864
|
Box4,
|
|
391
865
|
{
|
|
392
866
|
borderStyle: "double",
|
|
393
|
-
borderColor:
|
|
867
|
+
borderColor: COLORS.brand,
|
|
394
868
|
paddingX: 3,
|
|
395
869
|
paddingY: 2,
|
|
396
870
|
flexDirection: "column",
|
|
397
871
|
alignItems: "center",
|
|
398
872
|
width: "80%",
|
|
399
873
|
children: [
|
|
400
|
-
/* @__PURE__ */ jsxs4(Text4, { bold: true, color:
|
|
874
|
+
/* @__PURE__ */ jsxs4(Text4, { bold: true, color: COLORS.brand, children: [
|
|
401
875
|
"\u2B50",
|
|
402
876
|
" ",
|
|
403
877
|
t("upgrade_proFeature", { title })
|
|
404
878
|
] }),
|
|
405
879
|
/* @__PURE__ */ jsx5(Text4, { children: " " }),
|
|
406
|
-
/* @__PURE__ */ jsx5(Text4, { color:
|
|
880
|
+
/* @__PURE__ */ jsx5(Text4, { color: COLORS.text, wrap: "wrap", children: t(keys.desc) }),
|
|
407
881
|
/* @__PURE__ */ jsx5(Text4, { children: " " }),
|
|
408
882
|
/* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", alignItems: "center", children: [
|
|
409
|
-
/* @__PURE__ */ jsx5(Text4, { color:
|
|
883
|
+
/* @__PURE__ */ jsx5(Text4, { color: COLORS.info, bold: true, children: t("upgrade_pricing") }),
|
|
410
884
|
/* @__PURE__ */ jsx5(Text4, { children: " " }),
|
|
411
|
-
/* @__PURE__ */ jsx5(Text4, { color:
|
|
412
|
-
/* @__PURE__ */ jsxs4(Text4, { color:
|
|
885
|
+
/* @__PURE__ */ jsx5(Text4, { color: COLORS.muted, children: t("upgrade_buyAt") }),
|
|
886
|
+
/* @__PURE__ */ jsxs4(Text4, { color: COLORS.sky, bold: true, children: [
|
|
413
887
|
" ",
|
|
414
888
|
t("upgrade_buyUrl")
|
|
415
889
|
] }),
|
|
416
890
|
/* @__PURE__ */ jsx5(Text4, { children: " " }),
|
|
417
|
-
/* @__PURE__ */ jsx5(Text4, { color:
|
|
418
|
-
/* @__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: [
|
|
419
893
|
" ",
|
|
420
894
|
t("upgrade_activateCmd")
|
|
421
895
|
] }),
|
|
422
896
|
/* @__PURE__ */ jsx5(Text4, { children: " " }),
|
|
423
|
-
/* @__PURE__ */ jsx5(Text4, { color:
|
|
897
|
+
/* @__PURE__ */ jsx5(Text4, { color: COLORS.brand, children: t("upgrade_proLabel") })
|
|
424
898
|
] })
|
|
425
899
|
]
|
|
426
900
|
}
|
|
@@ -429,21 +903,29 @@ function UpgradePrompt({ viewId }) {
|
|
|
429
903
|
|
|
430
904
|
// src/views/dashboard.tsx
|
|
431
905
|
import { useEffect, useMemo as useMemo2 } from "react";
|
|
432
|
-
import { Box as Box8, Text as Text10 } from "ink";
|
|
906
|
+
import { Box as Box8, Text as Text10, useStdout } from "ink";
|
|
433
907
|
|
|
434
908
|
// src/stores/brew-store.ts
|
|
435
|
-
import { create as
|
|
909
|
+
import { create as create4 } from "zustand";
|
|
436
910
|
|
|
437
911
|
// src/lib/brew-api.ts
|
|
438
912
|
import { spawn as spawn2 } from "child_process";
|
|
439
913
|
|
|
440
914
|
// src/lib/brew-cli.ts
|
|
441
915
|
import { spawn } from "child_process";
|
|
442
|
-
|
|
916
|
+
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
917
|
+
var STREAM_IDLE_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
918
|
+
async function execBrew(args, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
443
919
|
return new Promise((resolve, reject) => {
|
|
444
920
|
const proc = spawn("brew", args, { env: { ...process.env, HOMEBREW_NO_AUTO_UPDATE: "1" } });
|
|
445
921
|
let stdout = "";
|
|
446
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);
|
|
447
929
|
proc.stdout.on("data", (d) => {
|
|
448
930
|
stdout += d.toString();
|
|
449
931
|
});
|
|
@@ -451,6 +933,8 @@ async function execBrew(args) {
|
|
|
451
933
|
stderr += d.toString();
|
|
452
934
|
});
|
|
453
935
|
proc.on("close", (code) => {
|
|
936
|
+
clearTimeout(timer);
|
|
937
|
+
if (killed) return;
|
|
454
938
|
if (code === 0) {
|
|
455
939
|
resolve(stdout);
|
|
456
940
|
} else {
|
|
@@ -458,6 +942,8 @@ async function execBrew(args) {
|
|
|
458
942
|
}
|
|
459
943
|
});
|
|
460
944
|
proc.on("error", (err) => {
|
|
945
|
+
clearTimeout(timer);
|
|
946
|
+
if (killed) return;
|
|
461
947
|
reject(new Error(`Failed to run brew: ${err.message}`));
|
|
462
948
|
});
|
|
463
949
|
});
|
|
@@ -471,7 +957,9 @@ async function* streamBrew(args) {
|
|
|
471
957
|
const lines = [];
|
|
472
958
|
let done = false;
|
|
473
959
|
let exitError = null;
|
|
960
|
+
let lastOutputAt = Date.now();
|
|
474
961
|
const push = (chunk) => {
|
|
962
|
+
lastOutputAt = Date.now();
|
|
475
963
|
buffer += chunk.toString();
|
|
476
964
|
const parts = buffer.split("\n");
|
|
477
965
|
buffer = parts.pop() ?? "";
|
|
@@ -497,6 +985,10 @@ async function* streamBrew(args) {
|
|
|
497
985
|
if (lines.length > 0) {
|
|
498
986
|
yield lines.shift();
|
|
499
987
|
} else if (!done) {
|
|
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
|
+
}
|
|
500
992
|
await new Promise((r) => setTimeout(r, 100));
|
|
501
993
|
}
|
|
502
994
|
}
|
|
@@ -608,6 +1100,10 @@ function parseLeavesOutput(raw) {
|
|
|
608
1100
|
}
|
|
609
1101
|
|
|
610
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
|
+
}
|
|
611
1107
|
async function brewUpdate() {
|
|
612
1108
|
return new Promise((resolve, reject) => {
|
|
613
1109
|
const proc = spawn2("brew", ["update"], { stdio: "ignore" });
|
|
@@ -631,9 +1127,20 @@ async function getServices() {
|
|
|
631
1127
|
return parseServicesJson(raw);
|
|
632
1128
|
}
|
|
633
1129
|
async function getFormulaInfo(name) {
|
|
1130
|
+
validatePackageName(name);
|
|
634
1131
|
const raw = await execBrew(["info", "--json=v2", name]);
|
|
635
1132
|
return parseFormulaInfoJson(raw);
|
|
636
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
|
+
}
|
|
637
1144
|
async function search(term) {
|
|
638
1145
|
const safeTerm = term.replace(/^-+/, "");
|
|
639
1146
|
if (!safeTerm) return { formulae: [], casks: [] };
|
|
@@ -658,11 +1165,21 @@ async function getLeaves() {
|
|
|
658
1165
|
return parseLeavesOutput(raw);
|
|
659
1166
|
}
|
|
660
1167
|
async function uninstallPackage(name) {
|
|
1168
|
+
validatePackageName(name);
|
|
661
1169
|
return execBrew(["uninstall", name]);
|
|
662
1170
|
}
|
|
663
1171
|
async function serviceAction(name, action) {
|
|
1172
|
+
validatePackageName(name);
|
|
664
1173
|
return execBrew(["services", action, name]);
|
|
665
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
|
+
}
|
|
666
1183
|
function formulaeToListItems(formulae) {
|
|
667
1184
|
return formulae.map((f) => {
|
|
668
1185
|
const installed = f.installed[0];
|
|
@@ -694,6 +1211,10 @@ function casksToListItems(casks) {
|
|
|
694
1211
|
}
|
|
695
1212
|
|
|
696
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;
|
|
697
1218
|
function setLoading(set, key, value) {
|
|
698
1219
|
set((s) => ({ loading: { ...s.loading, [key]: value } }));
|
|
699
1220
|
}
|
|
@@ -703,7 +1224,7 @@ function setError(set, key, error) {
|
|
|
703
1224
|
function recordFetchTime(set, key) {
|
|
704
1225
|
set((s) => ({ lastFetchedAt: { ...s.lastFetchedAt, [key]: Date.now() } }));
|
|
705
1226
|
}
|
|
706
|
-
var useBrewStore =
|
|
1227
|
+
var useBrewStore = create4((set, get) => ({
|
|
707
1228
|
formulae: [],
|
|
708
1229
|
casks: [],
|
|
709
1230
|
outdated: { formulae: [], casks: [] },
|
|
@@ -715,7 +1236,8 @@ var useBrewStore = create3((set) => ({
|
|
|
715
1236
|
// Pre-initialize loading flags for keys that fetchAll always triggers so
|
|
716
1237
|
// views that check loading.X get a spinner on first render rather than
|
|
717
1238
|
// flashing empty/zeroed content for one frame before the fetch starts.
|
|
718
|
-
|
|
1239
|
+
// SCR-013: Pre-initialize doctor loading to true
|
|
1240
|
+
loading: { installed: true, outdated: true, services: true, config: true, doctor: true },
|
|
719
1241
|
errors: {},
|
|
720
1242
|
lastFetchedAt: {},
|
|
721
1243
|
fetchInstalled: async () => {
|
|
@@ -774,9 +1296,7 @@ var useBrewStore = create3((set) => ({
|
|
|
774
1296
|
const result = await getLeaves();
|
|
775
1297
|
set({ leaves: result });
|
|
776
1298
|
} catch (err) {
|
|
777
|
-
|
|
778
|
-
console.error("[brew-store] fetchLeaves failed:", err instanceof Error ? err.message : String(err));
|
|
779
|
-
}
|
|
1299
|
+
logger.error("fetchLeaves failed", { error: err instanceof Error ? err.message : String(err) });
|
|
780
1300
|
} finally {
|
|
781
1301
|
recordFetchTime(set, "leaves");
|
|
782
1302
|
}
|
|
@@ -795,22 +1315,36 @@ var useBrewStore = create3((set) => ({
|
|
|
795
1315
|
}
|
|
796
1316
|
},
|
|
797
1317
|
fetchAll: async () => {
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
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
|
+
});
|
|
1329
|
+
}
|
|
1330
|
+
const store = get();
|
|
1331
|
+
fetchAllInFlight = Promise.all([
|
|
802
1332
|
store.fetchInstalled(),
|
|
803
1333
|
store.fetchOutdated(),
|
|
804
1334
|
store.fetchServices(),
|
|
805
1335
|
store.fetchConfig(),
|
|
806
|
-
store.fetchLeaves()
|
|
807
|
-
|
|
1336
|
+
store.fetchLeaves(),
|
|
1337
|
+
store.fetchDoctor()
|
|
1338
|
+
]).then(() => void 0).finally(() => {
|
|
1339
|
+
fetchAllInFlight = null;
|
|
1340
|
+
});
|
|
1341
|
+
return fetchAllInFlight;
|
|
808
1342
|
},
|
|
809
1343
|
uninstallPackage: async (name) => {
|
|
810
1344
|
setLoading(set, "action", true);
|
|
811
1345
|
try {
|
|
812
1346
|
await uninstallPackage(name);
|
|
813
|
-
await
|
|
1347
|
+
await get().fetchInstalled();
|
|
814
1348
|
} catch (err) {
|
|
815
1349
|
setError(set, "action", err instanceof Error ? err.message : String(err));
|
|
816
1350
|
} finally {
|
|
@@ -821,7 +1355,7 @@ var useBrewStore = create3((set) => ({
|
|
|
821
1355
|
setLoading(set, "service-action", true);
|
|
822
1356
|
try {
|
|
823
1357
|
await serviceAction(name, action);
|
|
824
|
-
await
|
|
1358
|
+
await get().fetchServices();
|
|
825
1359
|
} catch (err) {
|
|
826
1360
|
setError(set, "service-action", err instanceof Error ? err.message : String(err));
|
|
827
1361
|
} finally {
|
|
@@ -846,7 +1380,7 @@ function StatCard({ label, value, color = "white" }) {
|
|
|
846
1380
|
minWidth: 16,
|
|
847
1381
|
children: [
|
|
848
1382
|
/* @__PURE__ */ jsx6(Text5, { bold: true, color, children: value }),
|
|
849
|
-
/* @__PURE__ */ jsx6(Text5, { color:
|
|
1383
|
+
/* @__PURE__ */ jsx6(Text5, { color: COLORS.muted, children: label })
|
|
850
1384
|
]
|
|
851
1385
|
}
|
|
852
1386
|
);
|
|
@@ -863,12 +1397,12 @@ function Loading({ message }) {
|
|
|
863
1397
|
function ErrorMessage({ message }) {
|
|
864
1398
|
useLocaleStore((s) => s.locale);
|
|
865
1399
|
return /* @__PURE__ */ jsxs6(Box6, { paddingY: 1, children: [
|
|
866
|
-
/* @__PURE__ */ jsxs6(Text6, { color:
|
|
1400
|
+
/* @__PURE__ */ jsxs6(Text6, { color: COLORS.error, bold: true, children: [
|
|
867
1401
|
"\u2718",
|
|
868
1402
|
" ",
|
|
869
1403
|
t("error_prefix")
|
|
870
1404
|
] }),
|
|
871
|
-
/* @__PURE__ */ jsx7(Text6, { color:
|
|
1405
|
+
/* @__PURE__ */ jsx7(Text6, { color: COLORS.error, children: message })
|
|
872
1406
|
] });
|
|
873
1407
|
}
|
|
874
1408
|
|
|
@@ -876,11 +1410,11 @@ function ErrorMessage({ message }) {
|
|
|
876
1410
|
import { Text as Text7 } from "ink";
|
|
877
1411
|
import { jsxs as jsxs7 } from "react/jsx-runtime";
|
|
878
1412
|
var BADGE_STYLES = {
|
|
879
|
-
success: { icon: "\u2714", color:
|
|
880
|
-
warning: { icon: "\u25CF", color:
|
|
881
|
-
error: { icon: "\u2718", color:
|
|
882
|
-
info: { icon: "\u25C6", color:
|
|
883
|
-
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 }
|
|
884
1418
|
};
|
|
885
1419
|
function StatusBadge({ label, variant }) {
|
|
886
1420
|
const { icon, color } = BADGE_STYLES[variant];
|
|
@@ -894,11 +1428,11 @@ function StatusBadge({ label, variant }) {
|
|
|
894
1428
|
// src/components/common/section-header.tsx
|
|
895
1429
|
import { Box as Box7, Text as Text8 } from "ink";
|
|
896
1430
|
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
897
|
-
function SectionHeader({ emoji, title, color =
|
|
1431
|
+
function SectionHeader({ emoji, title, color = COLORS.gold, gradient, count }) {
|
|
898
1432
|
return /* @__PURE__ */ jsxs8(Box7, { gap: 1, children: [
|
|
899
1433
|
/* @__PURE__ */ jsx8(Text8, { children: emoji }),
|
|
900
1434
|
gradient ? /* @__PURE__ */ jsx8(GradientText, { colors: gradient, bold: true, children: title }) : /* @__PURE__ */ jsx8(Text8, { bold: true, color, children: title }),
|
|
901
|
-
count !== void 0 && /* @__PURE__ */ jsxs8(Text8, { color:
|
|
1435
|
+
count !== void 0 && /* @__PURE__ */ jsxs8(Text8, { color: COLORS.textSecondary, children: [
|
|
902
1436
|
"(",
|
|
903
1437
|
count,
|
|
904
1438
|
")"
|
|
@@ -911,20 +1445,55 @@ import { Text as Text9 } from "ink";
|
|
|
911
1445
|
import { Fragment as Fragment3, jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
912
1446
|
function VersionArrow({ current, latest }) {
|
|
913
1447
|
return /* @__PURE__ */ jsxs9(Fragment3, { children: [
|
|
914
|
-
/* @__PURE__ */
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
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: [
|
|
918
1456
|
" ",
|
|
919
|
-
|
|
920
|
-
|
|
1457
|
+
t("version_available"),
|
|
1458
|
+
" "
|
|
1459
|
+
] }),
|
|
1460
|
+
/* @__PURE__ */ jsx9(Text9, { color: COLORS.teal, children: latest })
|
|
921
1461
|
] });
|
|
922
1462
|
}
|
|
923
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
|
+
|
|
924
1491
|
// src/views/dashboard.tsx
|
|
925
1492
|
import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
926
1493
|
function DashboardView() {
|
|
927
|
-
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;
|
|
928
1497
|
useEffect(() => {
|
|
929
1498
|
fetchAll();
|
|
930
1499
|
}, []);
|
|
@@ -937,66 +1506,84 @@ function DashboardView() {
|
|
|
937
1506
|
[services]
|
|
938
1507
|
);
|
|
939
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;
|
|
940
1517
|
if (loading.installed) return /* @__PURE__ */ jsx10(Loading, { message: t("loading_fetchingBrew") });
|
|
941
1518
|
if (errors.installed) return /* @__PURE__ */ jsx10(ErrorMessage, { message: errors.installed });
|
|
1519
|
+
const isNarrow = columns < 60;
|
|
942
1520
|
return /* @__PURE__ */ jsxs10(Box8, { flexDirection: "column", gap: 2, children: [
|
|
943
1521
|
/* @__PURE__ */ jsx10(SectionHeader, { emoji: "\u{1F4CA}", title: t("dashboard_overview"), gradient: GRADIENTS.gold }),
|
|
944
|
-
/* @__PURE__ */ jsxs10(Box8, { gap: 1, flexWrap: "wrap", children: [
|
|
945
|
-
/* @__PURE__ */ jsx10(StatCard, { label: t("dashboard_formulae"), value: formulae.length, color:
|
|
946
|
-
/* @__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 }),
|
|
947
1525
|
/* @__PURE__ */ jsx10(
|
|
948
1526
|
StatCard,
|
|
949
1527
|
{
|
|
950
1528
|
label: t("dashboard_outdated"),
|
|
951
|
-
value:
|
|
952
|
-
color:
|
|
1529
|
+
value: outdatedValue,
|
|
1530
|
+
color: typeof outdatedValue === "number" && outdatedValue > 0 ? COLORS.warning : errors.outdated ? COLORS.error : COLORS.success
|
|
953
1531
|
}
|
|
954
1532
|
),
|
|
955
1533
|
/* @__PURE__ */ jsx10(
|
|
956
1534
|
StatCard,
|
|
957
1535
|
{
|
|
958
1536
|
label: t("dashboard_services"),
|
|
959
|
-
value:
|
|
960
|
-
color: errorServices > 0 ?
|
|
1537
|
+
value: servicesValue,
|
|
1538
|
+
color: errors.services || errorServices > 0 ? COLORS.error : COLORS.success
|
|
961
1539
|
}
|
|
962
1540
|
)
|
|
963
1541
|
] }),
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
/* @__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: [
|
|
967
1554
|
/* @__PURE__ */ jsxs10(Text10, { children: [
|
|
968
|
-
/* @__PURE__ */ jsx10(Text10, { color:
|
|
1555
|
+
/* @__PURE__ */ jsx10(Text10, { color: COLORS.muted, children: t("dashboard_homebrew") }),
|
|
969
1556
|
" ",
|
|
970
1557
|
config.HOMEBREW_VERSION
|
|
971
1558
|
] }),
|
|
972
1559
|
/* @__PURE__ */ jsxs10(Text10, { children: [
|
|
973
|
-
/* @__PURE__ */ jsx10(Text10, { color:
|
|
1560
|
+
/* @__PURE__ */ jsx10(Text10, { color: COLORS.muted, children: t("dashboard_prefix") }),
|
|
974
1561
|
" ",
|
|
975
1562
|
config.HOMEBREW_PREFIX
|
|
976
1563
|
] }),
|
|
977
1564
|
/* @__PURE__ */ jsxs10(Text10, { children: [
|
|
978
|
-
/* @__PURE__ */ jsx10(Text10, { color:
|
|
1565
|
+
/* @__PURE__ */ jsx10(Text10, { color: COLORS.muted, children: t("dashboard_updated") }),
|
|
979
1566
|
" ",
|
|
980
1567
|
config.coreUpdated
|
|
981
1568
|
] })
|
|
982
1569
|
] })
|
|
983
1570
|
] }),
|
|
984
|
-
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: [
|
|
985
1572
|
/* @__PURE__ */ jsx10(SectionHeader, { emoji: "\u{1F4E6}", title: t("dashboard_outdatedPackages"), gradient: GRADIENTS.fire }),
|
|
986
1573
|
/* @__PURE__ */ jsxs10(Box8, { paddingLeft: 2, flexDirection: "column", children: [
|
|
987
1574
|
outdated.formulae.slice(0, 10).map((pkg) => /* @__PURE__ */ jsxs10(Box8, { gap: 1, children: [
|
|
988
|
-
/* @__PURE__ */ jsx10(Text10, { color:
|
|
1575
|
+
/* @__PURE__ */ jsx10(Text10, { color: COLORS.text, children: pkg.name }),
|
|
989
1576
|
/* @__PURE__ */ jsx10(VersionArrow, { current: pkg.installed_versions[0] ?? "", latest: pkg.current_version })
|
|
990
1577
|
] }, pkg.name)),
|
|
991
|
-
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 }) })
|
|
992
1579
|
] })
|
|
993
1580
|
] }),
|
|
994
|
-
errorServices > 0 && /* @__PURE__ */ jsxs10(Box8, { flexDirection: "column", marginTop: 1, children: [
|
|
995
|
-
/* @__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 }),
|
|
996
1583
|
/* @__PURE__ */ jsx10(Box8, { paddingLeft: 2, flexDirection: "column", children: errorServiceList.map((s) => /* @__PURE__ */ jsxs10(Box8, { gap: 1, children: [
|
|
997
1584
|
/* @__PURE__ */ jsx10(StatusBadge, { label: t("badge_error"), variant: "error" }),
|
|
998
1585
|
/* @__PURE__ */ jsx10(Text10, { children: s.name }),
|
|
999
|
-
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 }) })
|
|
1000
1587
|
] }, s.name)) })
|
|
1001
1588
|
] })
|
|
1002
1589
|
] });
|
|
@@ -1004,7 +1591,7 @@ function DashboardView() {
|
|
|
1004
1591
|
|
|
1005
1592
|
// src/views/installed.tsx
|
|
1006
1593
|
import { useState as useState3, useMemo as useMemo3, useEffect as useEffect5 } from "react";
|
|
1007
|
-
import { Box as
|
|
1594
|
+
import { Box as Box14, Text as Text16, useInput as useInput3, useStdout as useStdout2 } from "ink";
|
|
1008
1595
|
|
|
1009
1596
|
// src/hooks/use-debounce.ts
|
|
1010
1597
|
import { useState, useEffect as useEffect2 } from "react";
|
|
@@ -1024,8 +1611,9 @@ async function logToHistory(args, success, error) {
|
|
|
1024
1611
|
const detected = detectAction(args);
|
|
1025
1612
|
if (!detected) return;
|
|
1026
1613
|
try {
|
|
1027
|
-
const
|
|
1028
|
-
|
|
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);
|
|
1029
1617
|
} catch {
|
|
1030
1618
|
}
|
|
1031
1619
|
}
|
|
@@ -1094,7 +1682,7 @@ import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
|
1094
1682
|
function SearchInput({ defaultValue, onChange, placeholder, isActive = true }) {
|
|
1095
1683
|
const resolvedPlaceholder = placeholder ?? t("searchInput_placeholder");
|
|
1096
1684
|
return /* @__PURE__ */ jsxs11(Box9, { children: [
|
|
1097
|
-
/* @__PURE__ */ jsxs11(Text11, { color:
|
|
1685
|
+
/* @__PURE__ */ jsxs11(Text11, { color: COLORS.gold, children: [
|
|
1098
1686
|
"\u{1F50D}",
|
|
1099
1687
|
" "
|
|
1100
1688
|
] }),
|
|
@@ -1105,7 +1693,7 @@ function SearchInput({ defaultValue, onChange, placeholder, isActive = true }) {
|
|
|
1105
1693
|
defaultValue,
|
|
1106
1694
|
onChange
|
|
1107
1695
|
}
|
|
1108
|
-
) : /* @__PURE__ */ jsx11(Text11, { color:
|
|
1696
|
+
) : /* @__PURE__ */ jsx11(Text11, { color: COLORS.textSecondary, children: defaultValue || placeholder })
|
|
1109
1697
|
] });
|
|
1110
1698
|
}
|
|
1111
1699
|
|
|
@@ -1128,12 +1716,12 @@ function ConfirmDialog({ message, onConfirm, onCancel }) {
|
|
|
1128
1716
|
else if (input === "n" || input === "N") onCancel();
|
|
1129
1717
|
else if (key.escape) onCancel();
|
|
1130
1718
|
});
|
|
1131
|
-
return /* @__PURE__ */ jsxs12(Box10, { borderStyle: "double", borderColor:
|
|
1132
|
-
/* @__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 }),
|
|
1133
1721
|
/* @__PURE__ */ jsxs12(Box10, { marginTop: 1, children: [
|
|
1134
|
-
/* @__PURE__ */ jsx12(Text12, { color:
|
|
1722
|
+
/* @__PURE__ */ jsx12(Text12, { color: COLORS.success, children: t("confirm_yes") }),
|
|
1135
1723
|
/* @__PURE__ */ jsx12(Text12, { children: " / " }),
|
|
1136
|
-
/* @__PURE__ */ jsx12(Text12, { color:
|
|
1724
|
+
/* @__PURE__ */ jsx12(Text12, { color: COLORS.error, children: t("confirm_no") })
|
|
1137
1725
|
] })
|
|
1138
1726
|
] });
|
|
1139
1727
|
}
|
|
@@ -1144,43 +1732,44 @@ import { Spinner as Spinner2 } from "@inkjs/ui";
|
|
|
1144
1732
|
import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1145
1733
|
function ProgressLog({ lines, isRunning, title, maxVisible = 15 }) {
|
|
1146
1734
|
const visible = lines.slice(-maxVisible);
|
|
1147
|
-
return /* @__PURE__ */ jsxs13(Box11, { flexDirection: "column", borderStyle: "round", borderColor:
|
|
1735
|
+
return /* @__PURE__ */ jsxs13(Box11, { flexDirection: "column", borderStyle: "round", borderColor: COLORS.sky, paddingX: 1, children: [
|
|
1148
1736
|
title && /* @__PURE__ */ jsxs13(Box11, { marginBottom: 1, children: [
|
|
1149
1737
|
isRunning && /* @__PURE__ */ jsx13(Spinner2, { label: "" }),
|
|
1150
|
-
/* @__PURE__ */ jsxs13(Text13, { bold: true, color:
|
|
1738
|
+
/* @__PURE__ */ jsxs13(Text13, { bold: true, color: COLORS.sky, children: [
|
|
1151
1739
|
" ",
|
|
1152
1740
|
title
|
|
1153
1741
|
] })
|
|
1154
1742
|
] }),
|
|
1155
|
-
visible.map((line, i) => /* @__PURE__ */ jsx13(Text13, { color:
|
|
1156
|
-
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") })
|
|
1157
1745
|
] });
|
|
1158
1746
|
}
|
|
1159
1747
|
|
|
1160
|
-
// src/
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
if (diff < 60) return t("time_justNow");
|
|
1172
|
-
if (diff < 3600) return t("time_minutesAgo", { n: Math.floor(diff / 60) });
|
|
1173
|
-
if (diff < 86400) return t("time_hoursAgo", { n: Math.floor(diff / 3600) });
|
|
1174
|
-
if (diff < 2592e3) return t("time_daysAgo", { n: Math.floor(diff / 86400) });
|
|
1175
|
-
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 }) });
|
|
1176
1759
|
}
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
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
|
+
] });
|
|
1180
1769
|
}
|
|
1181
1770
|
|
|
1182
1771
|
// src/views/installed.tsx
|
|
1183
|
-
import { jsx as
|
|
1772
|
+
import { jsx as jsx16, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
1184
1773
|
function InstalledView() {
|
|
1185
1774
|
const { formulae, casks, loading, errors, fetchInstalled } = useBrewStore();
|
|
1186
1775
|
const navigate = useNavigationStore((s) => s.navigate);
|
|
@@ -1193,7 +1782,7 @@ function InstalledView() {
|
|
|
1193
1782
|
const debouncedFilter = useDebounce(filter, 200);
|
|
1194
1783
|
const stream = useBrewStream();
|
|
1195
1784
|
const { openModal, closeModal } = useModalStore();
|
|
1196
|
-
const { stdout } =
|
|
1785
|
+
const { stdout } = useStdout2();
|
|
1197
1786
|
const cols = stdout?.columns ?? 80;
|
|
1198
1787
|
const nameWidth = Math.floor(cols * 0.35);
|
|
1199
1788
|
const versionWidth = Math.floor(cols * 0.15);
|
|
@@ -1250,18 +1839,18 @@ function InstalledView() {
|
|
|
1250
1839
|
} else if (input === "G") {
|
|
1251
1840
|
setCursor(Math.max(allItems.length - 1, 0));
|
|
1252
1841
|
} else if (key.return && allItems[cursor]) {
|
|
1253
|
-
selectPackage(allItems[cursor].name);
|
|
1842
|
+
selectPackage(allItems[cursor].name, tab === "formulae" ? "formula" : "cask");
|
|
1254
1843
|
navigate("package-info");
|
|
1255
1844
|
} else if (input === "f") {
|
|
1256
1845
|
setTab((t2) => t2 === "formulae" ? "casks" : "formulae");
|
|
1257
1846
|
setCursor(0);
|
|
1258
1847
|
}
|
|
1259
1848
|
}, { isActive: true });
|
|
1260
|
-
if (loading.installed) return /* @__PURE__ */
|
|
1261
|
-
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 });
|
|
1262
1851
|
if (stream.isRunning || stream.lines.length > 0) {
|
|
1263
|
-
return /* @__PURE__ */
|
|
1264
|
-
/* @__PURE__ */
|
|
1852
|
+
return /* @__PURE__ */ jsxs15(Box14, { flexDirection: "column", children: [
|
|
1853
|
+
/* @__PURE__ */ jsx16(
|
|
1265
1854
|
ProgressLog,
|
|
1266
1855
|
{
|
|
1267
1856
|
lines: stream.lines,
|
|
@@ -1269,13 +1858,19 @@ function InstalledView() {
|
|
|
1269
1858
|
title: t("pkgInfo_uninstalling", { name: "..." })
|
|
1270
1859
|
}
|
|
1271
1860
|
),
|
|
1272
|
-
stream.isRunning && /* @__PURE__ */
|
|
1861
|
+
stream.isRunning && /* @__PURE__ */ jsxs15(Text16, { color: COLORS.textSecondary, children: [
|
|
1273
1862
|
"esc:",
|
|
1274
1863
|
t("hint_cancel")
|
|
1275
1864
|
] }),
|
|
1276
|
-
!stream.isRunning && /* @__PURE__ */
|
|
1277
|
-
/* @__PURE__ */
|
|
1278
|
-
|
|
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: [
|
|
1279
1874
|
"esc:",
|
|
1280
1875
|
t("hint_back")
|
|
1281
1876
|
] })
|
|
@@ -1285,28 +1880,28 @@ function InstalledView() {
|
|
|
1285
1880
|
const MAX_VISIBLE_ROWS = Math.max(5, (stdout?.rows ?? 24) - 8);
|
|
1286
1881
|
const start = Math.max(0, cursor - Math.floor(MAX_VISIBLE_ROWS / 2));
|
|
1287
1882
|
const visible = allItems.slice(start, start + MAX_VISIBLE_ROWS);
|
|
1288
|
-
return /* @__PURE__ */
|
|
1289
|
-
/* @__PURE__ */
|
|
1290
|
-
/* @__PURE__ */
|
|
1291
|
-
|
|
1883
|
+
return /* @__PURE__ */ jsxs15(Box14, { flexDirection: "column", children: [
|
|
1884
|
+
/* @__PURE__ */ jsxs15(Box14, { marginBottom: 1, gap: 1, children: [
|
|
1885
|
+
/* @__PURE__ */ jsx16(
|
|
1886
|
+
Box14,
|
|
1292
1887
|
{
|
|
1293
1888
|
borderStyle: "round",
|
|
1294
|
-
borderColor: tab === "formulae" ?
|
|
1889
|
+
borderColor: tab === "formulae" ? COLORS.info : COLORS.textSecondary,
|
|
1295
1890
|
paddingX: 1,
|
|
1296
|
-
children: /* @__PURE__ */
|
|
1891
|
+
children: /* @__PURE__ */ jsxs15(Text16, { bold: tab === "formulae", color: tab === "formulae" ? COLORS.info : COLORS.textSecondary, children: [
|
|
1297
1892
|
"\u{1F4E6}",
|
|
1298
1893
|
" ",
|
|
1299
1894
|
t("installed_formulaeCount", { count: formulae.length })
|
|
1300
1895
|
] })
|
|
1301
1896
|
}
|
|
1302
1897
|
),
|
|
1303
|
-
/* @__PURE__ */
|
|
1304
|
-
|
|
1898
|
+
/* @__PURE__ */ jsx16(
|
|
1899
|
+
Box14,
|
|
1305
1900
|
{
|
|
1306
1901
|
borderStyle: "round",
|
|
1307
|
-
borderColor: tab === "casks" ?
|
|
1902
|
+
borderColor: tab === "casks" ? COLORS.purple : COLORS.textSecondary,
|
|
1308
1903
|
paddingX: 1,
|
|
1309
|
-
children: /* @__PURE__ */
|
|
1904
|
+
children: /* @__PURE__ */ jsxs15(Text16, { bold: tab === "casks", color: tab === "casks" ? COLORS.purple : COLORS.textSecondary, children: [
|
|
1310
1905
|
"\u{1F37A}",
|
|
1311
1906
|
" ",
|
|
1312
1907
|
t("installed_casksCount", { count: casks.length })
|
|
@@ -1314,7 +1909,7 @@ function InstalledView() {
|
|
|
1314
1909
|
}
|
|
1315
1910
|
)
|
|
1316
1911
|
] }),
|
|
1317
|
-
confirmUninstall && /* @__PURE__ */
|
|
1912
|
+
confirmUninstall && /* @__PURE__ */ jsx16(
|
|
1318
1913
|
ConfirmDialog,
|
|
1319
1914
|
{
|
|
1320
1915
|
message: t("installed_confirmUninstall", { name: confirmUninstall }),
|
|
@@ -1328,49 +1923,48 @@ function InstalledView() {
|
|
|
1328
1923
|
onCancel: () => setConfirmUninstall(null)
|
|
1329
1924
|
}
|
|
1330
1925
|
),
|
|
1331
|
-
isSearching && /* @__PURE__ */
|
|
1332
|
-
/* @__PURE__ */
|
|
1333
|
-
/* @__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: [
|
|
1334
1929
|
" ",
|
|
1335
|
-
"
|
|
1930
|
+
t("installed_col_package").padEnd(nameWidth)
|
|
1336
1931
|
] }),
|
|
1337
|
-
/* @__PURE__ */
|
|
1338
|
-
/* @__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") })
|
|
1339
1934
|
] }),
|
|
1340
|
-
/* @__PURE__ */
|
|
1341
|
-
visible.length === 0 && /* @__PURE__ */
|
|
1342
|
-
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: [
|
|
1343
1938
|
" ",
|
|
1344
1939
|
t("scroll_moreAbove", { count: start })
|
|
1345
1940
|
] }),
|
|
1346
1941
|
visible.map((item, i) => {
|
|
1347
1942
|
const idx = start + i;
|
|
1348
1943
|
const isCurrent = idx === cursor;
|
|
1349
|
-
return /* @__PURE__ */
|
|
1350
|
-
/* @__PURE__ */
|
|
1351
|
-
/* @__PURE__ */
|
|
1352
|
-
/* @__PURE__ */
|
|
1353
|
-
item.
|
|
1354
|
-
item.
|
|
1355
|
-
item.
|
|
1356
|
-
item.installedAsDependency && /* @__PURE__ */
|
|
1357
|
-
!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) })
|
|
1358
1952
|
] }, item.name);
|
|
1359
1953
|
}),
|
|
1360
|
-
start + MAX_VISIBLE_ROWS < allItems.length && /* @__PURE__ */
|
|
1954
|
+
start + MAX_VISIBLE_ROWS < allItems.length && /* @__PURE__ */ jsxs15(Text16, { color: COLORS.textSecondary, dimColor: true, children: [
|
|
1361
1955
|
" ",
|
|
1362
1956
|
t("scroll_moreBelow", { count: allItems.length - start - MAX_VISIBLE_ROWS })
|
|
1363
1957
|
] })
|
|
1364
1958
|
] }),
|
|
1365
|
-
/* @__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" }) })
|
|
1366
1960
|
] });
|
|
1367
1961
|
}
|
|
1368
1962
|
|
|
1369
1963
|
// src/views/search.tsx
|
|
1370
1964
|
import { useState as useState4, useCallback as useCallback2, useEffect as useEffect6, useRef as useRef2 } from "react";
|
|
1371
|
-
import { Box as
|
|
1965
|
+
import { Box as Box15, Text as Text17, useInput as useInput4 } from "ink";
|
|
1372
1966
|
import { TextInput as TextInput2 } from "@inkjs/ui";
|
|
1373
|
-
import { jsx as
|
|
1967
|
+
import { jsx as jsx17, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
1374
1968
|
function SearchView() {
|
|
1375
1969
|
const [query, setQuery] = useState4("");
|
|
1376
1970
|
const [results, setResults] = useState4(null);
|
|
@@ -1394,7 +1988,11 @@ function SearchView() {
|
|
|
1394
1988
|
return void 0;
|
|
1395
1989
|
}, [results]);
|
|
1396
1990
|
const doSearch = useCallback2(async (term) => {
|
|
1397
|
-
if (term.length < 2)
|
|
1991
|
+
if (term.length < 2) {
|
|
1992
|
+
setResults(null);
|
|
1993
|
+
setSearchError(t("search_minChars"));
|
|
1994
|
+
return;
|
|
1995
|
+
}
|
|
1398
1996
|
setSearching(true);
|
|
1399
1997
|
setSearchError(null);
|
|
1400
1998
|
try {
|
|
@@ -1403,7 +2001,7 @@ function SearchView() {
|
|
|
1403
2001
|
setCursor(0);
|
|
1404
2002
|
} catch (err) {
|
|
1405
2003
|
setResults({ formulae: [], casks: [] });
|
|
1406
|
-
setSearchError(err instanceof Error ? err.message : "
|
|
2004
|
+
setSearchError(err instanceof Error ? err.message : t("search_failed"));
|
|
1407
2005
|
} finally {
|
|
1408
2006
|
setSearching(false);
|
|
1409
2007
|
}
|
|
@@ -1423,6 +2021,12 @@ function SearchView() {
|
|
|
1423
2021
|
if (key.escape) stream.cancel();
|
|
1424
2022
|
return;
|
|
1425
2023
|
}
|
|
2024
|
+
if (stream.lines.length > 0) {
|
|
2025
|
+
if (key.escape) {
|
|
2026
|
+
stream.clear();
|
|
2027
|
+
}
|
|
2028
|
+
return;
|
|
2029
|
+
}
|
|
1426
2030
|
if (confirmInstall) return;
|
|
1427
2031
|
if (key.return && !results) {
|
|
1428
2032
|
void doSearch(query);
|
|
@@ -1447,8 +2051,8 @@ function SearchView() {
|
|
|
1447
2051
|
}
|
|
1448
2052
|
});
|
|
1449
2053
|
if (stream.isRunning || stream.lines.length > 0) {
|
|
1450
|
-
return /* @__PURE__ */
|
|
1451
|
-
/* @__PURE__ */
|
|
2054
|
+
return /* @__PURE__ */ jsxs16(Box15, { flexDirection: "column", children: [
|
|
2055
|
+
/* @__PURE__ */ jsx17(
|
|
1452
2056
|
ProgressLog,
|
|
1453
2057
|
{
|
|
1454
2058
|
lines: stream.lines,
|
|
@@ -1456,26 +2060,32 @@ function SearchView() {
|
|
|
1456
2060
|
title: t("search_installing")
|
|
1457
2061
|
}
|
|
1458
2062
|
),
|
|
1459
|
-
stream.isRunning && /* @__PURE__ */
|
|
2063
|
+
stream.isRunning && /* @__PURE__ */ jsxs16(Text17, { color: COLORS.textSecondary, children: [
|
|
1460
2064
|
"esc:",
|
|
1461
2065
|
t("hint_cancel")
|
|
1462
2066
|
] }),
|
|
1463
|
-
!stream.isRunning && /* @__PURE__ */
|
|
1464
|
-
/* @__PURE__ */
|
|
1465
|
-
|
|
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: [
|
|
1466
2076
|
"esc:",
|
|
1467
|
-
t("
|
|
2077
|
+
t("hint_clear")
|
|
1468
2078
|
] })
|
|
1469
2079
|
] })
|
|
1470
2080
|
] });
|
|
1471
2081
|
}
|
|
1472
|
-
return /* @__PURE__ */
|
|
1473
|
-
/* @__PURE__ */
|
|
1474
|
-
/* @__PURE__ */
|
|
2082
|
+
return /* @__PURE__ */ jsxs16(Box15, { flexDirection: "column", children: [
|
|
2083
|
+
/* @__PURE__ */ jsxs16(Box15, { marginBottom: 1, children: [
|
|
2084
|
+
/* @__PURE__ */ jsxs16(Text17, { color: COLORS.gold, children: [
|
|
1475
2085
|
"\u{1F50D}",
|
|
1476
2086
|
" "
|
|
1477
2087
|
] }),
|
|
1478
|
-
!results ? /* @__PURE__ */
|
|
2088
|
+
!results ? /* @__PURE__ */ jsx17(
|
|
1479
2089
|
TextInput2,
|
|
1480
2090
|
{
|
|
1481
2091
|
placeholder: t("search_placeholder"),
|
|
@@ -1483,17 +2093,17 @@ function SearchView() {
|
|
|
1483
2093
|
onChange: setQuery,
|
|
1484
2094
|
onSubmit: () => void doSearch(query)
|
|
1485
2095
|
}
|
|
1486
|
-
) : /* @__PURE__ */
|
|
2096
|
+
) : /* @__PURE__ */ jsxs16(Text17, { children: [
|
|
1487
2097
|
t("search_resultsFor"),
|
|
1488
2098
|
' "',
|
|
1489
|
-
/* @__PURE__ */
|
|
2099
|
+
/* @__PURE__ */ jsx17(Text17, { bold: true, color: COLORS.text, children: query }),
|
|
1490
2100
|
'" ',
|
|
1491
|
-
/* @__PURE__ */
|
|
2101
|
+
/* @__PURE__ */ jsx17(Text17, { color: COLORS.textSecondary, children: t("search_escToClear") })
|
|
1492
2102
|
] })
|
|
1493
2103
|
] }),
|
|
1494
|
-
searching && /* @__PURE__ */
|
|
1495
|
-
searchError && /* @__PURE__ */
|
|
1496
|
-
confirmInstall && /* @__PURE__ */
|
|
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(
|
|
1497
2107
|
ConfirmDialog,
|
|
1498
2108
|
{
|
|
1499
2109
|
message: t("search_confirmInstall", { name: confirmInstall }),
|
|
@@ -1506,46 +2116,40 @@ function SearchView() {
|
|
|
1506
2116
|
onCancel: () => setConfirmInstall(null)
|
|
1507
2117
|
}
|
|
1508
2118
|
),
|
|
1509
|
-
results && !searching && !confirmInstall && /* @__PURE__ */
|
|
1510
|
-
visibleFormulae.length > 0 && /* @__PURE__ */
|
|
1511
|
-
/* @__PURE__ */
|
|
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 }) }),
|
|
1512
2122
|
visibleFormulae.map((name, i) => {
|
|
1513
2123
|
const isCurrent = i === cursor;
|
|
1514
|
-
return /* @__PURE__ */
|
|
1515
|
-
/* @__PURE__ */ jsx15(Text15, { color: isCurrent ? "#22C55E" : "#9CA3AF", children: isCurrent ? "\u25B6" : " " }),
|
|
1516
|
-
/* @__PURE__ */ jsx15(Text15, { bold: isCurrent, inverse: isCurrent, children: name })
|
|
1517
|
-
] }, name);
|
|
2124
|
+
return /* @__PURE__ */ jsx17(SelectableRow, { isCurrent, children: /* @__PURE__ */ jsx17(Text17, { bold: isCurrent, inverse: isCurrent, children: name }) }, name);
|
|
1518
2125
|
}),
|
|
1519
|
-
results.formulae.length > MAX_VISIBLE && /* @__PURE__ */
|
|
2126
|
+
results.formulae.length > MAX_VISIBLE && /* @__PURE__ */ jsxs16(Text17, { color: COLORS.textSecondary, dimColor: true, children: [
|
|
1520
2127
|
" ",
|
|
1521
2128
|
t("scroll_moreBelow", { count: results.formulae.length - MAX_VISIBLE })
|
|
1522
2129
|
] })
|
|
1523
2130
|
] }),
|
|
1524
|
-
visibleCasks.length > 0 && /* @__PURE__ */
|
|
1525
|
-
/* @__PURE__ */
|
|
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 }) }),
|
|
1526
2133
|
visibleCasks.map((name, i) => {
|
|
1527
2134
|
const idx = visibleFormulae.length + i;
|
|
1528
2135
|
const isCurrent = idx === cursor;
|
|
1529
|
-
return /* @__PURE__ */
|
|
1530
|
-
/* @__PURE__ */ jsx15(Text15, { color: isCurrent ? "#22C55E" : "#9CA3AF", children: isCurrent ? "\u25B6" : " " }),
|
|
1531
|
-
/* @__PURE__ */ jsx15(Text15, { bold: isCurrent, inverse: isCurrent, children: name })
|
|
1532
|
-
] }, name);
|
|
2136
|
+
return /* @__PURE__ */ jsx17(SelectableRow, { isCurrent, children: /* @__PURE__ */ jsx17(Text17, { bold: isCurrent, inverse: isCurrent, children: name }) }, name);
|
|
1533
2137
|
}),
|
|
1534
|
-
results.casks.length > MAX_VISIBLE && /* @__PURE__ */
|
|
2138
|
+
results.casks.length > MAX_VISIBLE && /* @__PURE__ */ jsxs16(Text17, { color: COLORS.textSecondary, dimColor: true, children: [
|
|
1535
2139
|
" ",
|
|
1536
2140
|
t("scroll_moreBelow", { count: results.casks.length - MAX_VISIBLE })
|
|
1537
2141
|
] })
|
|
1538
2142
|
] }),
|
|
1539
|
-
allVisible.length === 0 && /* @__PURE__ */
|
|
1540
|
-
/* @__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}` : "" }) })
|
|
1541
2145
|
] })
|
|
1542
2146
|
] });
|
|
1543
2147
|
}
|
|
1544
2148
|
|
|
1545
2149
|
// src/views/outdated.tsx
|
|
1546
2150
|
import { useEffect as useEffect7, useRef as useRef3, useState as useState5 } from "react";
|
|
1547
|
-
import { Box as
|
|
1548
|
-
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";
|
|
1549
2153
|
function OutdatedView() {
|
|
1550
2154
|
const { outdated, loading, errors, fetchOutdated } = useBrewStore();
|
|
1551
2155
|
const stream = useBrewStream();
|
|
@@ -1567,6 +2171,17 @@ function OutdatedView() {
|
|
|
1567
2171
|
if (key.escape) stream.cancel();
|
|
1568
2172
|
return;
|
|
1569
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
|
+
}
|
|
1570
2185
|
if (confirmAction) return;
|
|
1571
2186
|
if (input === "j" || key.downArrow) {
|
|
1572
2187
|
setCursor((c) => Math.min(c + 1, Math.max(0, allOutdated.length - 1)));
|
|
@@ -1578,21 +2193,21 @@ function OutdatedView() {
|
|
|
1578
2193
|
setConfirmAction({ type: "all" });
|
|
1579
2194
|
} else if (input === "p" && allOutdated[cursor]) {
|
|
1580
2195
|
const pkg = allOutdated[cursor];
|
|
1581
|
-
void
|
|
2196
|
+
void (pkg.pinned ? unpinPackage(pkg.name) : pinPackage(pkg.name)).then(() => void fetchOutdated());
|
|
1582
2197
|
return;
|
|
1583
2198
|
} else if (input === "r") {
|
|
1584
2199
|
void fetchOutdated();
|
|
1585
2200
|
}
|
|
1586
2201
|
});
|
|
1587
|
-
const { stdout } =
|
|
2202
|
+
const { stdout } = useStdout3();
|
|
1588
2203
|
const MAX_VISIBLE_ROWS = Math.max(5, (stdout?.rows ?? 24) - 8);
|
|
1589
2204
|
const start = Math.max(0, cursor - Math.floor(MAX_VISIBLE_ROWS / 2));
|
|
1590
2205
|
const visible = allOutdated.slice(start, start + MAX_VISIBLE_ROWS);
|
|
1591
|
-
if (loading.outdated) return /* @__PURE__ */
|
|
1592
|
-
if (errors.outdated) return /* @__PURE__ */
|
|
2206
|
+
if (loading.outdated) return /* @__PURE__ */ jsx18(Loading, { message: t("loading_outdated") });
|
|
2207
|
+
if (errors.outdated) return /* @__PURE__ */ jsx18(ErrorMessage, { message: errors.outdated });
|
|
1593
2208
|
if (stream.isRunning || stream.lines.length > 0) {
|
|
1594
|
-
return /* @__PURE__ */
|
|
1595
|
-
/* @__PURE__ */
|
|
2209
|
+
return /* @__PURE__ */ jsxs17(Box16, { flexDirection: "column", children: [
|
|
2210
|
+
/* @__PURE__ */ jsx18(
|
|
1596
2211
|
ProgressLog,
|
|
1597
2212
|
{
|
|
1598
2213
|
lines: stream.lines,
|
|
@@ -1600,33 +2215,35 @@ function OutdatedView() {
|
|
|
1600
2215
|
title: t("outdated_upgrading")
|
|
1601
2216
|
}
|
|
1602
2217
|
),
|
|
1603
|
-
stream.isRunning && /* @__PURE__ */
|
|
2218
|
+
stream.isRunning && /* @__PURE__ */ jsxs17(Text18, { color: COLORS.textSecondary, children: [
|
|
1604
2219
|
"esc:",
|
|
1605
2220
|
t("hint_cancel")
|
|
1606
2221
|
] }),
|
|
1607
|
-
!stream.isRunning && /* @__PURE__ */
|
|
1608
|
-
/* @__PURE__ */
|
|
1609
|
-
/* @__PURE__ */
|
|
1610
|
-
/* @__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: [
|
|
1611
2226
|
" ",
|
|
1612
2227
|
t("outdated_pressRefresh")
|
|
1613
2228
|
] })
|
|
1614
2229
|
] }),
|
|
1615
|
-
/* @__PURE__ */
|
|
2230
|
+
/* @__PURE__ */ jsxs17(Text18, { color: COLORS.textSecondary, children: [
|
|
1616
2231
|
"r:",
|
|
1617
2232
|
t("hint_refresh"),
|
|
1618
2233
|
" esc:",
|
|
1619
|
-
t("
|
|
2234
|
+
t("hint_clear")
|
|
1620
2235
|
] })
|
|
1621
2236
|
] })
|
|
1622
2237
|
] });
|
|
1623
2238
|
}
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
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(
|
|
1627
2244
|
ConfirmDialog,
|
|
1628
2245
|
{
|
|
1629
|
-
message: confirmAction.type === "all" ?
|
|
2246
|
+
message: confirmAction.type === "all" ? upgradeAllMessage : t("outdated_confirmSingle", { name: confirmAction.type === "single" ? confirmAction.name : "" }),
|
|
1630
2247
|
onConfirm: () => {
|
|
1631
2248
|
hasRefreshed.current = false;
|
|
1632
2249
|
if (confirmAction.type === "all") {
|
|
@@ -1639,31 +2256,26 @@ function OutdatedView() {
|
|
|
1639
2256
|
onCancel: () => setConfirmAction(null)
|
|
1640
2257
|
}
|
|
1641
2258
|
) }),
|
|
1642
|
-
allOutdated.length === 0 && !confirmAction && /* @__PURE__ */
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
t("outdated_upToDate")
|
|
1646
|
-
] }) }) }),
|
|
1647
|
-
allOutdated.length > 0 && !confirmAction && /* @__PURE__ */ jsxs16(Box14, { flexDirection: "column", marginTop: 1, children: [
|
|
1648
|
-
start > 0 && /* @__PURE__ */ jsxs16(Text16, { color: "#6B7280", dimColor: true, children: [
|
|
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: [
|
|
1649
2262
|
" ",
|
|
1650
2263
|
t("scroll_moreAbove", { count: start })
|
|
1651
2264
|
] }),
|
|
1652
2265
|
visible.map((pkg, i) => {
|
|
1653
2266
|
const idx = start + i;
|
|
1654
2267
|
const isCurrent = idx === cursor;
|
|
1655
|
-
return /* @__PURE__ */
|
|
1656
|
-
/* @__PURE__ */
|
|
1657
|
-
/* @__PURE__ */
|
|
1658
|
-
/* @__PURE__ */
|
|
1659
|
-
pkg.pinned && /* @__PURE__ */ jsx16(StatusBadge, { label: t("outdated_pinned"), variant: "info" })
|
|
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" })
|
|
1660
2272
|
] }, pkg.name);
|
|
1661
2273
|
}),
|
|
1662
|
-
start + MAX_VISIBLE_ROWS < allOutdated.length && /* @__PURE__ */
|
|
2274
|
+
start + MAX_VISIBLE_ROWS < allOutdated.length && /* @__PURE__ */ jsxs17(Text18, { color: COLORS.textSecondary, dimColor: true, children: [
|
|
1663
2275
|
" ",
|
|
1664
2276
|
t("scroll_moreBelow", { count: allOutdated.length - start - MAX_VISIBLE_ROWS })
|
|
1665
2277
|
] }),
|
|
1666
|
-
/* @__PURE__ */
|
|
2278
|
+
/* @__PURE__ */ jsx18(Box16, { marginTop: 1, children: /* @__PURE__ */ jsxs17(Text18, { color: COLORS.text, bold: true, children: [
|
|
1667
2279
|
cursor + 1,
|
|
1668
2280
|
"/",
|
|
1669
2281
|
allOutdated.length
|
|
@@ -1674,8 +2286,8 @@ function OutdatedView() {
|
|
|
1674
2286
|
|
|
1675
2287
|
// src/views/package-info.tsx
|
|
1676
2288
|
import { useEffect as useEffect8, useRef as useRef4, useState as useState6 } from "react";
|
|
1677
|
-
import { Box as
|
|
1678
|
-
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";
|
|
1679
2291
|
var ACTION_PROGRESS_KEYS = {
|
|
1680
2292
|
install: "pkgInfo_installing",
|
|
1681
2293
|
uninstall: "pkgInfo_uninstalling",
|
|
@@ -1688,6 +2300,7 @@ var ACTION_CONFIRM_KEYS = {
|
|
|
1688
2300
|
};
|
|
1689
2301
|
function PackageInfoView() {
|
|
1690
2302
|
const packageName = useNavigationStore((s) => s.selectedPackage);
|
|
2303
|
+
const packageType = useNavigationStore((s) => s.selectedPackageType);
|
|
1691
2304
|
const [formula, setFormula] = useState6(null);
|
|
1692
2305
|
const [loading, setLoading2] = useState6(true);
|
|
1693
2306
|
const [error, setError2] = useState6(null);
|
|
@@ -1705,22 +2318,60 @@ function PackageInfoView() {
|
|
|
1705
2318
|
useEffect8(() => {
|
|
1706
2319
|
if (!packageName) return;
|
|
1707
2320
|
setLoading2(true);
|
|
1708
|
-
|
|
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);
|
|
1709
2358
|
if (mountedRef.current) {
|
|
1710
2359
|
setFormula(f);
|
|
1711
2360
|
setLoading2(false);
|
|
1712
2361
|
}
|
|
1713
|
-
}
|
|
2362
|
+
};
|
|
2363
|
+
fetchInfo().catch((err) => {
|
|
1714
2364
|
if (mountedRef.current) {
|
|
1715
2365
|
setError2(err.message);
|
|
1716
2366
|
setLoading2(false);
|
|
1717
2367
|
}
|
|
1718
2368
|
});
|
|
1719
|
-
}, [packageName]);
|
|
2369
|
+
}, [packageName, packageType]);
|
|
1720
2370
|
useEffect8(() => {
|
|
1721
2371
|
if (!stream.isRunning && !stream.error && stream.lines.length > 0 && !hasRefreshed.current && packageName) {
|
|
1722
2372
|
hasRefreshed.current = true;
|
|
1723
|
-
|
|
2373
|
+
const refreshFn = packageType === "cask" ? getCaskInfo(packageName).then((c) => c ? { ...c, installed: c.installed ? [{ version: c.installed }] : [] } : null) : getFormulaInfo(packageName);
|
|
2374
|
+
refreshFn.then((f) => {
|
|
1724
2375
|
if (mountedRef.current) {
|
|
1725
2376
|
setFormula(f);
|
|
1726
2377
|
}
|
|
@@ -1745,21 +2396,21 @@ function PackageInfoView() {
|
|
|
1745
2396
|
}
|
|
1746
2397
|
});
|
|
1747
2398
|
if (!packageName) {
|
|
1748
|
-
return /* @__PURE__ */
|
|
2399
|
+
return /* @__PURE__ */ jsx19(Text19, { color: COLORS.textSecondary, italic: true, children: t("pkgInfo_noPackage") });
|
|
1749
2400
|
}
|
|
1750
|
-
if (loading) return /* @__PURE__ */
|
|
1751
|
-
if (error) return /* @__PURE__ */
|
|
1752
|
-
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") });
|
|
1753
2404
|
if (stream.isRunning || stream.lines.length > 0) {
|
|
1754
|
-
return /* @__PURE__ */
|
|
1755
|
-
/* @__PURE__ */
|
|
1756
|
-
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: [
|
|
1757
2408
|
"esc:",
|
|
1758
2409
|
t("hint_cancel")
|
|
1759
2410
|
] }),
|
|
1760
|
-
!stream.isRunning && /* @__PURE__ */
|
|
1761
|
-
/* @__PURE__ */
|
|
1762
|
-
/* @__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: [
|
|
1763
2414
|
"esc:",
|
|
1764
2415
|
t("hint_back")
|
|
1765
2416
|
] })
|
|
@@ -1768,8 +2419,8 @@ function PackageInfoView() {
|
|
|
1768
2419
|
}
|
|
1769
2420
|
const installed = formula.installed[0];
|
|
1770
2421
|
const isInstalled = formula.installed.length > 0;
|
|
1771
|
-
return /* @__PURE__ */
|
|
1772
|
-
confirmAction && /* @__PURE__ */
|
|
2422
|
+
return /* @__PURE__ */ jsxs18(Box17, { flexDirection: "column", children: [
|
|
2423
|
+
confirmAction && /* @__PURE__ */ jsx19(
|
|
1773
2424
|
ConfirmDialog,
|
|
1774
2425
|
{
|
|
1775
2426
|
message: t(ACTION_CONFIRM_KEYS[confirmAction], { name: formula.name }),
|
|
@@ -1785,72 +2436,72 @@ function PackageInfoView() {
|
|
|
1785
2436
|
onCancel: () => setConfirmAction(null)
|
|
1786
2437
|
}
|
|
1787
2438
|
),
|
|
1788
|
-
/* @__PURE__ */
|
|
1789
|
-
/* @__PURE__ */
|
|
1790
|
-
/* @__PURE__ */
|
|
1791
|
-
isInstalled && /* @__PURE__ */
|
|
1792
|
-
formula.outdated && /* @__PURE__ */
|
|
1793
|
-
formula.pinned && /* @__PURE__ */
|
|
1794
|
-
formula.keg_only && /* @__PURE__ */
|
|
1795
|
-
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" })
|
|
1796
2447
|
] }),
|
|
1797
|
-
/* @__PURE__ */
|
|
1798
|
-
/* @__PURE__ */
|
|
1799
|
-
/* @__PURE__ */
|
|
1800
|
-
/* @__PURE__ */
|
|
1801
|
-
/* @__PURE__ */
|
|
1802
|
-
/* @__PURE__ */
|
|
1803
|
-
/* @__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") }),
|
|
1804
2455
|
" ",
|
|
1805
2456
|
formula.homepage
|
|
1806
2457
|
] }),
|
|
1807
|
-
/* @__PURE__ */
|
|
1808
|
-
/* @__PURE__ */
|
|
2458
|
+
/* @__PURE__ */ jsxs18(Text19, { children: [
|
|
2459
|
+
/* @__PURE__ */ jsx19(Text19, { color: COLORS.muted, children: t("pkgInfo_license") }),
|
|
1809
2460
|
" ",
|
|
1810
2461
|
formula.license
|
|
1811
2462
|
] }),
|
|
1812
|
-
/* @__PURE__ */
|
|
1813
|
-
/* @__PURE__ */
|
|
2463
|
+
/* @__PURE__ */ jsxs18(Text19, { children: [
|
|
2464
|
+
/* @__PURE__ */ jsx19(Text19, { color: COLORS.muted, children: t("pkgInfo_tap") }),
|
|
1814
2465
|
" ",
|
|
1815
2466
|
formula.tap
|
|
1816
2467
|
] }),
|
|
1817
|
-
/* @__PURE__ */
|
|
1818
|
-
/* @__PURE__ */
|
|
2468
|
+
/* @__PURE__ */ jsxs18(Text19, { children: [
|
|
2469
|
+
/* @__PURE__ */ jsx19(Text19, { color: COLORS.muted, children: t("pkgInfo_stable") }),
|
|
1819
2470
|
" ",
|
|
1820
2471
|
formula.versions.stable
|
|
1821
2472
|
] }),
|
|
1822
|
-
installed && /* @__PURE__ */
|
|
1823
|
-
/* @__PURE__ */
|
|
1824
|
-
/* @__PURE__ */
|
|
2473
|
+
installed && /* @__PURE__ */ jsxs18(Fragment4, { children: [
|
|
2474
|
+
/* @__PURE__ */ jsxs18(Text19, { children: [
|
|
2475
|
+
/* @__PURE__ */ jsx19(Text19, { color: COLORS.muted, children: t("pkgInfo_installed") }),
|
|
1825
2476
|
" ",
|
|
1826
2477
|
installed.version,
|
|
1827
2478
|
" (",
|
|
1828
2479
|
formatRelativeTime(installed.time),
|
|
1829
2480
|
")"
|
|
1830
2481
|
] }),
|
|
1831
|
-
/* @__PURE__ */
|
|
1832
|
-
/* @__PURE__ */
|
|
2482
|
+
/* @__PURE__ */ jsxs18(Text19, { children: [
|
|
2483
|
+
/* @__PURE__ */ jsx19(Text19, { color: COLORS.muted, children: t("pkgInfo_bottle") }),
|
|
1833
2484
|
" ",
|
|
1834
2485
|
installed.poured_from_bottle ? t("common_yes") : t("common_no")
|
|
1835
2486
|
] }),
|
|
1836
|
-
/* @__PURE__ */
|
|
1837
|
-
/* @__PURE__ */
|
|
2487
|
+
/* @__PURE__ */ jsxs18(Text19, { children: [
|
|
2488
|
+
/* @__PURE__ */ jsx19(Text19, { color: COLORS.muted, children: t("pkgInfo_onRequest") }),
|
|
1838
2489
|
" ",
|
|
1839
2490
|
installed.installed_on_request ? t("common_yes") : t("pkgInfo_noDependency")
|
|
1840
2491
|
] })
|
|
1841
2492
|
] })
|
|
1842
2493
|
] })
|
|
1843
2494
|
] }),
|
|
1844
|
-
formula.dependencies.length > 0 && /* @__PURE__ */
|
|
1845
|
-
/* @__PURE__ */
|
|
1846
|
-
/* @__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)) })
|
|
1847
2498
|
] }),
|
|
1848
|
-
formula.caveats && /* @__PURE__ */
|
|
1849
|
-
/* @__PURE__ */
|
|
1850
|
-
/* @__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 }) })
|
|
1851
2502
|
] })
|
|
1852
2503
|
] }),
|
|
1853
|
-
/* @__PURE__ */
|
|
2504
|
+
/* @__PURE__ */ jsx19(Box17, { marginTop: 1, children: /* @__PURE__ */ jsxs18(Text19, { color: COLORS.textSecondary, children: [
|
|
1854
2505
|
isInstalled ? `u:${t("hint_uninstall")}` : `i:${t("hint_install")}`,
|
|
1855
2506
|
isInstalled && formula.outdated ? ` U:${t("hint_upgrade")}` : "",
|
|
1856
2507
|
` esc:${t("hint_back")}`
|
|
@@ -1860,8 +2511,8 @@ function PackageInfoView() {
|
|
|
1860
2511
|
|
|
1861
2512
|
// src/views/services.tsx
|
|
1862
2513
|
import { useEffect as useEffect9, useState as useState7 } from "react";
|
|
1863
|
-
import { Box as
|
|
1864
|
-
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";
|
|
1865
2516
|
var STATUS_VARIANTS = {
|
|
1866
2517
|
started: "success",
|
|
1867
2518
|
stopped: "muted",
|
|
@@ -1873,16 +2524,21 @@ function ServicesView() {
|
|
|
1873
2524
|
const [cursor, setCursor] = useState7(0);
|
|
1874
2525
|
const [actionInProgress, setActionInProgress] = useState7(false);
|
|
1875
2526
|
const [confirmAction, setConfirmAction] = useState7(null);
|
|
1876
|
-
const
|
|
2527
|
+
const [lastError, setLastError] = useState7(null);
|
|
2528
|
+
const { stdout } = useStdout4();
|
|
1877
2529
|
const cols = stdout?.columns ?? 80;
|
|
1878
2530
|
const svcNameWidth = Math.floor(cols * 0.35);
|
|
1879
2531
|
const svcStatusWidth = Math.floor(cols * 0.15);
|
|
2532
|
+
const MAX_VISIBLE_ROWS = Math.max(5, (stdout?.rows ?? 24) - 10);
|
|
1880
2533
|
useEffect9(() => {
|
|
1881
2534
|
fetchServices();
|
|
1882
2535
|
}, []);
|
|
1883
2536
|
useInput7((input, key) => {
|
|
1884
2537
|
if (actionInProgress) return;
|
|
1885
2538
|
if (confirmAction) return;
|
|
2539
|
+
if (lastError) {
|
|
2540
|
+
setLastError(null);
|
|
2541
|
+
}
|
|
1886
2542
|
if (input === "j" || key.downArrow) {
|
|
1887
2543
|
setCursor((c) => Math.min(c + 1, Math.max(0, services.length - 1)));
|
|
1888
2544
|
} else if (input === "k" || key.upArrow) {
|
|
@@ -1894,26 +2550,31 @@ function ServicesView() {
|
|
|
1894
2550
|
if (!svc) return;
|
|
1895
2551
|
const doAction = (action) => {
|
|
1896
2552
|
setActionInProgress(true);
|
|
1897
|
-
void serviceAction2(svc.name, action).
|
|
2553
|
+
void serviceAction2(svc.name, action).catch((err) => {
|
|
2554
|
+
setLastError(err instanceof Error ? err.message : String(err));
|
|
2555
|
+
}).finally(() => {
|
|
1898
2556
|
setActionInProgress(false);
|
|
2557
|
+
const storeError = useBrewStore.getState().errors["service-action"];
|
|
2558
|
+
if (storeError) setLastError(storeError);
|
|
1899
2559
|
});
|
|
1900
2560
|
};
|
|
1901
2561
|
if (input === "s") doAction("start");
|
|
1902
2562
|
else if (input === "S") setConfirmAction({ type: "stop", name: svc.name });
|
|
1903
2563
|
else if (input === "R") setConfirmAction({ type: "restart", name: svc.name });
|
|
1904
2564
|
});
|
|
1905
|
-
|
|
1906
|
-
if (
|
|
1907
|
-
if (errors.services) return /* @__PURE__ */ jsx18(ErrorMessage, { message: errors.services });
|
|
2565
|
+
if (loading.services) return /* @__PURE__ */ jsx20(Loading, { message: t("loading_services") });
|
|
2566
|
+
if (errors.services) return /* @__PURE__ */ jsx20(ErrorMessage, { message: errors.services });
|
|
1908
2567
|
if (services.length === 0) {
|
|
1909
|
-
return /* @__PURE__ */
|
|
1910
|
-
/* @__PURE__ */
|
|
1911
|
-
/* @__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") })
|
|
1912
2571
|
] });
|
|
1913
2572
|
}
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
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(
|
|
1917
2578
|
ConfirmDialog,
|
|
1918
2579
|
{
|
|
1919
2580
|
message: confirmAction.type === "stop" ? t("services_confirmStop", { name: confirmAction.name }) : t("services_confirmRestart", { name: confirmAction.name }),
|
|
@@ -1921,36 +2582,48 @@ function ServicesView() {
|
|
|
1921
2582
|
const { type, name } = confirmAction;
|
|
1922
2583
|
setConfirmAction(null);
|
|
1923
2584
|
setActionInProgress(true);
|
|
1924
|
-
void serviceAction2(name, type).
|
|
2585
|
+
void serviceAction2(name, type).catch((err) => {
|
|
2586
|
+
setLastError(err instanceof Error ? err.message : String(err));
|
|
2587
|
+
}).finally(() => {
|
|
1925
2588
|
setActionInProgress(false);
|
|
2589
|
+
const storeError = useBrewStore.getState().errors["service-action"];
|
|
2590
|
+
if (storeError) setLastError(storeError);
|
|
1926
2591
|
});
|
|
1927
2592
|
},
|
|
1928
2593
|
onCancel: () => setConfirmAction(null)
|
|
1929
2594
|
}
|
|
1930
2595
|
) }),
|
|
1931
|
-
/* @__PURE__ */
|
|
1932
|
-
/* @__PURE__ */
|
|
1933
|
-
/* @__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: [
|
|
1934
2599
|
" ",
|
|
1935
2600
|
t("services_name").padEnd(svcNameWidth)
|
|
1936
2601
|
] }),
|
|
1937
|
-
/* @__PURE__ */
|
|
1938
|
-
/* @__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") })
|
|
1939
2604
|
] }),
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
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 }) })
|
|
1948
2617
|
] }, svc.name);
|
|
1949
|
-
})
|
|
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
|
+
] })
|
|
1950
2623
|
] }),
|
|
1951
|
-
actionInProgress && /* @__PURE__ */
|
|
1952
|
-
|
|
1953
|
-
/* @__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: [
|
|
1954
2627
|
cursor + 1,
|
|
1955
2628
|
"/",
|
|
1956
2629
|
services.length
|
|
@@ -1959,56 +2632,61 @@ function ServicesView() {
|
|
|
1959
2632
|
}
|
|
1960
2633
|
|
|
1961
2634
|
// src/views/doctor.tsx
|
|
1962
|
-
import { useEffect as useEffect10 } from "react";
|
|
1963
|
-
import { Box as
|
|
1964
|
-
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";
|
|
1965
2638
|
function DoctorView() {
|
|
1966
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
|
+
}, []);
|
|
1967
2647
|
useEffect10(() => {
|
|
1968
2648
|
fetchDoctor();
|
|
1969
2649
|
}, []);
|
|
1970
2650
|
useInput8((input) => {
|
|
1971
2651
|
if (input === "r") void fetchDoctor();
|
|
1972
2652
|
});
|
|
1973
|
-
if (loading.doctor) return /* @__PURE__ */
|
|
1974
|
-
if (errors.doctor) return /* @__PURE__ */
|
|
1975
|
-
return /* @__PURE__ */
|
|
1976
|
-
/* @__PURE__ */
|
|
1977
|
-
/* @__PURE__ */
|
|
1978
|
-
doctorClean && /* @__PURE__ */
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
doctorWarnings.map((warning, i) => /* @__PURE__ */ jsx19(Box17, { flexDirection: "column", marginBottom: 1, borderStyle: "single", borderColor: "#F59E0B", paddingX: 1, children: warning.split("\n").map((line, j) => /* @__PURE__ */ jsx19(Text19, { color: j === 0 ? "#F59E0B" : "#9CA3AF", children: line }, j)) }, warning.slice(0, 50) + i))
|
|
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
|
+
))
|
|
1985
2664
|
] }),
|
|
1986
|
-
/* @__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) : "" }) })
|
|
1987
2666
|
] });
|
|
1988
2667
|
}
|
|
1989
2668
|
|
|
1990
2669
|
// src/views/profiles.tsx
|
|
1991
|
-
import { useEffect as useEffect11, useRef as
|
|
1992
|
-
import { Box as
|
|
1993
|
-
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";
|
|
1994
2672
|
|
|
1995
2673
|
// src/stores/profile-store.ts
|
|
1996
|
-
import { create as
|
|
2674
|
+
import { create as create5 } from "zustand";
|
|
1997
2675
|
|
|
1998
2676
|
// src/lib/profiles/profile-manager.ts
|
|
1999
|
-
import { readFile, writeFile, readdir, rm } from "fs/promises";
|
|
2000
|
-
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";
|
|
2001
2679
|
|
|
2002
2680
|
// src/lib/license/watermark.ts
|
|
2003
|
-
function getWatermark(license) {
|
|
2681
|
+
function getWatermark(license, consent = true) {
|
|
2682
|
+
if (!consent) return "";
|
|
2004
2683
|
if (!license?.customerEmail) return "";
|
|
2005
2684
|
return `Licensed to: ${license.customerEmail}`;
|
|
2006
2685
|
}
|
|
2007
2686
|
|
|
2008
2687
|
// src/lib/profiles/profile-manager.ts
|
|
2009
|
-
function proCheck() {
|
|
2010
|
-
|
|
2011
|
-
requirePro(license, status);
|
|
2688
|
+
function proCheck(isPro) {
|
|
2689
|
+
if (!isPro) throw new Error("Pro license required");
|
|
2012
2690
|
}
|
|
2013
2691
|
var MAX_PROFILE_NAME_LENGTH = 100;
|
|
2014
2692
|
function validateProfileName(name) {
|
|
@@ -2024,10 +2702,10 @@ function validateProfileName(name) {
|
|
|
2024
2702
|
}
|
|
2025
2703
|
function profilePath(name) {
|
|
2026
2704
|
validateProfileName(name);
|
|
2027
|
-
return
|
|
2705
|
+
return join2(PROFILES_DIR, `${basename(name)}.json`);
|
|
2028
2706
|
}
|
|
2029
|
-
async function listProfiles() {
|
|
2030
|
-
proCheck();
|
|
2707
|
+
async function listProfiles(isPro) {
|
|
2708
|
+
proCheck(isPro);
|
|
2031
2709
|
await ensureDataDirs();
|
|
2032
2710
|
try {
|
|
2033
2711
|
const files = await readdir(PROFILES_DIR);
|
|
@@ -2036,9 +2714,9 @@ async function listProfiles() {
|
|
|
2036
2714
|
return [];
|
|
2037
2715
|
}
|
|
2038
2716
|
}
|
|
2039
|
-
async function loadProfile(name) {
|
|
2040
|
-
proCheck();
|
|
2041
|
-
const raw = await
|
|
2717
|
+
async function loadProfile(isPro, name) {
|
|
2718
|
+
proCheck(isPro);
|
|
2719
|
+
const raw = await readFile3(profilePath(name), "utf-8");
|
|
2042
2720
|
let file;
|
|
2043
2721
|
try {
|
|
2044
2722
|
file = JSON.parse(raw);
|
|
@@ -2053,21 +2731,28 @@ async function loadProfile(name) {
|
|
|
2053
2731
|
}
|
|
2054
2732
|
return file.profile;
|
|
2055
2733
|
}
|
|
2056
|
-
async function saveProfile(profile) {
|
|
2057
|
-
proCheck();
|
|
2734
|
+
async function saveProfile(isPro, profile) {
|
|
2735
|
+
proCheck(isPro);
|
|
2058
2736
|
await ensureDataDirs();
|
|
2737
|
+
const filePath = profilePath(profile.name);
|
|
2738
|
+
const tmpPath = filePath + ".tmp";
|
|
2059
2739
|
const file = { version: 1, profile };
|
|
2060
|
-
await
|
|
2740
|
+
await writeFile3(tmpPath, JSON.stringify(file, null, 2), { encoding: "utf-8", mode: 384 });
|
|
2741
|
+
await rename2(tmpPath, filePath);
|
|
2061
2742
|
}
|
|
2062
|
-
async function deleteProfile(name) {
|
|
2063
|
-
proCheck();
|
|
2743
|
+
async function deleteProfile(isPro, name) {
|
|
2744
|
+
proCheck(isPro);
|
|
2064
2745
|
try {
|
|
2065
|
-
await
|
|
2746
|
+
await rm2(profilePath(name));
|
|
2066
2747
|
} catch {
|
|
2067
2748
|
}
|
|
2068
2749
|
}
|
|
2069
|
-
async function exportCurrentSetup(name, description, license = null) {
|
|
2070
|
-
proCheck();
|
|
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
|
+
}
|
|
2071
2756
|
const [installed, leaves, tapsRaw] = await Promise.all([
|
|
2072
2757
|
getInstalled(),
|
|
2073
2758
|
getLeaves(),
|
|
@@ -2084,17 +2769,17 @@ async function exportCurrentSetup(name, description, license = null) {
|
|
|
2084
2769
|
formulae: leaves,
|
|
2085
2770
|
casks,
|
|
2086
2771
|
taps,
|
|
2087
|
-
exportedBy: getWatermark(license)
|
|
2088
|
-
//
|
|
2772
|
+
exportedBy: consent ? getWatermark(license) : ""
|
|
2773
|
+
// SEG-003: Only embed watermark with consent
|
|
2089
2774
|
};
|
|
2090
|
-
await saveProfile(profile);
|
|
2775
|
+
await saveProfile(isPro, profile);
|
|
2091
2776
|
return profile;
|
|
2092
2777
|
}
|
|
2093
|
-
async function updateProfile(oldName, newName, newDescription) {
|
|
2094
|
-
proCheck();
|
|
2095
|
-
const profile = await loadProfile(oldName);
|
|
2778
|
+
async function updateProfile(isPro, oldName, newName, newDescription) {
|
|
2779
|
+
proCheck(isPro);
|
|
2780
|
+
const profile = await loadProfile(isPro, oldName);
|
|
2096
2781
|
if (oldName !== newName) {
|
|
2097
|
-
await deleteProfile(oldName);
|
|
2782
|
+
await deleteProfile(isPro, oldName);
|
|
2098
2783
|
}
|
|
2099
2784
|
const updated = {
|
|
2100
2785
|
...profile,
|
|
@@ -2102,12 +2787,12 @@ async function updateProfile(oldName, newName, newDescription) {
|
|
|
2102
2787
|
description: newDescription,
|
|
2103
2788
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2104
2789
|
};
|
|
2105
|
-
await saveProfile(updated);
|
|
2790
|
+
await saveProfile(isPro, updated);
|
|
2106
2791
|
}
|
|
2107
2792
|
var TAP_PATTERN = /^[a-z0-9][-a-z0-9]*\/[a-z0-9][-a-z0-9]*$/;
|
|
2108
|
-
var
|
|
2109
|
-
async function* importProfile(profile) {
|
|
2110
|
-
proCheck();
|
|
2793
|
+
var PKG_PATTERN2 = /^[a-z0-9][-a-z0-9_.@+]*$/;
|
|
2794
|
+
async function* importProfile(isPro, profile) {
|
|
2795
|
+
proCheck(isPro);
|
|
2111
2796
|
const installed = await getInstalled();
|
|
2112
2797
|
const installedFormulae = new Set(installed.formulae.map((f) => f.name));
|
|
2113
2798
|
const installedCasks = new Set(installed.casks.filter((c) => c.installed).map((c) => c.token));
|
|
@@ -2124,7 +2809,7 @@ async function* importProfile(profile) {
|
|
|
2124
2809
|
}
|
|
2125
2810
|
const missingFormulae = profile.formulae.filter((f) => !installedFormulae.has(f));
|
|
2126
2811
|
for (const name of missingFormulae) {
|
|
2127
|
-
if (!
|
|
2812
|
+
if (!PKG_PATTERN2.test(name)) {
|
|
2128
2813
|
yield `Skipping invalid formula name: ${name}`;
|
|
2129
2814
|
continue;
|
|
2130
2815
|
}
|
|
@@ -2135,7 +2820,7 @@ async function* importProfile(profile) {
|
|
|
2135
2820
|
}
|
|
2136
2821
|
const missingCasks = profile.casks.filter((c) => !installedCasks.has(c));
|
|
2137
2822
|
for (const name of missingCasks) {
|
|
2138
|
-
if (!
|
|
2823
|
+
if (!PKG_PATTERN2.test(name)) {
|
|
2139
2824
|
yield `Skipping invalid cask name: ${name}`;
|
|
2140
2825
|
continue;
|
|
2141
2826
|
}
|
|
@@ -2149,20 +2834,23 @@ async function* importProfile(profile) {
|
|
|
2149
2834
|
}
|
|
2150
2835
|
|
|
2151
2836
|
// src/stores/profile-store.ts
|
|
2152
|
-
|
|
2837
|
+
function getIsPro() {
|
|
2838
|
+
return useLicenseStore.getState().isPro();
|
|
2839
|
+
}
|
|
2840
|
+
var useProfileStore = create5((set) => ({
|
|
2153
2841
|
profileNames: [],
|
|
2154
2842
|
selectedProfile: null,
|
|
2155
2843
|
loading: false,
|
|
2156
2844
|
loadError: null,
|
|
2157
2845
|
fetchProfiles: async () => {
|
|
2158
2846
|
set({ loading: true });
|
|
2159
|
-
const names = await listProfiles();
|
|
2847
|
+
const names = await listProfiles(getIsPro());
|
|
2160
2848
|
set({ profileNames: names, loading: false });
|
|
2161
2849
|
},
|
|
2162
2850
|
loadProfile: async (name) => {
|
|
2163
2851
|
set({ loadError: null });
|
|
2164
2852
|
try {
|
|
2165
|
-
const profile = await loadProfile(name);
|
|
2853
|
+
const profile = await loadProfile(getIsPro(), name);
|
|
2166
2854
|
set({ selectedProfile: profile });
|
|
2167
2855
|
} catch (err) {
|
|
2168
2856
|
set({ loadError: err instanceof Error ? err.message : String(err) });
|
|
@@ -2172,8 +2860,8 @@ var useProfileStore = create4((set) => ({
|
|
|
2172
2860
|
set({ loading: true, loadError: null });
|
|
2173
2861
|
try {
|
|
2174
2862
|
const license = useLicenseStore.getState().license;
|
|
2175
|
-
await exportCurrentSetup(name, description, license);
|
|
2176
|
-
const names = await listProfiles();
|
|
2863
|
+
await exportCurrentSetup(getIsPro(), name, description, license);
|
|
2864
|
+
const names = await listProfiles(getIsPro());
|
|
2177
2865
|
set({ profileNames: names, loading: false });
|
|
2178
2866
|
} catch (err) {
|
|
2179
2867
|
set({ loading: false, loadError: err instanceof Error ? err.message : String(err) });
|
|
@@ -2181,16 +2869,16 @@ var useProfileStore = create4((set) => ({
|
|
|
2181
2869
|
}
|
|
2182
2870
|
},
|
|
2183
2871
|
deleteProfile: async (name) => {
|
|
2184
|
-
await deleteProfile(name);
|
|
2185
|
-
const names = await listProfiles();
|
|
2872
|
+
await deleteProfile(getIsPro(), name);
|
|
2873
|
+
const names = await listProfiles(getIsPro());
|
|
2186
2874
|
set({ profileNames: names, selectedProfile: null });
|
|
2187
2875
|
},
|
|
2188
2876
|
updateProfile: async (oldName, newName, newDescription) => {
|
|
2189
2877
|
set({ loadError: null });
|
|
2190
2878
|
try {
|
|
2191
|
-
await updateProfile(oldName, newName, newDescription);
|
|
2192
|
-
const names = await listProfiles();
|
|
2193
|
-
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);
|
|
2194
2882
|
set({ profileNames: names, selectedProfile: updated });
|
|
2195
2883
|
} catch (err) {
|
|
2196
2884
|
set({ loadError: err instanceof Error ? err.message : String(err) });
|
|
@@ -2198,8 +2886,141 @@ var useProfileStore = create4((set) => ({
|
|
|
2198
2886
|
}
|
|
2199
2887
|
}));
|
|
2200
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
|
+
|
|
2201
3022
|
// src/views/profiles.tsx
|
|
2202
|
-
import { jsx as
|
|
3023
|
+
import { jsx as jsx26, jsxs as jsxs25 } from "react/jsx-runtime";
|
|
2203
3024
|
function ProfilesView() {
|
|
2204
3025
|
const { profileNames, selectedProfile, loading, loadError, fetchProfiles, loadProfile: loadProfile2, exportCurrent, deleteProfile: deleteProfile2, updateProfile: updateProfile2 } = useProfileStore();
|
|
2205
3026
|
const [cursor, setCursor] = useState8(0);
|
|
@@ -2210,9 +3031,11 @@ function ProfilesView() {
|
|
|
2210
3031
|
const [editDesc, setEditDesc] = useState8("");
|
|
2211
3032
|
const [importLines, setImportLines] = useState8([]);
|
|
2212
3033
|
const [importRunning, setImportRunning] = useState8(false);
|
|
3034
|
+
const [importHadError, setImportHadError] = useState8(false);
|
|
3035
|
+
const [importProfile2, setImportProfile] = useState8(null);
|
|
2213
3036
|
const { openModal, closeModal } = useModalStore();
|
|
2214
|
-
const importGenRef =
|
|
2215
|
-
const mountedRef =
|
|
3037
|
+
const importGenRef = useRef6(null);
|
|
3038
|
+
const mountedRef = useRef6(true);
|
|
2216
3039
|
useEffect11(() => {
|
|
2217
3040
|
fetchProfiles();
|
|
2218
3041
|
}, []);
|
|
@@ -2225,7 +3048,7 @@ function ProfilesView() {
|
|
|
2225
3048
|
};
|
|
2226
3049
|
}, []);
|
|
2227
3050
|
useEffect11(() => {
|
|
2228
|
-
if (mode
|
|
3051
|
+
if (mode !== "list") {
|
|
2229
3052
|
openModal();
|
|
2230
3053
|
return () => {
|
|
2231
3054
|
closeModal();
|
|
@@ -2249,7 +3072,7 @@ function ProfilesView() {
|
|
|
2249
3072
|
return;
|
|
2250
3073
|
}
|
|
2251
3074
|
if (input === "i" && profileNames[cursor]) {
|
|
2252
|
-
void
|
|
3075
|
+
void prepareImport(profileNames[cursor]);
|
|
2253
3076
|
return;
|
|
2254
3077
|
}
|
|
2255
3078
|
if (input === "j" || key.downArrow) setCursor((c) => Math.min(c + 1, Math.max(0, profileNames.length - 1)));
|
|
@@ -2269,13 +3092,26 @@ function ProfilesView() {
|
|
|
2269
3092
|
useInput9(() => {
|
|
2270
3093
|
setMode("list");
|
|
2271
3094
|
}, { isActive: mode === "importing" && !importRunning });
|
|
2272
|
-
const
|
|
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) => {
|
|
2273
3108
|
setMode("importing");
|
|
2274
3109
|
setImportLines([]);
|
|
2275
3110
|
setImportRunning(true);
|
|
3111
|
+
setImportHadError(false);
|
|
2276
3112
|
try {
|
|
2277
|
-
const
|
|
2278
|
-
const gen = importProfile(profile);
|
|
3113
|
+
const isPro = useLicenseStore.getState().isPro();
|
|
3114
|
+
const gen = importProfile(isPro, profile);
|
|
2279
3115
|
importGenRef.current = gen;
|
|
2280
3116
|
for await (const line of gen) {
|
|
2281
3117
|
if (!mountedRef.current) break;
|
|
@@ -2283,6 +3119,7 @@ function ProfilesView() {
|
|
|
2283
3119
|
}
|
|
2284
3120
|
} catch (err) {
|
|
2285
3121
|
if (mountedRef.current) {
|
|
3122
|
+
setImportHadError(true);
|
|
2286
3123
|
setImportLines((prev) => [...prev, `${t("error_prefix")}${err instanceof Error ? err.message : err}`]);
|
|
2287
3124
|
}
|
|
2288
3125
|
} finally {
|
|
@@ -2292,163 +3129,105 @@ function ProfilesView() {
|
|
|
2292
3129
|
}
|
|
2293
3130
|
}
|
|
2294
3131
|
};
|
|
2295
|
-
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
|
+
}
|
|
2296
3153
|
if (mode === "importing") {
|
|
2297
|
-
return /* @__PURE__ */
|
|
2298
|
-
/* @__PURE__ */
|
|
2299
|
-
!importRunning && /* @__PURE__ */
|
|
2300
|
-
"\u2714",
|
|
2301
|
-
" ",
|
|
2302
|
-
t("profiles_importComplete")
|
|
2303
|
-
] }) }) })
|
|
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")}` }) })
|
|
2304
3157
|
] });
|
|
2305
3158
|
}
|
|
2306
3159
|
if (mode === "create-name") {
|
|
2307
|
-
return /* @__PURE__ */
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
{
|
|
2312
|
-
placeholder: t("profiles_namePlaceholder"),
|
|
2313
|
-
onSubmit: (val) => {
|
|
2314
|
-
setNewName(val);
|
|
2315
|
-
setMode("create-desc");
|
|
2316
|
-
}
|
|
2317
|
-
}
|
|
2318
|
-
)
|
|
2319
|
-
] });
|
|
3160
|
+
return /* @__PURE__ */ jsx26(ProfileCreateName, { onSubmit: (val) => {
|
|
3161
|
+
setNewName(val);
|
|
3162
|
+
setMode("create-desc");
|
|
3163
|
+
} });
|
|
2320
3164
|
}
|
|
2321
3165
|
if (mode === "create-desc") {
|
|
2322
|
-
return /* @__PURE__ */
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
loadError
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
try {
|
|
2334
|
-
await exportCurrent(newName, val);
|
|
2335
|
-
} finally {
|
|
2336
|
-
setMode("list");
|
|
2337
|
-
setNewName("");
|
|
2338
|
-
}
|
|
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("");
|
|
2339
3177
|
}
|
|
2340
3178
|
}
|
|
2341
|
-
|
|
2342
|
-
|
|
3179
|
+
}
|
|
3180
|
+
);
|
|
2343
3181
|
}
|
|
2344
3182
|
if (mode === "edit-name") {
|
|
2345
|
-
return /* @__PURE__ */
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
{
|
|
2350
|
-
defaultValue: editName,
|
|
2351
|
-
onSubmit: (val) => {
|
|
2352
|
-
setEditName(val);
|
|
2353
|
-
setMode("edit-desc");
|
|
2354
|
-
}
|
|
2355
|
-
}
|
|
2356
|
-
)
|
|
2357
|
-
] });
|
|
3183
|
+
return /* @__PURE__ */ jsx26(ProfileEditName, { defaultName: editName, onSubmit: (val) => {
|
|
3184
|
+
setEditName(val);
|
|
3185
|
+
setMode("edit-desc");
|
|
3186
|
+
} });
|
|
2358
3187
|
}
|
|
2359
3188
|
if (mode === "edit-desc") {
|
|
2360
|
-
return /* @__PURE__ */
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
defaultValue: editDesc,
|
|
2370
|
-
onSubmit: async (val) => {
|
|
2371
|
-
if (selectedProfile) {
|
|
2372
|
-
await updateProfile2(selectedProfile.name, editName, val);
|
|
2373
|
-
}
|
|
2374
|
-
setMode("detail");
|
|
2375
|
-
setEditName("");
|
|
2376
|
-
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);
|
|
2377
3198
|
}
|
|
3199
|
+
setMode("detail");
|
|
3200
|
+
setEditName("");
|
|
3201
|
+
setEditDesc("");
|
|
2378
3202
|
}
|
|
2379
|
-
|
|
2380
|
-
|
|
3203
|
+
}
|
|
3204
|
+
);
|
|
2381
3205
|
}
|
|
2382
3206
|
if (mode === "detail" && selectedProfile) {
|
|
2383
|
-
return /* @__PURE__ */
|
|
2384
|
-
/* @__PURE__ */ jsx20(Text20, { bold: true, color: "#FFD700", children: selectedProfile.name }),
|
|
2385
|
-
/* @__PURE__ */ jsx20(Text20, { color: "#9CA3AF", children: selectedProfile.description }),
|
|
2386
|
-
/* @__PURE__ */ jsx20(Text20, { color: "#9CA3AF", children: t("profiles_created", { date: new Date(selectedProfile.createdAt).toLocaleDateString() }) }),
|
|
2387
|
-
/* @__PURE__ */ jsxs20(Box18, { marginTop: 1, flexDirection: "column", children: [
|
|
2388
|
-
/* @__PURE__ */ jsx20(Text20, { bold: true, children: t("profiles_formulaeCount", { count: selectedProfile.formulae.length }) }),
|
|
2389
|
-
/* @__PURE__ */ jsxs20(Box18, { paddingLeft: 2, flexDirection: "column", children: [
|
|
2390
|
-
selectedProfile.formulae.slice(0, 30).map((f) => /* @__PURE__ */ jsx20(Text20, { color: "#9CA3AF", children: f }, f)),
|
|
2391
|
-
selectedProfile.formulae.length > 30 && /* @__PURE__ */ jsx20(Text20, { color: "#6B7280", italic: true, children: t("common_andMore", { count: selectedProfile.formulae.length - 30 }) })
|
|
2392
|
-
] }),
|
|
2393
|
-
/* @__PURE__ */ jsx20(Text20, { bold: true, children: t("profiles_casksCount", { count: selectedProfile.casks.length }) }),
|
|
2394
|
-
/* @__PURE__ */ jsx20(Box18, { paddingLeft: 2, flexDirection: "column", children: selectedProfile.casks.map((c) => /* @__PURE__ */ jsx20(Text20, { color: "#9CA3AF", children: c }, c)) })
|
|
2395
|
-
] }),
|
|
2396
|
-
/* @__PURE__ */ jsx20(Box18, { marginTop: 1, children: /* @__PURE__ */ jsxs20(Text20, { color: "#6B7280", children: [
|
|
2397
|
-
"esc:",
|
|
2398
|
-
t("hint_back"),
|
|
2399
|
-
" e:",
|
|
2400
|
-
t("hint_edit"),
|
|
2401
|
-
" i:",
|
|
2402
|
-
t("hint_importProfile")
|
|
2403
|
-
] }) })
|
|
2404
|
-
] });
|
|
3207
|
+
return /* @__PURE__ */ jsx26(ProfileDetailMode, { profile: selectedProfile });
|
|
2405
3208
|
}
|
|
2406
|
-
return /* @__PURE__ */
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
/* @__PURE__ */ jsx20(Text20, { color: "#6B7280", italic: true, children: t("profiles_noProfiles") }),
|
|
2421
|
-
/* @__PURE__ */ jsxs20(Text20, { color: "#9CA3AF", children: [
|
|
2422
|
-
t("profiles_press"),
|
|
2423
|
-
" ",
|
|
2424
|
-
/* @__PURE__ */ jsx20(Text20, { color: "#FFD700", bold: true, children: "n" }),
|
|
2425
|
-
" ",
|
|
2426
|
-
t("profiles_exportHint")
|
|
2427
|
-
] })
|
|
2428
|
-
] }) }),
|
|
2429
|
-
profileNames.length > 0 && !confirmDelete && /* @__PURE__ */ jsxs20(Box18, { flexDirection: "column", marginTop: 1, children: [
|
|
2430
|
-
profileNames.map((name, i) => {
|
|
2431
|
-
const isCurrent = i === cursor;
|
|
2432
|
-
return /* @__PURE__ */ jsxs20(Box18, { gap: 1, children: [
|
|
2433
|
-
/* @__PURE__ */ jsx20(Text20, { color: isCurrent ? "#22C55E" : "#9CA3AF", children: isCurrent ? "\u25B6" : " " }),
|
|
2434
|
-
/* @__PURE__ */ jsx20(Text20, { bold: isCurrent, inverse: isCurrent, children: name })
|
|
2435
|
-
] }, name);
|
|
2436
|
-
}),
|
|
2437
|
-
/* @__PURE__ */ jsx20(Box18, { marginTop: 1, children: /* @__PURE__ */ jsxs20(Text20, { color: "#F9FAFB", bold: true, children: [
|
|
2438
|
-
cursor + 1,
|
|
2439
|
-
"/",
|
|
2440
|
-
profileNames.length
|
|
2441
|
-
] }) })
|
|
2442
|
-
] })
|
|
2443
|
-
] });
|
|
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
|
+
);
|
|
2444
3223
|
}
|
|
2445
3224
|
|
|
2446
3225
|
// src/views/smart-cleanup.tsx
|
|
2447
|
-
import { useEffect as useEffect12, useRef as
|
|
2448
|
-
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";
|
|
2449
3228
|
|
|
2450
3229
|
// src/stores/cleanup-store.ts
|
|
2451
|
-
import { create as
|
|
3230
|
+
import { create as create6 } from "zustand";
|
|
2452
3231
|
|
|
2453
3232
|
// src/lib/cleanup/cleanup-analyzer.ts
|
|
2454
3233
|
import { execFile } from "child_process";
|
|
@@ -2471,9 +3250,8 @@ async function getCellarPath(name) {
|
|
|
2471
3250
|
return null;
|
|
2472
3251
|
}
|
|
2473
3252
|
}
|
|
2474
|
-
async function analyzeCleanup(formulae, leaves) {
|
|
2475
|
-
|
|
2476
|
-
requirePro(license, status);
|
|
3253
|
+
async function analyzeCleanup(isPro, formulae, leaves) {
|
|
3254
|
+
if (!isPro) throw new Error("Pro license required");
|
|
2477
3255
|
const leavesSet = new Set(leaves);
|
|
2478
3256
|
const reverseDeps = /* @__PURE__ */ new Map();
|
|
2479
3257
|
for (const f of formulae) {
|
|
@@ -2530,7 +3308,7 @@ async function analyzeCleanup(formulae, leaves) {
|
|
|
2530
3308
|
}
|
|
2531
3309
|
|
|
2532
3310
|
// src/stores/cleanup-store.ts
|
|
2533
|
-
var useCleanupStore =
|
|
3311
|
+
var useCleanupStore = create6((set, get) => ({
|
|
2534
3312
|
summary: null,
|
|
2535
3313
|
selected: /* @__PURE__ */ new Set(),
|
|
2536
3314
|
loading: false,
|
|
@@ -2544,7 +3322,8 @@ var useCleanupStore = create5((set, get) => ({
|
|
|
2544
3322
|
await brewState.fetchLeaves();
|
|
2545
3323
|
}
|
|
2546
3324
|
const { formulae, leaves } = useBrewStore.getState();
|
|
2547
|
-
const
|
|
3325
|
+
const isPro = useLicenseStore.getState().isPro();
|
|
3326
|
+
const summary = await analyzeCleanup(isPro, formulae, leaves);
|
|
2548
3327
|
set({ summary, selected: /* @__PURE__ */ new Set(), loading: false });
|
|
2549
3328
|
} catch (err) {
|
|
2550
3329
|
set({ error: err instanceof Error ? err.message : String(err), loading: false });
|
|
@@ -2564,7 +3343,7 @@ var useCleanupStore = create5((set, get) => ({
|
|
|
2564
3343
|
}));
|
|
2565
3344
|
|
|
2566
3345
|
// src/views/smart-cleanup.tsx
|
|
2567
|
-
import { jsx as
|
|
3346
|
+
import { jsx as jsx27, jsxs as jsxs26 } from "react/jsx-runtime";
|
|
2568
3347
|
function SmartCleanupView() {
|
|
2569
3348
|
const { summary, selected, loading, error, analyze, toggleSelect, selectAll } = useCleanupStore();
|
|
2570
3349
|
const [cursor, setCursor] = useState9(0);
|
|
@@ -2572,7 +3351,7 @@ function SmartCleanupView() {
|
|
|
2572
3351
|
const [confirmForce, setConfirmForce] = useState9(false);
|
|
2573
3352
|
const [failedNames, setFailedNames] = useState9([]);
|
|
2574
3353
|
const stream = useBrewStream();
|
|
2575
|
-
const hasRefreshed =
|
|
3354
|
+
const hasRefreshed = useRef7(false);
|
|
2576
3355
|
useEffect12(() => {
|
|
2577
3356
|
analyze();
|
|
2578
3357
|
}, []);
|
|
@@ -2615,34 +3394,26 @@ function SmartCleanupView() {
|
|
|
2615
3394
|
if (input === "j" || key.downArrow) setCursor((c) => Math.min(c + 1, Math.max(0, candidates.length - 1)));
|
|
2616
3395
|
else if (input === "k" || key.upArrow) setCursor((c) => Math.max(c - 1, 0));
|
|
2617
3396
|
});
|
|
2618
|
-
if (loading) return /* @__PURE__ */
|
|
2619
|
-
if (error) return /* @__PURE__ */
|
|
3397
|
+
if (loading) return /* @__PURE__ */ jsx27(Loading, { message: t("loading_cleanup") });
|
|
3398
|
+
if (error) return /* @__PURE__ */ jsx27(ErrorMessage, { message: error });
|
|
2620
3399
|
if (stream.isRunning || stream.lines.length > 0) {
|
|
2621
|
-
return /* @__PURE__ */
|
|
2622
|
-
/* @__PURE__ */
|
|
2623
|
-
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: [
|
|
2624
3403
|
"esc:",
|
|
2625
3404
|
t("hint_cancel")
|
|
2626
3405
|
] }),
|
|
2627
|
-
!stream.isRunning && !stream.error && /* @__PURE__ */
|
|
2628
|
-
|
|
2629
|
-
" ",
|
|
2630
|
-
|
|
2631
|
-
] }) }),
|
|
2632
|
-
!stream.isRunning && stream.error && /* @__PURE__ */ jsxs21(Box19, { flexDirection: "column", gap: 1, children: [
|
|
2633
|
-
/* @__PURE__ */ jsx21(Box19, { borderStyle: "round", borderColor: "#EF4444", paddingX: 2, paddingY: 0, children: /* @__PURE__ */ jsxs21(Text21, { color: "#EF4444", bold: true, children: [
|
|
2634
|
-
"\u2718",
|
|
2635
|
-
" ",
|
|
2636
|
-
t("cleanup_depError")
|
|
2637
|
-
] }) }),
|
|
2638
|
-
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: [
|
|
2639
3410
|
"F:",
|
|
2640
3411
|
t("hint_force"),
|
|
2641
3412
|
" r:",
|
|
2642
3413
|
t("hint_refresh")
|
|
2643
3414
|
] })
|
|
2644
3415
|
] }),
|
|
2645
|
-
confirmForce && /* @__PURE__ */
|
|
3416
|
+
confirmForce && /* @__PURE__ */ jsx27(Box25, { marginY: 1, children: /* @__PURE__ */ jsx27(
|
|
2646
3417
|
ConfirmDialog,
|
|
2647
3418
|
{
|
|
2648
3419
|
message: t("cleanup_confirmForce", { count: failedNames.length }),
|
|
@@ -2657,49 +3428,47 @@ function SmartCleanupView() {
|
|
|
2657
3428
|
) })
|
|
2658
3429
|
] });
|
|
2659
3430
|
}
|
|
2660
|
-
return /* @__PURE__ */
|
|
2661
|
-
/* @__PURE__ */
|
|
2662
|
-
summary && /* @__PURE__ */
|
|
2663
|
-
/* @__PURE__ */
|
|
2664
|
-
/* @__PURE__ */
|
|
2665
|
-
/* @__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 })
|
|
2666
3437
|
] }),
|
|
2667
|
-
confirmClean && /* @__PURE__ */
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
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: [
|
|
2687
3457
|
candidates.map((c, i) => {
|
|
2688
3458
|
const isCurrent = i === cursor;
|
|
2689
3459
|
const isSelected = selected.has(c.name);
|
|
2690
|
-
return /* @__PURE__ */
|
|
2691
|
-
/* @__PURE__ */
|
|
2692
|
-
/* @__PURE__ */
|
|
2693
|
-
/* @__PURE__ */
|
|
2694
|
-
/* @__PURE__ */
|
|
2695
|
-
/* @__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: [
|
|
2696
3465
|
"[",
|
|
2697
3466
|
c.reason,
|
|
2698
3467
|
"]"
|
|
2699
3468
|
] })
|
|
2700
3469
|
] }, c.name);
|
|
2701
3470
|
}),
|
|
2702
|
-
/* @__PURE__ */
|
|
3471
|
+
/* @__PURE__ */ jsx27(Box25, { marginTop: 1, children: /* @__PURE__ */ jsxs26(Text26, { color: COLORS.text, bold: true, children: [
|
|
2703
3472
|
cursor + 1,
|
|
2704
3473
|
"/",
|
|
2705
3474
|
candidates.length
|
|
@@ -2710,41 +3479,44 @@ function SmartCleanupView() {
|
|
|
2710
3479
|
|
|
2711
3480
|
// src/views/history.tsx
|
|
2712
3481
|
import { useEffect as useEffect13, useState as useState10, useMemo as useMemo4 } from "react";
|
|
2713
|
-
import { Box as
|
|
3482
|
+
import { Box as Box26, Text as Text27, useInput as useInput11, useStdout as useStdout5 } from "ink";
|
|
2714
3483
|
|
|
2715
3484
|
// src/stores/history-store.ts
|
|
2716
|
-
import { create as
|
|
2717
|
-
var useHistoryStore =
|
|
3485
|
+
import { create as create7 } from "zustand";
|
|
3486
|
+
var useHistoryStore = create7((set) => ({
|
|
2718
3487
|
entries: [],
|
|
2719
3488
|
loading: false,
|
|
2720
3489
|
error: null,
|
|
2721
3490
|
fetchHistory: async () => {
|
|
2722
3491
|
set({ loading: true, error: null });
|
|
2723
3492
|
try {
|
|
2724
|
-
const
|
|
3493
|
+
const isPro = useLicenseStore.getState().isPro();
|
|
3494
|
+
const entries = await loadHistory(isPro);
|
|
2725
3495
|
set({ entries, loading: false });
|
|
2726
3496
|
} catch (err) {
|
|
2727
3497
|
set({ loading: false, error: err instanceof Error ? err.message : String(err) });
|
|
2728
3498
|
}
|
|
2729
3499
|
},
|
|
2730
3500
|
logAction: async (action, packageName, success, error = null) => {
|
|
2731
|
-
|
|
2732
|
-
|
|
3501
|
+
const isPro = useLicenseStore.getState().isPro();
|
|
3502
|
+
await appendEntry(isPro, action, packageName, success, error);
|
|
3503
|
+
const entries = await loadHistory(isPro);
|
|
2733
3504
|
set({ entries });
|
|
2734
3505
|
},
|
|
2735
3506
|
clearHistory: async () => {
|
|
2736
|
-
|
|
3507
|
+
const isPro = useLicenseStore.getState().isPro();
|
|
3508
|
+
await clearHistory(isPro);
|
|
2737
3509
|
set({ entries: [] });
|
|
2738
3510
|
}
|
|
2739
3511
|
}));
|
|
2740
3512
|
|
|
2741
3513
|
// src/views/history.tsx
|
|
2742
|
-
import { jsx as
|
|
3514
|
+
import { jsx as jsx28, jsxs as jsxs27 } from "react/jsx-runtime";
|
|
2743
3515
|
var ACTION_ICONS = {
|
|
2744
|
-
install: { icon: "+", color:
|
|
2745
|
-
uninstall: { icon: "-", color:
|
|
2746
|
-
upgrade: { icon: "\u2191", color:
|
|
2747
|
-
"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 }
|
|
2748
3520
|
};
|
|
2749
3521
|
var ACTION_LABEL_KEYS = {
|
|
2750
3522
|
install: "history_actionInstall",
|
|
@@ -2817,19 +3589,19 @@ function HistoryView() {
|
|
|
2817
3589
|
if (input === "j" || key.downArrow) setCursor((c) => Math.min(c + 1, Math.max(0, filtered.length - 1)));
|
|
2818
3590
|
else if (input === "k" || key.upArrow) setCursor((c) => Math.max(c - 1, 0));
|
|
2819
3591
|
});
|
|
2820
|
-
if (loading) return /* @__PURE__ */
|
|
2821
|
-
if (error) return /* @__PURE__ */
|
|
2822
|
-
const { stdout } =
|
|
3592
|
+
if (loading) return /* @__PURE__ */ jsx28(Loading, { message: t("loading_history") });
|
|
3593
|
+
if (error) return /* @__PURE__ */ jsx28(ErrorMessage, { message: error });
|
|
3594
|
+
const { stdout } = useStdout5();
|
|
2823
3595
|
const MAX_VISIBLE_ROWS = Math.max(5, (stdout?.rows ?? 24) - 8);
|
|
2824
3596
|
const start = Math.max(0, cursor - Math.floor(MAX_VISIBLE_ROWS / 2));
|
|
2825
3597
|
const visible = filtered.slice(start, start + MAX_VISIBLE_ROWS);
|
|
2826
|
-
return /* @__PURE__ */
|
|
2827
|
-
/* @__PURE__ */
|
|
2828
|
-
/* @__PURE__ */
|
|
2829
|
-
/* @__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 }) })
|
|
2830
3602
|
] }),
|
|
2831
|
-
isSearching && /* @__PURE__ */
|
|
2832
|
-
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(
|
|
2833
3605
|
ConfirmDialog,
|
|
2834
3606
|
{
|
|
2835
3607
|
message: t("history_confirmClear", { count: entries.length }),
|
|
@@ -2840,10 +3612,10 @@ function HistoryView() {
|
|
|
2840
3612
|
onCancel: () => setConfirmClear(false)
|
|
2841
3613
|
}
|
|
2842
3614
|
),
|
|
2843
|
-
confirmReplay && /* @__PURE__ */
|
|
3615
|
+
confirmReplay && /* @__PURE__ */ jsx28(
|
|
2844
3616
|
ConfirmDialog,
|
|
2845
3617
|
{
|
|
2846
|
-
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 ?? "" }),
|
|
2847
3619
|
onConfirm: async () => {
|
|
2848
3620
|
const entry = confirmReplay;
|
|
2849
3621
|
setConfirmReplay(null);
|
|
@@ -2868,10 +3640,10 @@ function HistoryView() {
|
|
|
2868
3640
|
onCancel: () => setConfirmReplay(null)
|
|
2869
3641
|
}
|
|
2870
3642
|
),
|
|
2871
|
-
(stream.isRunning || stream.lines.length > 0) && /* @__PURE__ */
|
|
2872
|
-
filtered.length === 0 && !confirmClear && /* @__PURE__ */
|
|
2873
|
-
filtered.length > 0 && !confirmClear && /* @__PURE__ */
|
|
2874
|
-
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: [
|
|
2875
3647
|
" ",
|
|
2876
3648
|
t("scroll_moreAbove", { count: start })
|
|
2877
3649
|
] }),
|
|
@@ -2880,20 +3652,19 @@ function HistoryView() {
|
|
|
2880
3652
|
const isCurrent = idx === cursor;
|
|
2881
3653
|
const { icon, color } = ACTION_ICONS[entry.action];
|
|
2882
3654
|
const ts = new Date(entry.timestamp).getTime() / 1e3;
|
|
2883
|
-
return /* @__PURE__ */
|
|
2884
|
-
/* @__PURE__ */
|
|
2885
|
-
/* @__PURE__ */
|
|
2886
|
-
/* @__PURE__ */
|
|
2887
|
-
/* @__PURE__ */
|
|
2888
|
-
|
|
2889
|
-
/* @__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) })
|
|
2890
3661
|
] }, entry.id);
|
|
2891
3662
|
}),
|
|
2892
|
-
start + MAX_VISIBLE_ROWS < filtered.length && /* @__PURE__ */
|
|
3663
|
+
start + MAX_VISIBLE_ROWS < filtered.length && /* @__PURE__ */ jsxs27(Text27, { color: COLORS.textSecondary, dimColor: true, children: [
|
|
2893
3664
|
" ",
|
|
2894
3665
|
t("scroll_moreBelow", { count: filtered.length - start - MAX_VISIBLE_ROWS })
|
|
2895
3666
|
] }),
|
|
2896
|
-
/* @__PURE__ */
|
|
3667
|
+
/* @__PURE__ */ jsx28(Box26, { marginTop: 1, children: /* @__PURE__ */ jsxs27(Text27, { color: COLORS.text, bold: true, children: [
|
|
2897
3668
|
cursor + 1,
|
|
2898
3669
|
"/",
|
|
2899
3670
|
filtered.length
|
|
@@ -2904,10 +3675,10 @@ function HistoryView() {
|
|
|
2904
3675
|
|
|
2905
3676
|
// src/views/security-audit.tsx
|
|
2906
3677
|
import { useEffect as useEffect14, useState as useState11 } from "react";
|
|
2907
|
-
import { Box as
|
|
3678
|
+
import { Box as Box27, Text as Text28, useInput as useInput12 } from "ink";
|
|
2908
3679
|
|
|
2909
3680
|
// src/stores/security-store.ts
|
|
2910
|
-
import { create as
|
|
3681
|
+
import { create as create8 } from "zustand";
|
|
2911
3682
|
|
|
2912
3683
|
// src/lib/security/osv-api.ts
|
|
2913
3684
|
var OSV_BATCH_URL = "https://api.osv.dev/v1/querybatch";
|
|
@@ -2950,6 +3721,12 @@ async function queryBatch(packages, queries) {
|
|
|
2950
3721
|
throw new Error(`OSV API error: ${res.status} ${res.statusText}`);
|
|
2951
3722
|
}
|
|
2952
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
|
+
}
|
|
2953
3730
|
const result = /* @__PURE__ */ new Map();
|
|
2954
3731
|
for (let i = 0; i < packages.length; i++) {
|
|
2955
3732
|
const vulns = data.results[i]?.vulns;
|
|
@@ -2969,6 +3746,7 @@ async function queryBatch(packages, queries) {
|
|
|
2969
3746
|
}
|
|
2970
3747
|
async function queryOneByOne(packages) {
|
|
2971
3748
|
const result = /* @__PURE__ */ new Map();
|
|
3749
|
+
const errors = [];
|
|
2972
3750
|
for (const pkg of packages) {
|
|
2973
3751
|
try {
|
|
2974
3752
|
const partial = await queryBatch(
|
|
@@ -2976,15 +3754,43 @@ async function queryOneByOne(packages) {
|
|
|
2976
3754
|
[{ package: { name: pkg.name, ecosystem: "Homebrew" }, version: pkg.version }]
|
|
2977
3755
|
);
|
|
2978
3756
|
for (const [k, v] of partial) result.set(k, v);
|
|
2979
|
-
} 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}`);
|
|
2980
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) });
|
|
2981
3784
|
}
|
|
2982
3785
|
return result;
|
|
2983
3786
|
}
|
|
2984
3787
|
async function queryVulnerabilities(packages) {
|
|
3788
|
+
const validPackages = packages.filter(
|
|
3789
|
+
(p) => p.version && typeof p.version === "string" && p.version.trim().length > 0
|
|
3790
|
+
);
|
|
2985
3791
|
const result = /* @__PURE__ */ new Map();
|
|
2986
|
-
for (let i = 0; i <
|
|
2987
|
-
const batch =
|
|
3792
|
+
for (let i = 0; i < validPackages.length; i += BATCH_SIZE) {
|
|
3793
|
+
const batch = validPackages.slice(i, i + BATCH_SIZE);
|
|
2988
3794
|
const queries = batch.map((p) => ({
|
|
2989
3795
|
package: { name: p.name, ecosystem: "Homebrew" },
|
|
2990
3796
|
version: p.version
|
|
@@ -3003,9 +3809,8 @@ var SEVERITY_ORDER = {
|
|
|
3003
3809
|
LOW: 1,
|
|
3004
3810
|
UNKNOWN: 0
|
|
3005
3811
|
};
|
|
3006
|
-
async function runSecurityAudit(formulae, casks) {
|
|
3007
|
-
|
|
3008
|
-
requirePro(license, status);
|
|
3812
|
+
async function runSecurityAudit(isPro, formulae, casks) {
|
|
3813
|
+
if (!isPro) throw new Error("Pro license required");
|
|
3009
3814
|
const packages = [];
|
|
3010
3815
|
for (const f of formulae) {
|
|
3011
3816
|
const version = f.installed[0]?.version ?? f.versions.stable;
|
|
@@ -3056,11 +3861,17 @@ async function runSecurityAudit(formulae, casks) {
|
|
|
3056
3861
|
}
|
|
3057
3862
|
|
|
3058
3863
|
// src/stores/security-store.ts
|
|
3059
|
-
var
|
|
3864
|
+
var CACHE_TTL_MS = 30 * 60 * 1e3;
|
|
3865
|
+
var useSecurityStore = create8((set, get) => ({
|
|
3060
3866
|
summary: null,
|
|
3061
3867
|
loading: false,
|
|
3062
3868
|
error: null,
|
|
3063
|
-
|
|
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
|
+
}
|
|
3064
3875
|
set({ loading: true, error: null });
|
|
3065
3876
|
try {
|
|
3066
3877
|
const brewState = useBrewStore.getState();
|
|
@@ -3068,8 +3879,9 @@ var useSecurityStore = create7((set) => ({
|
|
|
3068
3879
|
await brewState.fetchInstalled();
|
|
3069
3880
|
}
|
|
3070
3881
|
const { formulae, casks } = useBrewStore.getState();
|
|
3071
|
-
const
|
|
3072
|
-
|
|
3882
|
+
const isPro = useLicenseStore.getState().isPro();
|
|
3883
|
+
const result = await runSecurityAudit(isPro, formulae, casks);
|
|
3884
|
+
set({ summary: result, loading: false, cachedAt: Date.now() });
|
|
3073
3885
|
} catch (err) {
|
|
3074
3886
|
set({ error: err instanceof Error ? err.message : String(err), loading: false });
|
|
3075
3887
|
}
|
|
@@ -3077,13 +3889,13 @@ var useSecurityStore = create7((set) => ({
|
|
|
3077
3889
|
}));
|
|
3078
3890
|
|
|
3079
3891
|
// src/views/security-audit.tsx
|
|
3080
|
-
import { jsx as
|
|
3892
|
+
import { jsx as jsx29, jsxs as jsxs28 } from "react/jsx-runtime";
|
|
3081
3893
|
var SEVERITY_COLORS = {
|
|
3082
|
-
CRITICAL:
|
|
3083
|
-
HIGH:
|
|
3084
|
-
MEDIUM:
|
|
3085
|
-
LOW:
|
|
3086
|
-
UNKNOWN:
|
|
3894
|
+
CRITICAL: COLORS.error,
|
|
3895
|
+
HIGH: COLORS.error,
|
|
3896
|
+
MEDIUM: COLORS.warning,
|
|
3897
|
+
LOW: COLORS.textSecondary,
|
|
3898
|
+
UNKNOWN: COLORS.textSecondary
|
|
3087
3899
|
};
|
|
3088
3900
|
var SEVERITY_BADGE = {
|
|
3089
3901
|
CRITICAL: "error",
|
|
@@ -3092,8 +3904,11 @@ var SEVERITY_BADGE = {
|
|
|
3092
3904
|
LOW: "muted",
|
|
3093
3905
|
UNKNOWN: "muted"
|
|
3094
3906
|
};
|
|
3907
|
+
function isNetworkError2(msg) {
|
|
3908
|
+
return /fetch failed|ECONNREFUSED|ENOTFOUND|ETIMEDOUT|network|abort/i.test(msg);
|
|
3909
|
+
}
|
|
3095
3910
|
function SecurityAuditView() {
|
|
3096
|
-
const { summary, loading, error, scan } = useSecurityStore();
|
|
3911
|
+
const { summary, loading, error, scan, cachedAt } = useSecurityStore();
|
|
3097
3912
|
const [cursor, setCursor] = useState11(0);
|
|
3098
3913
|
const [expandedPkg, setExpandedPkg] = useState11(null);
|
|
3099
3914
|
const [confirmUpgrade, setConfirmUpgrade] = useState11(null);
|
|
@@ -3105,7 +3920,7 @@ function SecurityAuditView() {
|
|
|
3105
3920
|
useInput12((input, key) => {
|
|
3106
3921
|
if (confirmUpgrade || stream.isRunning) return;
|
|
3107
3922
|
if (input === "r") {
|
|
3108
|
-
void scan();
|
|
3923
|
+
void scan(true);
|
|
3109
3924
|
return;
|
|
3110
3925
|
}
|
|
3111
3926
|
if (input === "u" && results[cursor]) {
|
|
@@ -3118,30 +3933,31 @@ function SecurityAuditView() {
|
|
|
3118
3933
|
setExpandedPkg(expandedPkg === results[cursor].packageName ? null : results[cursor].packageName);
|
|
3119
3934
|
}
|
|
3120
3935
|
});
|
|
3121
|
-
if (loading) return /* @__PURE__ */
|
|
3122
|
-
if (error)
|
|
3123
|
-
|
|
3124
|
-
/* @__PURE__ */
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
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(
|
|
3128
3947
|
StatCard,
|
|
3129
3948
|
{
|
|
3130
3949
|
label: t("security_vulnerable"),
|
|
3131
3950
|
value: summary.vulnerablePackages,
|
|
3132
|
-
color: summary.vulnerablePackages > 0 ?
|
|
3951
|
+
color: summary.vulnerablePackages > 0 ? COLORS.error : COLORS.success
|
|
3133
3952
|
}
|
|
3134
3953
|
),
|
|
3135
|
-
summary.criticalCount > 0 && /* @__PURE__ */
|
|
3136
|
-
summary.highCount > 0 && /* @__PURE__ */
|
|
3137
|
-
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 })
|
|
3138
3957
|
] }),
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
t("security_noVulns")
|
|
3143
|
-
] }) }) }),
|
|
3144
|
-
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(
|
|
3145
3961
|
ConfirmDialog,
|
|
3146
3962
|
{
|
|
3147
3963
|
message: t("security_confirmUpgrade", { name: confirmUpgrade }),
|
|
@@ -3149,40 +3965,39 @@ function SecurityAuditView() {
|
|
|
3149
3965
|
const name = confirmUpgrade;
|
|
3150
3966
|
setConfirmUpgrade(null);
|
|
3151
3967
|
await stream.run(["upgrade", name]);
|
|
3152
|
-
void scan();
|
|
3968
|
+
void scan(true);
|
|
3153
3969
|
},
|
|
3154
3970
|
onCancel: () => setConfirmUpgrade(null)
|
|
3155
3971
|
}
|
|
3156
3972
|
) }),
|
|
3157
|
-
(stream.isRunning || stream.lines.length > 0) && /* @__PURE__ */
|
|
3158
|
-
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: [
|
|
3159
3975
|
results.map((pkg, i) => {
|
|
3160
3976
|
const isCurrent = i === cursor;
|
|
3161
3977
|
const isExpanded = expandedPkg === pkg.packageName;
|
|
3162
|
-
return /* @__PURE__ */
|
|
3163
|
-
/* @__PURE__ */
|
|
3164
|
-
/* @__PURE__ */
|
|
3165
|
-
/* @__PURE__ */
|
|
3166
|
-
/* @__PURE__ */
|
|
3167
|
-
/* @__PURE__ */
|
|
3168
|
-
/* @__PURE__ */
|
|
3169
|
-
/* @__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" })
|
|
3170
3985
|
] }),
|
|
3171
|
-
isExpanded && /* @__PURE__ */
|
|
3172
|
-
/* @__PURE__ */
|
|
3173
|
-
/* @__PURE__ */
|
|
3174
|
-
/* @__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: [
|
|
3175
3990
|
"[",
|
|
3176
3991
|
vuln.severity,
|
|
3177
3992
|
"]"
|
|
3178
3993
|
] })
|
|
3179
3994
|
] }),
|
|
3180
|
-
/* @__PURE__ */
|
|
3181
|
-
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 }) })
|
|
3182
3997
|
] }, vuln.id)) })
|
|
3183
3998
|
] }, pkg.packageName);
|
|
3184
3999
|
}),
|
|
3185
|
-
/* @__PURE__ */
|
|
4000
|
+
/* @__PURE__ */ jsx29(Box27, { marginTop: 1, children: /* @__PURE__ */ jsxs28(Text28, { color: COLORS.text, bold: true, children: [
|
|
3186
4001
|
cursor + 1,
|
|
3187
4002
|
"/",
|
|
3188
4003
|
results.length
|
|
@@ -3193,12 +4008,13 @@ function SecurityAuditView() {
|
|
|
3193
4008
|
|
|
3194
4009
|
// src/views/account.tsx
|
|
3195
4010
|
import { useState as useState12 } from "react";
|
|
3196
|
-
import { Box as
|
|
3197
|
-
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";
|
|
3198
4013
|
function AccountView() {
|
|
3199
4014
|
const { status, license, deactivate: deactivate2, degradation } = useLicenseStore();
|
|
3200
4015
|
const [confirmDeactivate, setConfirmDeactivate] = useState12(false);
|
|
3201
4016
|
const [deactivating, setDeactivating] = useState12(false);
|
|
4017
|
+
const [deactivateError, setDeactivateError] = useState12(null);
|
|
3202
4018
|
useInput13((input) => {
|
|
3203
4019
|
if (confirmDeactivate || deactivating) return;
|
|
3204
4020
|
if (input === "d" && status === "pro") {
|
|
@@ -3209,17 +4025,23 @@ function AccountView() {
|
|
|
3209
4025
|
if (key.length <= 8) return key;
|
|
3210
4026
|
return key.slice(0, 4) + "-****-****-" + key.slice(-4);
|
|
3211
4027
|
};
|
|
3212
|
-
|
|
3213
|
-
/* @__PURE__ */
|
|
3214
|
-
|
|
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(
|
|
3215
4034
|
ConfirmDialog,
|
|
3216
4035
|
{
|
|
3217
4036
|
message: t("account_confirmDeactivate"),
|
|
3218
4037
|
onConfirm: async () => {
|
|
3219
4038
|
setConfirmDeactivate(false);
|
|
3220
4039
|
setDeactivating(true);
|
|
4040
|
+
setDeactivateError(null);
|
|
3221
4041
|
try {
|
|
3222
4042
|
await deactivate2();
|
|
4043
|
+
} catch (err) {
|
|
4044
|
+
setDeactivateError(t("deactivate_failed") + ": " + String(err));
|
|
3223
4045
|
} finally {
|
|
3224
4046
|
setDeactivating(false);
|
|
3225
4047
|
}
|
|
@@ -3227,122 +4049,128 @@ function AccountView() {
|
|
|
3227
4049
|
onCancel: () => setConfirmDeactivate(false)
|
|
3228
4050
|
}
|
|
3229
4051
|
) }),
|
|
3230
|
-
/* @__PURE__ */
|
|
3231
|
-
/* @__PURE__ */
|
|
3232
|
-
/* @__PURE__ */
|
|
3233
|
-
status === "pro" && /* @__PURE__ */
|
|
3234
|
-
status === "free" && /* @__PURE__ */
|
|
3235
|
-
status === "expired" && /* @__PURE__ */
|
|
3236
|
-
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") })
|
|
3237
4058
|
] }),
|
|
3238
|
-
(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", {
|
|
3239
4060
|
days: Math.floor((Date.now() - new Date(license.lastValidatedAt).getTime()) / (24 * 60 * 60 * 1e3))
|
|
3240
4061
|
}) }) }),
|
|
3241
|
-
license && /* @__PURE__ */
|
|
3242
|
-
/* @__PURE__ */
|
|
3243
|
-
/* @__PURE__ */
|
|
3244
|
-
/* @__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 })
|
|
3245
4066
|
] }),
|
|
3246
|
-
/* @__PURE__ */
|
|
3247
|
-
/* @__PURE__ */
|
|
3248
|
-
/* @__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 })
|
|
3249
4070
|
] }),
|
|
3250
|
-
/* @__PURE__ */
|
|
3251
|
-
/* @__PURE__ */
|
|
3252
|
-
/* @__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" })
|
|
3253
4074
|
] }),
|
|
3254
|
-
/* @__PURE__ */
|
|
3255
|
-
/* @__PURE__ */
|
|
3256
|
-
/* @__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) })
|
|
3257
4078
|
] }),
|
|
3258
|
-
license.expiresAt && /* @__PURE__ */
|
|
3259
|
-
/* @__PURE__ */
|
|
3260
|
-
/* @__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) })
|
|
3261
4082
|
] }),
|
|
3262
|
-
/* @__PURE__ */
|
|
3263
|
-
/* @__PURE__ */
|
|
3264
|
-
/* @__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) })
|
|
3265
4086
|
] })
|
|
3266
4087
|
] }),
|
|
3267
|
-
status === "free" && /* @__PURE__ */
|
|
3268
|
-
/* @__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: [
|
|
3269
4090
|
"\u2B50",
|
|
3270
4091
|
" ",
|
|
3271
4092
|
t("account_upgradeTitle")
|
|
3272
4093
|
] }),
|
|
3273
|
-
/* @__PURE__ */
|
|
3274
|
-
/* @__PURE__ */
|
|
3275
|
-
/* @__PURE__ */
|
|
3276
|
-
/* @__PURE__ */
|
|
3277
|
-
/* @__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: [
|
|
3278
4099
|
t("upgrade_buyAt"),
|
|
3279
4100
|
" ",
|
|
3280
|
-
/* @__PURE__ */
|
|
4101
|
+
/* @__PURE__ */ jsx30(Text29, { color: COLORS.sky, bold: true, children: t("upgrade_buyUrl") })
|
|
3281
4102
|
] }),
|
|
3282
|
-
/* @__PURE__ */
|
|
4103
|
+
/* @__PURE__ */ jsxs29(Text29, { color: COLORS.muted, children: [
|
|
3283
4104
|
t("account_runActivate"),
|
|
3284
4105
|
" ",
|
|
3285
|
-
/* @__PURE__ */
|
|
4106
|
+
/* @__PURE__ */ jsx30(Text29, { color: COLORS.success, bold: true, children: t("account_activateCmd") })
|
|
3286
4107
|
] })
|
|
3287
4108
|
] }),
|
|
3288
|
-
status === "expired" && /* @__PURE__ */
|
|
3289
|
-
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 })
|
|
3290
4112
|
] }),
|
|
3291
|
-
/* @__PURE__ */
|
|
4113
|
+
/* @__PURE__ */ jsx30(Box28, { marginTop: 2, children: /* @__PURE__ */ jsxs29(Text29, { color: COLORS.textSecondary, children: [
|
|
3292
4114
|
status === "pro" ? `d:${t("hint_deactivate")}` : "",
|
|
3293
4115
|
" ",
|
|
3294
|
-
t("app_version", { version: "0.
|
|
4116
|
+
t("app_version", { version: "0.3.1" })
|
|
3295
4117
|
] }) })
|
|
3296
4118
|
] });
|
|
3297
4119
|
}
|
|
3298
4120
|
|
|
3299
4121
|
// src/app.tsx
|
|
3300
|
-
import { jsx as
|
|
3301
|
-
function
|
|
3302
|
-
const { exit } = useApp();
|
|
3303
|
-
const currentView = useNavigationStore((s) => s.currentView);
|
|
3304
|
-
const isPro = useLicenseStore((s) => s.isPro);
|
|
4122
|
+
import { jsx as jsx31, jsxs as jsxs30 } from "react/jsx-runtime";
|
|
4123
|
+
function LicenseInitializer() {
|
|
3305
4124
|
const initLicense = useLicenseStore((s) => s.initialize);
|
|
3306
4125
|
useEffect15(() => {
|
|
3307
4126
|
initLicense();
|
|
3308
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);
|
|
3309
4165
|
useGlobalKeyboard({ onQuit: exit });
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
switch (currentView) {
|
|
3315
|
-
case "dashboard":
|
|
3316
|
-
return /* @__PURE__ */ jsx25(DashboardView, {});
|
|
3317
|
-
case "installed":
|
|
3318
|
-
return /* @__PURE__ */ jsx25(InstalledView, {});
|
|
3319
|
-
case "search":
|
|
3320
|
-
return /* @__PURE__ */ jsx25(SearchView, {});
|
|
3321
|
-
case "outdated":
|
|
3322
|
-
return /* @__PURE__ */ jsx25(OutdatedView, {});
|
|
3323
|
-
case "package-info":
|
|
3324
|
-
return /* @__PURE__ */ jsx25(PackageInfoView, {});
|
|
3325
|
-
case "services":
|
|
3326
|
-
return /* @__PURE__ */ jsx25(ServicesView, {});
|
|
3327
|
-
case "doctor":
|
|
3328
|
-
return /* @__PURE__ */ jsx25(DoctorView, {});
|
|
3329
|
-
case "profiles":
|
|
3330
|
-
return /* @__PURE__ */ jsx25(ProfilesView, {});
|
|
3331
|
-
case "smart-cleanup":
|
|
3332
|
-
return /* @__PURE__ */ jsx25(SmartCleanupView, {});
|
|
3333
|
-
case "history":
|
|
3334
|
-
return /* @__PURE__ */ jsx25(HistoryView, {});
|
|
3335
|
-
case "security-audit":
|
|
3336
|
-
return /* @__PURE__ */ jsx25(SecurityAuditView, {});
|
|
3337
|
-
case "account":
|
|
3338
|
-
return /* @__PURE__ */ jsx25(AccountView, {});
|
|
3339
|
-
}
|
|
3340
|
-
};
|
|
3341
|
-
return /* @__PURE__ */ jsx25(AppLayout, { children: renderView() });
|
|
4166
|
+
return /* @__PURE__ */ jsxs30(AppLayout, { children: [
|
|
4167
|
+
/* @__PURE__ */ jsx31(LicenseInitializer, {}),
|
|
4168
|
+
/* @__PURE__ */ jsx31(ViewRouter, { currentView })
|
|
4169
|
+
] });
|
|
3342
4170
|
}
|
|
3343
4171
|
|
|
3344
4172
|
// src/index.tsx
|
|
3345
|
-
import { jsx as
|
|
4173
|
+
import { jsx as jsx32 } from "react/jsx-runtime";
|
|
3346
4174
|
var [, , command, arg] = process.argv;
|
|
3347
4175
|
async function runCli() {
|
|
3348
4176
|
await ensureDataDirs();
|
|
@@ -3357,7 +4185,7 @@ async function runCli() {
|
|
|
3357
4185
|
console.log(t("cli_activated", { email: license.customerEmail }));
|
|
3358
4186
|
console.log(t("cli_planPro"));
|
|
3359
4187
|
if (license.expiresAt) {
|
|
3360
|
-
console.log(t("cli_expires", { date:
|
|
4188
|
+
console.log(t("cli_expires", { date: formatDate(license.expiresAt) }));
|
|
3361
4189
|
}
|
|
3362
4190
|
} catch (err) {
|
|
3363
4191
|
console.error(t("cli_activationFailed", { error: err instanceof Error ? err.message : String(err) }));
|
|
@@ -3385,25 +4213,62 @@ async function runCli() {
|
|
|
3385
4213
|
}
|
|
3386
4214
|
return;
|
|
3387
4215
|
}
|
|
3388
|
-
if (command === "
|
|
4216
|
+
if (command === "revalidate") {
|
|
3389
4217
|
const license = await loadLicense();
|
|
3390
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") {
|
|
3391
4242
|
console.log(t("cli_planFree"));
|
|
3392
4243
|
console.log(t("cli_upgradeHint"));
|
|
3393
4244
|
} else {
|
|
3394
|
-
|
|
3395
|
-
console.log(
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
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"));
|
|
3399
4262
|
}
|
|
3400
4263
|
}
|
|
3401
4264
|
return;
|
|
3402
4265
|
}
|
|
3403
4266
|
if (command === "install-brewbar") {
|
|
3404
|
-
|
|
4267
|
+
await useLicenseStore.getState().initialize();
|
|
4268
|
+
const isPro = useLicenseStore.getState().isPro();
|
|
4269
|
+
const { installBrewBar } = await import("./brewbar-installer-H5MLNNTD.js");
|
|
3405
4270
|
try {
|
|
3406
|
-
await installBrewBar(arg === "--force");
|
|
4271
|
+
await installBrewBar(isPro, arg === "--force");
|
|
3407
4272
|
console.log(t("cli_brewbarInstalled"));
|
|
3408
4273
|
} catch (err) {
|
|
3409
4274
|
console.error(err instanceof Error ? err.message : String(err));
|
|
@@ -3412,7 +4277,7 @@ async function runCli() {
|
|
|
3412
4277
|
return;
|
|
3413
4278
|
}
|
|
3414
4279
|
if (command === "uninstall-brewbar") {
|
|
3415
|
-
const { uninstallBrewBar } = await import("./brewbar-installer-
|
|
4280
|
+
const { uninstallBrewBar } = await import("./brewbar-installer-H5MLNNTD.js");
|
|
3416
4281
|
try {
|
|
3417
4282
|
await uninstallBrewBar();
|
|
3418
4283
|
console.log(t("cli_brewbarUninstalled"));
|
|
@@ -3422,9 +4287,22 @@ async function runCli() {
|
|
|
3422
4287
|
}
|
|
3423
4288
|
return;
|
|
3424
4289
|
}
|
|
3425
|
-
|
|
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, {}));
|
|
3426
4303
|
}
|
|
3427
4304
|
runCli().catch((err) => {
|
|
3428
4305
|
console.error(err instanceof Error ? err.message : String(err));
|
|
3429
4306
|
process.exit(1);
|
|
3430
4307
|
});
|
|
4308
|
+
//# sourceMappingURL=index.js.map
|