brew-tui 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/index.js CHANGED
@@ -15,7 +15,7 @@ import {
15
15
  t,
16
16
  tp,
17
17
  useLocaleStore
18
- } from "./chunk-PTLSNG2N.js";
18
+ } from "./chunk-PPSKR6PA.js";
19
19
 
20
20
  // src/index.tsx
21
21
  import { createInterface } from "readline/promises";
@@ -160,7 +160,7 @@ var GRADIENTS = {
160
160
  };
161
161
 
162
162
  // src/components/layout/header.tsx
163
- import { jsx as jsx2, jsxs } from "react/jsx-runtime";
163
+ import { Fragment as Fragment2, jsx as jsx2, jsxs } from "react/jsx-runtime";
164
164
  var LOGO_BREW = [
165
165
  "\u256D\u2501\u2501\u256E\u2571\u256D\u2501\u2501\u2501\u256E\u256D\u2501\u2501\u2501\u256E\u256D\u256E\u256D\u256E\u256D\u256E\u2571\u2571\u2571\u2571\u2571\u2571\u2571",
166
166
  "\u2503\u256D\u256E\u2503\u2571\u2503\u256D\u2501\u256E\u2503\u2503\u256D\u2501\u2501\u256F\u2503\u2503\u2503\u2503\u2503\u2503\u2571\u2571\u2571\u2571\u2571\u2571\u2571",
@@ -227,26 +227,30 @@ function Header() {
227
227
  /* @__PURE__ */ jsx2(GradientText, { colors: GRADIENTS.gold, children: brew }),
228
228
  /* @__PURE__ */ jsx2(GradientText, { colors: ["#B8860B", "#8B6914", "#6B4F10"], children: LOGO_TUI[i] })
229
229
  ] }, i)) }),
230
+ /* @__PURE__ */ jsx2(Text2, { children: " " }),
230
231
  /* @__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) => {
231
232
  const key = VIEW_KEYS[view];
232
233
  const viewLabel = t(VIEW_LABEL_KEYS[view]);
233
- const label = key ? `${key}:${viewLabel}` : viewLabel;
234
234
  const isPro = isProView(view);
235
+ const isActive = view === currentView;
236
+ const isAccount = view === "account";
235
237
  return /* @__PURE__ */ jsxs(React2.Fragment, { children: [
236
238
  i > 0 && /* @__PURE__ */ jsxs(Text2, { color: COLORS.border, children: [
237
239
  " ",
238
240
  "\u2502",
239
241
  " "
240
242
  ] }),
241
- /* @__PURE__ */ jsx2(
242
- Text2,
243
- {
244
- bold: view === currentView,
245
- color: view === currentView ? COLORS.success : COLORS.textSecondary,
246
- underline: view === currentView,
247
- children: view === currentView ? `\u25CF ${label}` : label
248
- }
249
- ),
243
+ isActive && /* @__PURE__ */ jsxs(Text2, { color: COLORS.sky, children: [
244
+ "\u25CF",
245
+ " "
246
+ ] }),
247
+ key ? /* @__PURE__ */ jsxs(Fragment2, { children: [
248
+ /* @__PURE__ */ jsx2(Text2, { bold: true, color: "#FFFFFF", children: key }),
249
+ /* @__PURE__ */ jsxs(Text2, { bold: isActive, underline: isActive, color: isActive ? COLORS.success : isAccount ? COLORS.gold : COLORS.textSecondary, children: [
250
+ " ",
251
+ viewLabel
252
+ ] })
253
+ ] }) : /* @__PURE__ */ jsx2(Text2, { bold: isActive, underline: isActive, color: isActive ? COLORS.success : isAccount ? COLORS.gold : COLORS.textSecondary, children: viewLabel }),
250
254
  isPro && /* @__PURE__ */ jsxs(Text2, { color: COLORS.brand, bold: true, children: [
251
255
  " ",
252
256
  t("pro_badge")
@@ -259,7 +263,7 @@ function Header() {
259
263
  // src/components/layout/footer.tsx
260
264
  import React3 from "react";
261
265
  import { Box as Box2, Text as Text3 } from "ink";
262
- import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
266
+ import { Fragment as Fragment3, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
263
267
  var VIEW_HINT_DEFS = {
264
268
  dashboard: [["1-0", "hint_navigate"], ["tab", "hint_next"], ["q", "hint_quit"]],
265
269
  installed: [["/", "hint_filter"], ["enter", "hint_info"], ["u", "hint_uninstall"], ["f", "hint_toggle"], ["tab", "hint_next"], ["q", "hint_quit"]],
@@ -272,11 +276,11 @@ var VIEW_HINT_DEFS = {
272
276
  "smart-cleanup": [["enter", "hint_toggle"], ["c", "hint_clean"], ["r", "hint_refresh"], ["q", "hint_quit"]],
273
277
  history: [["/", "hint_search"], ["enter", "hint_replay"], ["f", "hint_filter"], ["c", "hint_clear"], ["q", "hint_quit"]],
274
278
  "security-audit": [["r", "hint_scan"], ["enter", "hint_details"], ["u", "hint_upgrade"], ["q", "hint_quit"]],
275
- account: [["d", "hint_deactivate"], ["q", "hint_quit"]]
279
+ account: [["p", "hint_promo"], ["d", "hint_deactivate"], ["q", "hint_quit"]]
276
280
  };
277
281
  function HintItem({ def }) {
278
282
  if (def.length === 1) return /* @__PURE__ */ jsx3(Text3, { color: COLORS.gold, dimColor: true, children: t(def[0]) });
279
- return /* @__PURE__ */ jsxs2(Fragment2, { children: [
283
+ return /* @__PURE__ */ jsxs2(Fragment3, { children: [
280
284
  /* @__PURE__ */ jsx3(Text3, { color: COLORS.text, bold: true, children: def[0] }),
281
285
  /* @__PURE__ */ jsx3(Text3, { color: COLORS.textSecondary, children: ":" }),
282
286
  /* @__PURE__ */ jsx3(Text3, { color: COLORS.gold, dimColor: true, children: t(def[1]) })
@@ -530,9 +534,9 @@ function isEncryptedLicenseFile(obj) {
530
534
  async function getMachineId2() {
531
535
  try {
532
536
  const { readFile: readMachineId } = await import("fs/promises");
533
- const { join: join3 } = await import("path");
537
+ const { join: join4 } = await import("path");
534
538
  const { homedir: homedir2 } = await import("os");
535
- const machineIdPath = join3(homedir2(), ".brew-tui", "machine-id");
539
+ const machineIdPath = join4(homedir2(), ".brew-tui", "machine-id");
536
540
  return (await readMachineId(machineIdPath, "utf-8")).trim() || null;
537
541
  } catch {
538
542
  return null;
@@ -1430,7 +1434,10 @@ import { Box as Box7, Text as Text8 } from "ink";
1430
1434
  import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1431
1435
  function SectionHeader({ emoji, title, color = COLORS.gold, gradient, count }) {
1432
1436
  return /* @__PURE__ */ jsxs8(Box7, { gap: 1, children: [
1433
- /* @__PURE__ */ jsx8(Text8, { children: emoji }),
1437
+ /* @__PURE__ */ jsxs8(Text8, { children: [
1438
+ emoji,
1439
+ " "
1440
+ ] }),
1434
1441
  gradient ? /* @__PURE__ */ jsx8(GradientText, { colors: gradient, bold: true, children: title }) : /* @__PURE__ */ jsx8(Text8, { bold: true, color, children: title }),
1435
1442
  count !== void 0 && /* @__PURE__ */ jsxs8(Text8, { color: COLORS.textSecondary, children: [
1436
1443
  "(",
@@ -1442,9 +1449,9 @@ function SectionHeader({ emoji, title, color = COLORS.gold, gradient, count }) {
1442
1449
 
1443
1450
  // src/components/common/version-arrow.tsx
1444
1451
  import { Text as Text9 } from "ink";
1445
- import { Fragment as Fragment3, jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
1452
+ import { Fragment as Fragment4, jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
1446
1453
  function VersionArrow({ current, latest }) {
1447
- return /* @__PURE__ */ jsxs9(Fragment3, { children: [
1454
+ return /* @__PURE__ */ jsxs9(Fragment4, { children: [
1448
1455
  /* @__PURE__ */ jsxs9(Text9, { color: COLORS.muted, children: [
1449
1456
  t("version_installed"),
1450
1457
  " "
@@ -2287,7 +2294,7 @@ ${t("outdated_upgradeAllList", { list: allOutdated.map((p) => p.name).join(", ")
2287
2294
  // src/views/package-info.tsx
2288
2295
  import { useEffect as useEffect8, useRef as useRef4, useState as useState6 } from "react";
2289
2296
  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";
2297
+ import { Fragment as Fragment5, jsx as jsx19, jsxs as jsxs18 } from "react/jsx-runtime";
2291
2298
  var ACTION_PROGRESS_KEYS = {
2292
2299
  install: "pkgInfo_installing",
2293
2300
  uninstall: "pkgInfo_uninstalling",
@@ -2408,7 +2415,7 @@ function PackageInfoView() {
2408
2415
  "esc:",
2409
2416
  t("hint_cancel")
2410
2417
  ] }),
2411
- !stream.isRunning && /* @__PURE__ */ jsxs18(Fragment4, { children: [
2418
+ !stream.isRunning && /* @__PURE__ */ jsxs18(Fragment5, { children: [
2412
2419
  /* @__PURE__ */ jsx19(Text19, { color: stream.error ? COLORS.error : COLORS.success, bold: true, children: stream.error ? `\u2718 ${stream.error}` : `\u2714 ${t("pkgInfo_done")}` }),
2413
2420
  /* @__PURE__ */ jsxs18(Text19, { color: COLORS.textSecondary, children: [
2414
2421
  "esc:",
@@ -2470,7 +2477,7 @@ function PackageInfoView() {
2470
2477
  " ",
2471
2478
  formula.versions.stable
2472
2479
  ] }),
2473
- installed && /* @__PURE__ */ jsxs18(Fragment4, { children: [
2480
+ installed && /* @__PURE__ */ jsxs18(Fragment5, { children: [
2474
2481
  /* @__PURE__ */ jsxs18(Text19, { children: [
2475
2482
  /* @__PURE__ */ jsx19(Text19, { color: COLORS.muted, children: t("pkgInfo_installed") }),
2476
2483
  " ",
@@ -4009,17 +4016,93 @@ function SecurityAuditView() {
4009
4016
  // src/views/account.tsx
4010
4017
  import { useState as useState12 } from "react";
4011
4018
  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";
4019
+ import { TextInput as TextInput5 } from "@inkjs/ui";
4020
+
4021
+ // src/lib/license/promo.ts
4022
+ import { readFile as readFile4, writeFile as writeFile4, rename as rename3 } from "fs/promises";
4023
+ import { randomBytes as randomBytes2 } from "crypto";
4024
+ import { join as join3 } from "path";
4025
+ var PROMO_PATH = join3(DATA_DIR, "promo.json");
4026
+ var PROMO_API_URL = "https://api.molinesdesigns.com/promo";
4027
+ async function validatePromoCode(code) {
4028
+ const normalized = code.trim().toUpperCase();
4029
+ if (!normalized || normalized.length < 8) {
4030
+ return { valid: false, error: "Invalid promo code format" };
4031
+ }
4032
+ try {
4033
+ const res = await fetchWithTimeout(`${PROMO_API_URL}/validate`, {
4034
+ method: "POST",
4035
+ headers: { "Content-Type": "application/json" },
4036
+ body: JSON.stringify({ code: normalized })
4037
+ }, 1e4);
4038
+ if (!res.ok) {
4039
+ const body = await res.json().catch(() => ({}));
4040
+ return { valid: false, error: body.error ?? "Invalid or expired promo code" };
4041
+ }
4042
+ const data = await res.json();
4043
+ return {
4044
+ valid: true,
4045
+ type: data.type,
4046
+ durationDays: data.durationDays
4047
+ };
4048
+ } catch (err) {
4049
+ logger.error("Promo validation failed", { error: String(err) });
4050
+ return { valid: false, error: "Could not validate promo code. Check your connection." };
4051
+ }
4052
+ }
4053
+ async function redeemPromoCode(code) {
4054
+ const validation = await validatePromoCode(code);
4055
+ if (!validation.valid) {
4056
+ return { success: false, error: validation.error };
4057
+ }
4058
+ await ensureDataDirs();
4059
+ const redemption = {
4060
+ code: code.trim().toUpperCase(),
4061
+ redeemedAt: (/* @__PURE__ */ new Date()).toISOString(),
4062
+ expiresAt: new Date(Date.now() + validation.durationDays * 24 * 60 * 60 * 1e3).toISOString(),
4063
+ type: validation.type
4064
+ };
4065
+ let file = { version: 1, redemptions: [] };
4066
+ try {
4067
+ const raw = await readFile4(PROMO_PATH, "utf-8");
4068
+ file = JSON.parse(raw);
4069
+ } catch {
4070
+ }
4071
+ if (file.redemptions.some((r) => r.code === redemption.code)) {
4072
+ return { success: false, error: "This promo code has already been redeemed" };
4073
+ }
4074
+ file.redemptions.push(redemption);
4075
+ const tmpPath = PROMO_PATH + ".tmp";
4076
+ await writeFile4(tmpPath, JSON.stringify(file, null, 2), { encoding: "utf-8", mode: 384 });
4077
+ await rename3(tmpPath, PROMO_PATH);
4078
+ return { success: true, expiresAt: redemption.expiresAt };
4079
+ }
4080
+
4081
+ // src/views/account.tsx
4082
+ import { Fragment as Fragment6, jsx as jsx30, jsxs as jsxs29 } from "react/jsx-runtime";
4013
4083
  function AccountView() {
4014
4084
  const { status, license, deactivate: deactivate2, degradation } = useLicenseStore();
4015
4085
  const [confirmDeactivate, setConfirmDeactivate] = useState12(false);
4016
4086
  const [deactivating, setDeactivating] = useState12(false);
4017
4087
  const [deactivateError, setDeactivateError] = useState12(null);
4018
- useInput13((input) => {
4019
- if (confirmDeactivate || deactivating) return;
4088
+ const [promoMode, setPromoMode] = useState12(false);
4089
+ const [promoLoading, setPromoLoading] = useState12(false);
4090
+ const [promoResult, setPromoResult] = useState12(null);
4091
+ useInput13((input, key) => {
4092
+ if (confirmDeactivate || deactivating || promoMode) {
4093
+ if (key.escape && promoMode) {
4094
+ setPromoMode(false);
4095
+ setPromoResult(null);
4096
+ }
4097
+ return;
4098
+ }
4020
4099
  if (input === "d" && status === "pro") {
4021
4100
  setConfirmDeactivate(true);
4022
4101
  }
4102
+ if (input === "p") {
4103
+ setPromoMode(true);
4104
+ setPromoResult(null);
4105
+ }
4023
4106
  });
4024
4107
  const maskKey = (key) => {
4025
4108
  if (key.length <= 8) return key;
@@ -4059,7 +4142,7 @@ function AccountView() {
4059
4142
  (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", {
4060
4143
  days: Math.floor((Date.now() - new Date(license.lastValidatedAt).getTime()) / (24 * 60 * 60 * 1e3))
4061
4144
  }) }) }),
4062
- license && /* @__PURE__ */ jsxs29(Fragment5, { children: [
4145
+ license && /* @__PURE__ */ jsxs29(Fragment6, { children: [
4063
4146
  /* @__PURE__ */ jsxs29(Box28, { gap: 1, children: [
4064
4147
  /* @__PURE__ */ jsx30(Text29, { color: COLORS.muted, children: t("account_emailLabel") }),
4065
4148
  /* @__PURE__ */ jsx30(Text29, { children: license.customerEmail })
@@ -4110,10 +4193,41 @@ function AccountView() {
4110
4193
  deactivating && /* @__PURE__ */ jsx30(Text29, { color: COLORS.sky, children: t("account_deactivating") }),
4111
4194
  deactivateError && /* @__PURE__ */ jsx30(Text29, { color: COLORS.error, children: deactivateError })
4112
4195
  ] }),
4113
- /* @__PURE__ */ jsx30(Box28, { marginTop: 2, children: /* @__PURE__ */ jsxs29(Text29, { color: COLORS.textSecondary, children: [
4114
- status === "pro" ? `d:${t("hint_deactivate")}` : "",
4196
+ /* @__PURE__ */ jsx30(Box28, { flexDirection: "column", marginTop: 1, paddingLeft: 2, children: promoMode ? /* @__PURE__ */ jsxs29(Box28, { flexDirection: "column", gap: 1, children: [
4197
+ /* @__PURE__ */ jsx30(Text29, { bold: true, color: COLORS.gold, children: t("account_promoTitle") }),
4198
+ promoLoading ? /* @__PURE__ */ jsx30(Text29, { color: COLORS.sky, children: t("account_promoValidating") }) : /* @__PURE__ */ jsxs29(Box28, { gap: 1, children: [
4199
+ /* @__PURE__ */ jsx30(Text29, { color: COLORS.muted, children: t("account_promoLabel") }),
4200
+ /* @__PURE__ */ jsx30(
4201
+ TextInput5,
4202
+ {
4203
+ defaultValue: "",
4204
+ placeholder: "BREW-XXXX-XXXX",
4205
+ onSubmit: async (value) => {
4206
+ if (!value.trim()) return;
4207
+ setPromoLoading(true);
4208
+ try {
4209
+ const result = await redeemPromoCode(value);
4210
+ if (result.success) {
4211
+ setPromoResult({ success: true, message: t("account_promoSuccess", { expires: formatDate(result.expiresAt) }) });
4212
+ } else {
4213
+ setPromoResult({ success: false, message: result.error ?? t("account_promoInvalid") });
4214
+ }
4215
+ } catch {
4216
+ setPromoResult({ success: false, message: t("account_promoError") });
4217
+ } finally {
4218
+ setPromoLoading(false);
4219
+ }
4220
+ }
4221
+ }
4222
+ )
4223
+ ] }),
4224
+ promoResult && /* @__PURE__ */ jsx30(ResultBanner, { status: promoResult.success ? "success" : "error", message: promoResult.message }),
4225
+ /* @__PURE__ */ jsx30(Text29, { color: COLORS.textSecondary, dimColor: true, children: t("account_promoEsc") })
4226
+ ] }) : /* @__PURE__ */ jsx30(Text29, { color: COLORS.textSecondary, children: t("account_promoHint") }) }),
4227
+ /* @__PURE__ */ jsx30(Box28, { marginTop: 1, children: /* @__PURE__ */ jsxs29(Text29, { color: COLORS.textSecondary, children: [
4228
+ status === "pro" ? `d ${t("hint_deactivate")}` : "",
4115
4229
  " ",
4116
- t("app_version", { version: "0.3.0" })
4230
+ t("app_version", { version: "0.3.2" })
4117
4231
  ] }) })
4118
4232
  ] });
4119
4233
  }
@@ -4266,7 +4380,7 @@ async function runCli() {
4266
4380
  if (command === "install-brewbar") {
4267
4381
  await useLicenseStore.getState().initialize();
4268
4382
  const isPro = useLicenseStore.getState().isPro();
4269
- const { installBrewBar } = await import("./brewbar-installer-H5MLNNTD.js");
4383
+ const { installBrewBar } = await import("./brewbar-installer-SFVZEIA6.js");
4270
4384
  try {
4271
4385
  await installBrewBar(isPro, arg === "--force");
4272
4386
  console.log(t("cli_brewbarInstalled"));
@@ -4277,7 +4391,7 @@ async function runCli() {
4277
4391
  return;
4278
4392
  }
4279
4393
  if (command === "uninstall-brewbar") {
4280
- const { uninstallBrewBar } = await import("./brewbar-installer-H5MLNNTD.js");
4394
+ const { uninstallBrewBar } = await import("./brewbar-installer-SFVZEIA6.js");
4281
4395
  try {
4282
4396
  await uninstallBrewBar();
4283
4397
  console.log(t("cli_brewbarUninstalled"));