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
|
@@ -107,6 +107,8 @@ var en = {
|
|
|
107
107
|
dashboard_updated: "Updated:",
|
|
108
108
|
dashboard_outdatedPackages: "Outdated Packages",
|
|
109
109
|
dashboard_serviceErrors: "Service Errors",
|
|
110
|
+
dashboard_partialData: "Some Homebrew sections failed to load:",
|
|
111
|
+
dashboard_statError: "ERR",
|
|
110
112
|
// ── Installed ──
|
|
111
113
|
installed_formulaeCount: "Formulae ({{count}})",
|
|
112
114
|
installed_casksCount: "Casks ({{count}})",
|
|
@@ -123,6 +125,7 @@ var en = {
|
|
|
123
125
|
search_formulaeHeader: "=== Formulae ({{count}})",
|
|
124
126
|
search_casksHeader: "=== Casks ({{count}})",
|
|
125
127
|
search_noResults: "No results found",
|
|
128
|
+
search_minChars: "Type at least 2 characters to search.",
|
|
126
129
|
// ── Outdated ──
|
|
127
130
|
outdated_title: "Outdated Packages ({{count}})",
|
|
128
131
|
outdated_upgrading: "Upgrading...",
|
|
@@ -171,6 +174,7 @@ var en = {
|
|
|
171
174
|
profiles_title: "Package Profiles ({{count}})",
|
|
172
175
|
profiles_importTitle: "Importing profile...",
|
|
173
176
|
profiles_importComplete: "Import complete. Press any key.",
|
|
177
|
+
profiles_importPartial: "Import finished with errors. Check the log above.",
|
|
174
178
|
profiles_createName: "Create Profile \u2014 Name:",
|
|
175
179
|
profiles_namePlaceholder: "e.g. work, personal, project-x",
|
|
176
180
|
profiles_createDesc: 'Create Profile "{{name}}" \u2014 Description:',
|
|
@@ -242,6 +246,7 @@ var en = {
|
|
|
242
246
|
account_activateCmd: "brew-tui activate <key>",
|
|
243
247
|
account_licenseExpired: "Your license has expired. Renew to continue using Pro features.",
|
|
244
248
|
account_deactivating: "Deactivating...",
|
|
249
|
+
account_loading: "Loading license status...",
|
|
245
250
|
// ── Upgrade Prompt ──
|
|
246
251
|
upgrade_proFeature: "{{title}} \u2014 Pro Feature",
|
|
247
252
|
upgrade_profiles: "Package Profiles",
|
|
@@ -277,11 +282,16 @@ var en = {
|
|
|
277
282
|
cli_deactivated: "\u2714 License deactivated.",
|
|
278
283
|
cli_planFree: "Plan: Free",
|
|
279
284
|
cli_planPro: "Plan: Pro",
|
|
285
|
+
cli_planExpired: "Plan: Expired",
|
|
280
286
|
cli_confirmDeactivate: "Deactivate your Pro license on this machine? (y/N): ",
|
|
281
287
|
cli_deactivateCancelled: "Deactivation cancelled.",
|
|
282
288
|
cli_upgradeHint: "Run `brew-tui activate <key>` to upgrade to Pro.",
|
|
289
|
+
cli_revalidateHint: "Run `brew-tui revalidate` to refresh your current license.",
|
|
283
290
|
cli_email: "Email: {{email}}",
|
|
284
291
|
cli_status: "Status: {{status}}",
|
|
292
|
+
cli_revalidated: "\u2714 License revalidated.",
|
|
293
|
+
cli_revalidateGrace: "\u26A0 Could not reach the server. Your current license remains usable within the offline grace period.",
|
|
294
|
+
cli_revalidateFailed: "\u2718 License revalidation failed. Renew your subscription or activate a valid key.",
|
|
285
295
|
cli_rateLimited: "Too many activation attempts. Try again in {{minutes}} minutes.",
|
|
286
296
|
cli_cooldown: "Please wait before trying again.",
|
|
287
297
|
cli_brewbarInstalling: "Downloading BrewBar...",
|
|
@@ -290,6 +300,7 @@ var en = {
|
|
|
290
300
|
cli_brewbarUninstalled: "\u2714 BrewBar removed from /Applications.",
|
|
291
301
|
cli_brewbarNotInstalled: "BrewBar is not installed.",
|
|
292
302
|
cli_brewbarProRequired: "\u2718 BrewBar requires a Pro license.\n Run: brew-tui activate <key>",
|
|
303
|
+
cli_brewbarRevalidateRequired: "\u2718 BrewBar requires a valid Pro license.\n Run: brew-tui revalidate",
|
|
293
304
|
cli_brewbarMacOnly: "\u2718 BrewBar is only available on macOS.",
|
|
294
305
|
cli_brewbarDownloadFailed: "\u2718 Failed to download BrewBar: {{error}}",
|
|
295
306
|
cli_deactivateRemoteFailed: "\u26A0 Warning: Could not reach the server to deactivate remotely. The license was removed locally but may still count as active.",
|
|
@@ -302,7 +313,39 @@ var en = {
|
|
|
302
313
|
plural_warnings_other: "{{count}} warnings",
|
|
303
314
|
// ── Scroll indicators ──
|
|
304
315
|
scroll_moreAbove: "\u2191 {{count}} more",
|
|
305
|
-
scroll_moreBelow: "\u2193 {{count}} more"
|
|
316
|
+
scroll_moreBelow: "\u2193 {{count}} more",
|
|
317
|
+
// ── SCR-001: Cleanup warning ──
|
|
318
|
+
cleanup_warning_system_tools: "Warning: detected orphans may include dependencies of tools not managed by Homebrew. Review the list before proceeding.",
|
|
319
|
+
// ── SCR-002: Installed column headers ──
|
|
320
|
+
installed_col_package: "Package",
|
|
321
|
+
installed_col_version: "Version",
|
|
322
|
+
installed_col_status: "Status",
|
|
323
|
+
// ── SCR-003: Search failed ──
|
|
324
|
+
search_failed: "Search failed",
|
|
325
|
+
// ── SCR-007: Deactivate failed ──
|
|
326
|
+
deactivate_failed: "Deactivation failed",
|
|
327
|
+
// ── ACC-005: Version labels ──
|
|
328
|
+
version_installed: "installed:",
|
|
329
|
+
version_available: "available:",
|
|
330
|
+
// ── SCR-006: Upgrade-all replay warning ──
|
|
331
|
+
upgrade_all_warning: "Note: this will upgrade all currently outdated packages, which may differ from the original set.",
|
|
332
|
+
// ── SEG-007: Delete account ──
|
|
333
|
+
delete_account_confirm: "Delete all Brew-TUI data (~/.brew-tui)? This removes your license, profiles, and history. This cannot be undone.",
|
|
334
|
+
delete_account_success: "All Brew-TUI data has been removed.",
|
|
335
|
+
// ── SCR-012: Upgrade All packages list ──
|
|
336
|
+
outdated_upgradeAllList: "Packages to upgrade: {{list}}",
|
|
337
|
+
// ── SCR-005: Profile import summary ──
|
|
338
|
+
profiles_importSummary: "This profile contains {{formulae}} formulae and {{casks}} casks. Continue?",
|
|
339
|
+
// ── SCR-017: Network error ��─
|
|
340
|
+
security_networkError: "Could not reach OSV.dev vulnerability database. Check your internet connection.",
|
|
341
|
+
// ── ARQ-004: Dashboard last updated ──
|
|
342
|
+
dashboard_lastUpdated: "Last updated: {{time}}",
|
|
343
|
+
// ── SCR-014: Services last error ──
|
|
344
|
+
services_lastError: "Last error: {{error}}",
|
|
345
|
+
// ── SCR-010: Generic network error ──
|
|
346
|
+
error_network: "Network error: unable to reach the server.",
|
|
347
|
+
// ── ARQ-005: Security cache ──
|
|
348
|
+
security_cachedResults: "Showing cached results ({{time}} ago). Press r to rescan."
|
|
306
349
|
};
|
|
307
350
|
var en_default = en;
|
|
308
351
|
|
|
@@ -412,6 +455,8 @@ var es = {
|
|
|
412
455
|
dashboard_updated: "Actualizado:",
|
|
413
456
|
dashboard_outdatedPackages: "Paquetes Desactualizados",
|
|
414
457
|
dashboard_serviceErrors: "Errores de Servicios",
|
|
458
|
+
dashboard_partialData: "Algunas secciones de Homebrew no pudieron cargarse:",
|
|
459
|
+
dashboard_statError: "ERR",
|
|
415
460
|
// ── Installed ──
|
|
416
461
|
installed_formulaeCount: "Formulae ({{count}})",
|
|
417
462
|
installed_casksCount: "Casks ({{count}})",
|
|
@@ -428,6 +473,7 @@ var es = {
|
|
|
428
473
|
search_formulaeHeader: "=== Formulae ({{count}})",
|
|
429
474
|
search_casksHeader: "=== Casks ({{count}})",
|
|
430
475
|
search_noResults: "Sin resultados",
|
|
476
|
+
search_minChars: "Escribe al menos 2 caracteres para buscar.",
|
|
431
477
|
// ── Outdated ──
|
|
432
478
|
outdated_title: "Paquetes Desactualizados ({{count}})",
|
|
433
479
|
outdated_upgrading: "Actualizando...",
|
|
@@ -476,6 +522,7 @@ var es = {
|
|
|
476
522
|
profiles_title: "Perfiles de Paquetes ({{count}})",
|
|
477
523
|
profiles_importTitle: "Importando perfil...",
|
|
478
524
|
profiles_importComplete: "Importaci\xF3n completa. Presiona cualquier tecla.",
|
|
525
|
+
profiles_importPartial: "Importaci\xF3n finalizada con errores. Revisa el registro arriba.",
|
|
479
526
|
profiles_createName: "Crear Perfil \u2014 Nombre:",
|
|
480
527
|
profiles_namePlaceholder: "ej. trabajo, personal, proyecto-x",
|
|
481
528
|
profiles_createDesc: 'Crear Perfil "{{name}}" \u2014 Descripci\xF3n:',
|
|
@@ -547,6 +594,7 @@ var es = {
|
|
|
547
594
|
account_activateCmd: "brew-tui activate <clave>",
|
|
548
595
|
account_licenseExpired: "Tu licencia ha expirado. Renueva para seguir usando las funciones Pro.",
|
|
549
596
|
account_deactivating: "Desactivando...",
|
|
597
|
+
account_loading: "Cargando estado de la licencia...",
|
|
550
598
|
// ── Upgrade Prompt ──
|
|
551
599
|
upgrade_proFeature: "{{title}} \u2014 Funci\xF3n Pro",
|
|
552
600
|
upgrade_profiles: "Perfiles de Paquetes",
|
|
@@ -582,11 +630,16 @@ var es = {
|
|
|
582
630
|
cli_deactivated: "\u2714 Licencia desactivada.",
|
|
583
631
|
cli_planFree: "Plan: Gratis",
|
|
584
632
|
cli_planPro: "Plan: Pro",
|
|
633
|
+
cli_planExpired: "Plan: Expirada",
|
|
585
634
|
cli_confirmDeactivate: "\xBFDesactivar tu licencia Pro en esta m\xE1quina? (s/N): ",
|
|
586
635
|
cli_deactivateCancelled: "Desactivaci\xF3n cancelada.",
|
|
587
636
|
cli_upgradeHint: "Ejecuta `brew-tui activate <clave>` para actualizar a Pro.",
|
|
637
|
+
cli_revalidateHint: "Ejecuta `brew-tui revalidate` para refrescar tu licencia actual.",
|
|
588
638
|
cli_email: "Email: {{email}}",
|
|
589
639
|
cli_status: "Estado: {{status}}",
|
|
640
|
+
cli_revalidated: "\u2714 Licencia revalidada.",
|
|
641
|
+
cli_revalidateGrace: "\u26A0 No se pudo contactar al servidor. Tu licencia actual sigue siendo usable dentro del periodo de gracia offline.",
|
|
642
|
+
cli_revalidateFailed: "\u2718 La revalidaci\xF3n de la licencia fall\xF3. Renueva tu suscripci\xF3n o activa una clave v\xE1lida.",
|
|
590
643
|
cli_rateLimited: "Demasiados intentos de activaci\xF3n. Int\xE9ntalo en {{minutes}} minutos.",
|
|
591
644
|
cli_cooldown: "Por favor espera antes de intentar de nuevo.",
|
|
592
645
|
cli_brewbarInstalling: "Descargando BrewBar...",
|
|
@@ -595,6 +648,7 @@ var es = {
|
|
|
595
648
|
cli_brewbarUninstalled: "\u2714 BrewBar eliminado de /Applications.",
|
|
596
649
|
cli_brewbarNotInstalled: "BrewBar no est\xE1 instalado.",
|
|
597
650
|
cli_brewbarProRequired: "\u2718 BrewBar requiere una licencia Pro.\n Ejecuta: brew-tui activate <clave>",
|
|
651
|
+
cli_brewbarRevalidateRequired: "\u2718 BrewBar requiere una licencia Pro v\xE1lida.\n Ejecuta: brew-tui revalidate",
|
|
598
652
|
cli_brewbarMacOnly: "\u2718 BrewBar solo est\xE1 disponible en macOS.",
|
|
599
653
|
cli_brewbarDownloadFailed: "\u2718 Error al descargar BrewBar: {{error}}",
|
|
600
654
|
cli_deactivateRemoteFailed: "\u26A0 Advertencia: No se pudo contactar al servidor para desactivar remotamente. La licencia se elimin\xF3 localmente pero puede seguir contando como activa.",
|
|
@@ -607,7 +661,39 @@ var es = {
|
|
|
607
661
|
plural_warnings_other: "{{count}} advertencias",
|
|
608
662
|
// ── Scroll indicators ──
|
|
609
663
|
scroll_moreAbove: "\u2191 {{count}} m\xE1s",
|
|
610
|
-
scroll_moreBelow: "\u2193 {{count}} m\xE1s"
|
|
664
|
+
scroll_moreBelow: "\u2193 {{count}} m\xE1s",
|
|
665
|
+
// ── SCR-001: Cleanup warning ──
|
|
666
|
+
cleanup_warning_system_tools: "Advertencia: los hu\xE9rfanos detectados pueden incluir dependencias de herramientas no gestionadas por Homebrew. Revisa la lista antes de continuar.",
|
|
667
|
+
// ── SCR-002: Installed column headers ──
|
|
668
|
+
installed_col_package: "Paquete",
|
|
669
|
+
installed_col_version: "Versi\xF3n",
|
|
670
|
+
installed_col_status: "Estado",
|
|
671
|
+
// ── SCR-003: Search failed ──
|
|
672
|
+
search_failed: "B\xFAsqueda fallida",
|
|
673
|
+
// ── SCR-007: Deactivate failed ──
|
|
674
|
+
deactivate_failed: "Error al desactivar",
|
|
675
|
+
// ── ACC-005: Version labels ──
|
|
676
|
+
version_installed: "instalado:",
|
|
677
|
+
version_available: "disponible:",
|
|
678
|
+
// ── SCR-006: Upgrade-all replay warning ──
|
|
679
|
+
upgrade_all_warning: "Nota: esto actualizar\xE1 todos los paquetes desactualizados actualmente, que pueden diferir del conjunto original.",
|
|
680
|
+
// ── SEG-007: Delete account ──
|
|
681
|
+
delete_account_confirm: "\xBFEliminar todos los datos de Brew-TUI (~/.brew-tui)? Esto elimina tu licencia, perfiles e historial. Esta acci\xF3n no se puede deshacer.",
|
|
682
|
+
delete_account_success: "Todos los datos de Brew-TUI han sido eliminados.",
|
|
683
|
+
// ── SCR-012: Upgrade All packages list ──
|
|
684
|
+
outdated_upgradeAllList: "Paquetes a actualizar: {{list}}",
|
|
685
|
+
// ── SCR-005: Profile import summary ──
|
|
686
|
+
profiles_importSummary: "Este perfil contiene {{formulae}} formulae y {{casks}} casks. \xBFContinuar?",
|
|
687
|
+
// ── SCR-017: Network error ──
|
|
688
|
+
security_networkError: "No se pudo conectar con la base de datos de vulnerabilidades OSV.dev. Verifica tu conexi\xF3n a internet.",
|
|
689
|
+
// ── ARQ-004: Dashboard last updated ──
|
|
690
|
+
dashboard_lastUpdated: "\xDAltima actualizaci\xF3n: {{time}}",
|
|
691
|
+
// ── SCR-014: Services last error ──
|
|
692
|
+
services_lastError: "\xDAltimo error: {{error}}",
|
|
693
|
+
// ── SCR-010: Generic network error ──
|
|
694
|
+
error_network: "Error de red: no se puede conectar con el servidor.",
|
|
695
|
+
// ── ARQ-005: Security cache ──
|
|
696
|
+
security_cachedResults: "Mostrando resultados en cach\xE9 (hace {{time}}). Presiona r para re-escanear."
|
|
611
697
|
};
|
|
612
698
|
var es_default = es;
|
|
613
699
|
|
|
@@ -631,6 +717,9 @@ var useLocaleStore = create((set) => ({
|
|
|
631
717
|
locale: detectLocale(),
|
|
632
718
|
setLocale: (locale) => set({ locale })
|
|
633
719
|
}));
|
|
720
|
+
function getLocale() {
|
|
721
|
+
return useLocaleStore.getState().locale;
|
|
722
|
+
}
|
|
634
723
|
function t(key, values) {
|
|
635
724
|
const locale = useLocaleStore.getState().locale;
|
|
636
725
|
let text = locales[locale][key] ?? locales["en"][key] ?? key;
|
|
@@ -646,533 +735,30 @@ function tp(baseKey, count, values) {
|
|
|
646
735
|
return t(`${baseKey}${suffix}`, { count, ...values });
|
|
647
736
|
}
|
|
648
737
|
|
|
649
|
-
// src/
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
var
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
}
|
|
738
|
+
// src/utils/logger.ts
|
|
739
|
+
var LOG_LEVELS = { debug: 0, info: 1, warn: 2, error: 3 };
|
|
740
|
+
var currentLevel = process.env.LOG_LEVEL || "warn";
|
|
741
|
+
function shouldLog(level) {
|
|
742
|
+
return LOG_LEVELS[level] >= LOG_LEVELS[currentLevel];
|
|
743
|
+
}
|
|
744
|
+
var logger = {
|
|
745
|
+
debug: (msg, ctx) => shouldLog("debug") && console.debug(`[DEBUG] ${msg}`, ctx || ""),
|
|
746
|
+
info: (msg, ctx) => shouldLog("info") && console.info(`[INFO] ${msg}`, ctx || ""),
|
|
747
|
+
warn: (msg, ctx) => shouldLog("warn") && console.warn(`[WARN] ${msg}`, ctx || ""),
|
|
748
|
+
error: (msg, ctx) => shouldLog("error") && console.error(`[ERROR] ${msg}`, ctx || "")
|
|
749
|
+
};
|
|
661
750
|
|
|
662
751
|
// src/lib/fetch-timeout.ts
|
|
663
752
|
function fetchWithTimeout(url, options = {}, timeoutMs = 15e3) {
|
|
664
753
|
return fetch(url, { ...options, signal: AbortSignal.timeout(timeoutMs) });
|
|
665
754
|
}
|
|
666
755
|
|
|
667
|
-
// src/lib/license/license-manager.ts
|
|
668
|
-
import { readFile, writeFile, rename, rm } from "fs/promises";
|
|
669
|
-
import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from "crypto";
|
|
670
|
-
|
|
671
|
-
// src/lib/license/polar-api.ts
|
|
672
|
-
import { hostname } from "os";
|
|
673
|
-
var BASE_URL = "https://api.polar.sh/v1/customer-portal/license-keys";
|
|
674
|
-
var POLAR_ORGANIZATION_ID = "b8f245c0-d116-4457-92fb-1bda47139f82";
|
|
675
|
-
function validateApiUrl(url) {
|
|
676
|
-
const parsed = new URL(url);
|
|
677
|
-
if (parsed.protocol !== "https:") {
|
|
678
|
-
throw new Error("HTTPS required for license API");
|
|
679
|
-
}
|
|
680
|
-
if (!parsed.hostname.endsWith("polar.sh")) {
|
|
681
|
-
throw new Error("Invalid API host");
|
|
682
|
-
}
|
|
683
|
-
}
|
|
684
|
-
async function post(endpoint, body, expectEmpty = false) {
|
|
685
|
-
const url = `${BASE_URL}/${endpoint}`;
|
|
686
|
-
validateApiUrl(url);
|
|
687
|
-
const res = await fetchWithTimeout(url, {
|
|
688
|
-
method: "POST",
|
|
689
|
-
headers: { "Content-Type": "application/json" },
|
|
690
|
-
body: JSON.stringify(body)
|
|
691
|
-
}, 15e3);
|
|
692
|
-
if (!res.ok) {
|
|
693
|
-
let message = `Request failed with status ${res.status}`;
|
|
694
|
-
try {
|
|
695
|
-
const errBody = await res.json();
|
|
696
|
-
if (typeof errBody.detail === "string") message = errBody.detail;
|
|
697
|
-
else if (typeof errBody.error === "string") message = errBody.error;
|
|
698
|
-
else if (typeof errBody.message === "string") message = errBody.message;
|
|
699
|
-
} catch {
|
|
700
|
-
}
|
|
701
|
-
throw new Error(message);
|
|
702
|
-
}
|
|
703
|
-
if (expectEmpty || res.status === 204) return void 0;
|
|
704
|
-
return res.json();
|
|
705
|
-
}
|
|
706
|
-
async function activateLicense(key) {
|
|
707
|
-
const activation = await post("activate", {
|
|
708
|
-
key,
|
|
709
|
-
organization_id: POLAR_ORGANIZATION_ID,
|
|
710
|
-
label: hostname()
|
|
711
|
-
});
|
|
712
|
-
let customerEmail = "";
|
|
713
|
-
let customerName = "";
|
|
714
|
-
try {
|
|
715
|
-
const validated = await post("validate", {
|
|
716
|
-
key,
|
|
717
|
-
organization_id: POLAR_ORGANIZATION_ID,
|
|
718
|
-
activation_id: activation.id
|
|
719
|
-
});
|
|
720
|
-
customerEmail = validated.customer?.email ?? "";
|
|
721
|
-
customerName = validated.customer?.name ?? "";
|
|
722
|
-
} catch {
|
|
723
|
-
}
|
|
724
|
-
return {
|
|
725
|
-
activated: true,
|
|
726
|
-
error: null,
|
|
727
|
-
instance: { id: activation.id },
|
|
728
|
-
license_key: {
|
|
729
|
-
id: 0,
|
|
730
|
-
status: activation.license_key.status,
|
|
731
|
-
key,
|
|
732
|
-
activation_limit: 0,
|
|
733
|
-
activations_count: 0,
|
|
734
|
-
expires_at: activation.license_key.expires_at
|
|
735
|
-
},
|
|
736
|
-
meta: { customer_email: customerEmail, customer_name: customerName }
|
|
737
|
-
};
|
|
738
|
-
}
|
|
739
|
-
async function validateLicense(key, instanceId) {
|
|
740
|
-
const res = await post("validate", {
|
|
741
|
-
key,
|
|
742
|
-
organization_id: POLAR_ORGANIZATION_ID,
|
|
743
|
-
activation_id: instanceId
|
|
744
|
-
});
|
|
745
|
-
const notExpired = res.expires_at === null || new Date(res.expires_at) > /* @__PURE__ */ new Date();
|
|
746
|
-
const valid = res.status === "granted" && notExpired;
|
|
747
|
-
return {
|
|
748
|
-
valid,
|
|
749
|
-
error: valid ? null : `License ${res.status}`,
|
|
750
|
-
license_key: {
|
|
751
|
-
id: 0,
|
|
752
|
-
status: res.status,
|
|
753
|
-
key,
|
|
754
|
-
expires_at: res.expires_at
|
|
755
|
-
},
|
|
756
|
-
instance: { id: instanceId }
|
|
757
|
-
};
|
|
758
|
-
}
|
|
759
|
-
async function deactivateLicense(key, instanceId) {
|
|
760
|
-
await post(
|
|
761
|
-
"deactivate",
|
|
762
|
-
{ key, organization_id: POLAR_ORGANIZATION_ID, activation_id: instanceId },
|
|
763
|
-
true
|
|
764
|
-
);
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
// src/lib/license/license-manager.ts
|
|
768
|
-
var REVALIDATION_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
769
|
-
var GRACE_PERIOD_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
770
|
-
var ACTIVATION_COOLDOWN_MS = 3e4;
|
|
771
|
-
var MAX_ATTEMPTS = 5;
|
|
772
|
-
var LOCKOUT_MS = 15 * 60 * 1e3;
|
|
773
|
-
var tracker = {
|
|
774
|
-
attempts: 0,
|
|
775
|
-
lastAttempt: 0,
|
|
776
|
-
lockedUntil: 0
|
|
777
|
-
};
|
|
778
|
-
function checkRateLimit() {
|
|
779
|
-
const now = Date.now();
|
|
780
|
-
if (now < tracker.lockedUntil) {
|
|
781
|
-
const remaining = Math.ceil((tracker.lockedUntil - now) / 6e4);
|
|
782
|
-
throw new Error(t("cli_rateLimited", { minutes: remaining }));
|
|
783
|
-
}
|
|
784
|
-
if (now - tracker.lastAttempt < ACTIVATION_COOLDOWN_MS) {
|
|
785
|
-
throw new Error(t("cli_cooldown"));
|
|
786
|
-
}
|
|
787
|
-
}
|
|
788
|
-
function recordAttempt(success) {
|
|
789
|
-
const now = Date.now();
|
|
790
|
-
tracker.lastAttempt = now;
|
|
791
|
-
if (success) {
|
|
792
|
-
tracker.attempts = 0;
|
|
793
|
-
return;
|
|
794
|
-
}
|
|
795
|
-
tracker.attempts++;
|
|
796
|
-
if (tracker.attempts >= MAX_ATTEMPTS) {
|
|
797
|
-
tracker.lockedUntil = now + LOCKOUT_MS;
|
|
798
|
-
tracker.attempts = 0;
|
|
799
|
-
}
|
|
800
|
-
}
|
|
801
|
-
var ENCRYPTION_SECRET = "brew-tui-license-aes256gcm-v1";
|
|
802
|
-
var SCRYPT_SALT = "brew-tui-salt-v1";
|
|
803
|
-
var _derivedKey = null;
|
|
804
|
-
function deriveEncryptionKey() {
|
|
805
|
-
if (!_derivedKey) _derivedKey = scryptSync(ENCRYPTION_SECRET, SCRYPT_SALT, 32);
|
|
806
|
-
return _derivedKey;
|
|
807
|
-
}
|
|
808
|
-
function encryptLicenseData(data) {
|
|
809
|
-
const key = deriveEncryptionKey();
|
|
810
|
-
const iv = randomBytes(12);
|
|
811
|
-
const cipher = createCipheriv("aes-256-gcm", key, iv);
|
|
812
|
-
const plaintext = JSON.stringify(data);
|
|
813
|
-
const ciphertext = Buffer.concat([cipher.update(plaintext, "utf-8"), cipher.final()]);
|
|
814
|
-
const tag = cipher.getAuthTag();
|
|
815
|
-
return {
|
|
816
|
-
encrypted: ciphertext.toString("base64"),
|
|
817
|
-
iv: iv.toString("base64"),
|
|
818
|
-
tag: tag.toString("base64")
|
|
819
|
-
};
|
|
820
|
-
}
|
|
821
|
-
function decryptLicenseData(encrypted, iv, tag) {
|
|
822
|
-
const key = deriveEncryptionKey();
|
|
823
|
-
const decipher = createDecipheriv(
|
|
824
|
-
"aes-256-gcm",
|
|
825
|
-
key,
|
|
826
|
-
Buffer.from(iv, "base64")
|
|
827
|
-
);
|
|
828
|
-
decipher.setAuthTag(Buffer.from(tag, "base64"));
|
|
829
|
-
const plaintext = Buffer.concat([
|
|
830
|
-
decipher.update(Buffer.from(encrypted, "base64")),
|
|
831
|
-
decipher.final()
|
|
832
|
-
]);
|
|
833
|
-
return JSON.parse(plaintext.toString("utf-8"));
|
|
834
|
-
}
|
|
835
|
-
async function loadLicense() {
|
|
836
|
-
try {
|
|
837
|
-
const raw = await readFile(LICENSE_PATH, "utf-8");
|
|
838
|
-
const file = JSON.parse(raw);
|
|
839
|
-
if (file.version !== 1) {
|
|
840
|
-
throw new Error("Unsupported data version");
|
|
841
|
-
}
|
|
842
|
-
if (file.encrypted && file.iv && file.tag) {
|
|
843
|
-
const data = decryptLicenseData(file.encrypted, file.iv, file.tag);
|
|
844
|
-
return data;
|
|
845
|
-
}
|
|
846
|
-
if (file.license) {
|
|
847
|
-
const data = file.license;
|
|
848
|
-
await saveLicense(data);
|
|
849
|
-
return data;
|
|
850
|
-
}
|
|
851
|
-
return null;
|
|
852
|
-
} catch {
|
|
853
|
-
return null;
|
|
854
|
-
}
|
|
855
|
-
}
|
|
856
|
-
async function saveLicense(data) {
|
|
857
|
-
await ensureDataDirs();
|
|
858
|
-
const { encrypted, iv, tag } = encryptLicenseData(data);
|
|
859
|
-
const file = { version: 1, encrypted, iv, tag };
|
|
860
|
-
const tmpPath = LICENSE_PATH + ".tmp";
|
|
861
|
-
await writeFile(tmpPath, JSON.stringify(file, null, 2), { encoding: "utf-8", mode: 384 });
|
|
862
|
-
await rename(tmpPath, LICENSE_PATH);
|
|
863
|
-
}
|
|
864
|
-
async function clearLicense() {
|
|
865
|
-
try {
|
|
866
|
-
await rm(LICENSE_PATH);
|
|
867
|
-
} catch {
|
|
868
|
-
}
|
|
869
|
-
}
|
|
870
|
-
function isExpired(license) {
|
|
871
|
-
if (!license.expiresAt) return false;
|
|
872
|
-
return new Date(license.expiresAt).getTime() < Date.now();
|
|
873
|
-
}
|
|
874
|
-
function needsRevalidation(license) {
|
|
875
|
-
const lastValidated = new Date(license.lastValidatedAt).getTime();
|
|
876
|
-
if (isNaN(lastValidated)) return true;
|
|
877
|
-
return Date.now() - lastValidated > REVALIDATION_INTERVAL_MS;
|
|
878
|
-
}
|
|
879
|
-
function isWithinGracePeriod(license) {
|
|
880
|
-
const lastValidated = new Date(license.lastValidatedAt).getTime();
|
|
881
|
-
if (isNaN(lastValidated)) return false;
|
|
882
|
-
return Date.now() - lastValidated < GRACE_PERIOD_MS;
|
|
883
|
-
}
|
|
884
|
-
function getDegradationLevel(license) {
|
|
885
|
-
const lastValidated = new Date(license.lastValidatedAt).getTime();
|
|
886
|
-
if (isNaN(lastValidated)) return "expired";
|
|
887
|
-
const elapsed = Date.now() - lastValidated;
|
|
888
|
-
if (elapsed < 0) return "none";
|
|
889
|
-
const days = elapsed / (24 * 60 * 60 * 1e3);
|
|
890
|
-
if (days <= 7) return "none";
|
|
891
|
-
if (days <= 14) return "warning";
|
|
892
|
-
if (days <= 30) return "limited";
|
|
893
|
-
return "expired";
|
|
894
|
-
}
|
|
895
|
-
function validateLicenseKey(key) {
|
|
896
|
-
if (key.length < 10 || key.length > 100) {
|
|
897
|
-
throw new Error("Invalid license key format");
|
|
898
|
-
}
|
|
899
|
-
if (!/^[\w-]+$/.test(key)) {
|
|
900
|
-
throw new Error("Invalid license key format");
|
|
901
|
-
}
|
|
902
|
-
}
|
|
903
|
-
async function activate(key) {
|
|
904
|
-
validateLicenseKey(key);
|
|
905
|
-
checkRateLimit();
|
|
906
|
-
let success = false;
|
|
907
|
-
try {
|
|
908
|
-
const res = await activateLicense(key);
|
|
909
|
-
if (!res.activated) {
|
|
910
|
-
throw new Error(res.error ?? "Activation failed");
|
|
911
|
-
}
|
|
912
|
-
const license = {
|
|
913
|
-
key,
|
|
914
|
-
instanceId: res.instance.id,
|
|
915
|
-
status: "active",
|
|
916
|
-
customerEmail: res.meta.customer_email,
|
|
917
|
-
customerName: res.meta.customer_name,
|
|
918
|
-
plan: "pro",
|
|
919
|
-
activatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
920
|
-
expiresAt: res.license_key.expires_at,
|
|
921
|
-
lastValidatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
922
|
-
};
|
|
923
|
-
await saveLicense(license);
|
|
924
|
-
success = true;
|
|
925
|
-
return license;
|
|
926
|
-
} finally {
|
|
927
|
-
recordAttempt(success);
|
|
928
|
-
}
|
|
929
|
-
}
|
|
930
|
-
async function revalidate(license) {
|
|
931
|
-
try {
|
|
932
|
-
const res = await validateLicense(license.key, license.instanceId);
|
|
933
|
-
if (res.valid) {
|
|
934
|
-
const updated = {
|
|
935
|
-
...license,
|
|
936
|
-
lastValidatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
937
|
-
status: "active",
|
|
938
|
-
expiresAt: res.license_key.expires_at
|
|
939
|
-
};
|
|
940
|
-
await saveLicense(updated);
|
|
941
|
-
return true;
|
|
942
|
-
}
|
|
943
|
-
await saveLicense({ ...license, status: "expired" });
|
|
944
|
-
return false;
|
|
945
|
-
} catch {
|
|
946
|
-
return isWithinGracePeriod(license);
|
|
947
|
-
}
|
|
948
|
-
}
|
|
949
|
-
async function deactivate(license) {
|
|
950
|
-
let remoteSuccess = false;
|
|
951
|
-
for (let attempt = 0; attempt < 3; attempt++) {
|
|
952
|
-
try {
|
|
953
|
-
await deactivateLicense(license.key, license.instanceId);
|
|
954
|
-
remoteSuccess = true;
|
|
955
|
-
break;
|
|
956
|
-
} catch {
|
|
957
|
-
if (attempt < 2) await new Promise((r) => setTimeout(r, 1e3));
|
|
958
|
-
}
|
|
959
|
-
}
|
|
960
|
-
await clearLicense();
|
|
961
|
-
return { remoteSuccess };
|
|
962
|
-
}
|
|
963
|
-
|
|
964
|
-
// src/stores/license-store.ts
|
|
965
|
-
import { create as create2 } from "zustand";
|
|
966
|
-
|
|
967
|
-
// src/lib/license/anti-tamper.ts
|
|
968
|
-
var _originalIsPro = null;
|
|
969
|
-
var _originalGetState = null;
|
|
970
|
-
var _storeApi = null;
|
|
971
|
-
function initStoreIntegrity(store) {
|
|
972
|
-
_storeApi = store;
|
|
973
|
-
_originalIsPro = store.getState().isPro;
|
|
974
|
-
_originalGetState = store.getState;
|
|
975
|
-
}
|
|
976
|
-
function verifyStoreIntegrity() {
|
|
977
|
-
if (!_storeApi || !_originalIsPro || !_originalGetState) return false;
|
|
978
|
-
const state = _storeApi.getState();
|
|
979
|
-
if (state.isPro !== _originalIsPro) return false;
|
|
980
|
-
if (_storeApi.getState !== _originalGetState) return false;
|
|
981
|
-
if (state.status === "free" && state.isPro()) return false;
|
|
982
|
-
return true;
|
|
983
|
-
}
|
|
984
|
-
|
|
985
|
-
// src/stores/license-store.ts
|
|
986
|
-
var REVALIDATION_CHECK_MS = 60 * 60 * 1e3;
|
|
987
|
-
var _revalidating = false;
|
|
988
|
-
var _revalidationInterval = null;
|
|
989
|
-
var useLicenseStore = create2((set, get) => ({
|
|
990
|
-
status: "validating",
|
|
991
|
-
license: null,
|
|
992
|
-
error: null,
|
|
993
|
-
degradation: "none",
|
|
994
|
-
initialize: async () => {
|
|
995
|
-
initStoreIntegrity(useLicenseStore);
|
|
996
|
-
await ensureDataDirs();
|
|
997
|
-
const license = await loadLicense();
|
|
998
|
-
if (!license) {
|
|
999
|
-
set({ status: "free", license: null, degradation: "none" });
|
|
1000
|
-
return;
|
|
1001
|
-
}
|
|
1002
|
-
if (isExpired(license)) {
|
|
1003
|
-
set({ status: "expired", license, degradation: "expired" });
|
|
1004
|
-
return;
|
|
1005
|
-
}
|
|
1006
|
-
const level = getDegradationLevel(license);
|
|
1007
|
-
if (level === "expired") {
|
|
1008
|
-
set({ status: "expired", license, degradation: "expired" });
|
|
1009
|
-
return;
|
|
1010
|
-
}
|
|
1011
|
-
set({ status: "pro", license, degradation: level });
|
|
1012
|
-
if (needsRevalidation(license) && !_revalidating) {
|
|
1013
|
-
_revalidating = true;
|
|
1014
|
-
try {
|
|
1015
|
-
const valid = await revalidate(license);
|
|
1016
|
-
if (!valid) {
|
|
1017
|
-
set({ status: "expired", license: { ...license, status: "expired" } });
|
|
1018
|
-
} else {
|
|
1019
|
-
const updated = await loadLicense();
|
|
1020
|
-
if (updated) set({ license: updated });
|
|
1021
|
-
}
|
|
1022
|
-
} finally {
|
|
1023
|
-
_revalidating = false;
|
|
1024
|
-
}
|
|
1025
|
-
}
|
|
1026
|
-
if (_revalidationInterval) clearInterval(_revalidationInterval);
|
|
1027
|
-
_revalidationInterval = setInterval(async () => {
|
|
1028
|
-
const current = get().license;
|
|
1029
|
-
if (!current || get().status !== "pro") return;
|
|
1030
|
-
if (!needsRevalidation(current)) return;
|
|
1031
|
-
if (_revalidating) return;
|
|
1032
|
-
_revalidating = true;
|
|
1033
|
-
try {
|
|
1034
|
-
const valid = await revalidate(current);
|
|
1035
|
-
if (!valid) {
|
|
1036
|
-
set({ status: "expired", license: { ...current, status: "expired" } });
|
|
1037
|
-
} else {
|
|
1038
|
-
const updated = await loadLicense();
|
|
1039
|
-
if (updated) set({ license: updated });
|
|
1040
|
-
}
|
|
1041
|
-
} finally {
|
|
1042
|
-
_revalidating = false;
|
|
1043
|
-
}
|
|
1044
|
-
}, REVALIDATION_CHECK_MS);
|
|
1045
|
-
_revalidationInterval.unref();
|
|
1046
|
-
},
|
|
1047
|
-
activate: async (key) => {
|
|
1048
|
-
set({ error: null });
|
|
1049
|
-
try {
|
|
1050
|
-
const license = await activate(key);
|
|
1051
|
-
set({ status: "pro", license });
|
|
1052
|
-
return true;
|
|
1053
|
-
} catch (err) {
|
|
1054
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
1055
|
-
set({ error: msg });
|
|
1056
|
-
return false;
|
|
1057
|
-
}
|
|
1058
|
-
},
|
|
1059
|
-
deactivate: async () => {
|
|
1060
|
-
const { license } = get();
|
|
1061
|
-
if (license) {
|
|
1062
|
-
const { remoteSuccess } = await deactivate(license);
|
|
1063
|
-
if (!remoteSuccess) {
|
|
1064
|
-
set({ status: "free", license: null, error: "License removed locally but server deactivation failed. It may remain active remotely." });
|
|
1065
|
-
return;
|
|
1066
|
-
}
|
|
1067
|
-
}
|
|
1068
|
-
set({ status: "free", license: null, error: null });
|
|
1069
|
-
},
|
|
1070
|
-
isPro: () => get().status === "pro"
|
|
1071
|
-
}));
|
|
1072
|
-
|
|
1073
|
-
// src/lib/license/anti-debug.ts
|
|
1074
|
-
import inspector from "inspector";
|
|
1075
|
-
function isDebuggerAttached() {
|
|
1076
|
-
if (false) return false;
|
|
1077
|
-
if (inspector.url()) return true;
|
|
1078
|
-
if (process.execArgv.some((a) => a.includes("--inspect") || a.includes("--debug"))) return true;
|
|
1079
|
-
if (process.env.NODE_OPTIONS?.includes("--inspect")) return true;
|
|
1080
|
-
return false;
|
|
1081
|
-
}
|
|
1082
|
-
|
|
1083
|
-
// src/lib/license/canary.ts
|
|
1084
|
-
var _canaryTripped = false;
|
|
1085
|
-
function isProUnlocked() {
|
|
1086
|
-
return false;
|
|
1087
|
-
}
|
|
1088
|
-
function hasProAccess() {
|
|
1089
|
-
return false;
|
|
1090
|
-
}
|
|
1091
|
-
function isLicenseValid() {
|
|
1092
|
-
return false;
|
|
1093
|
-
}
|
|
1094
|
-
function checkCanaries() {
|
|
1095
|
-
if (isProUnlocked()) {
|
|
1096
|
-
_canaryTripped = true;
|
|
1097
|
-
return false;
|
|
1098
|
-
}
|
|
1099
|
-
if (hasProAccess()) {
|
|
1100
|
-
_canaryTripped = true;
|
|
1101
|
-
return false;
|
|
1102
|
-
}
|
|
1103
|
-
if (isLicenseValid()) {
|
|
1104
|
-
_canaryTripped = true;
|
|
1105
|
-
return false;
|
|
1106
|
-
}
|
|
1107
|
-
return !_canaryTripped;
|
|
1108
|
-
}
|
|
1109
|
-
|
|
1110
|
-
// src/lib/license/integrity.ts
|
|
1111
|
-
import { readFileSync } from "fs";
|
|
1112
|
-
import { createHash } from "crypto";
|
|
1113
|
-
import { fileURLToPath } from "url";
|
|
1114
|
-
var _baselineHash = null;
|
|
1115
|
-
function _captureBaseline() {
|
|
1116
|
-
try {
|
|
1117
|
-
const bundlePath = fileURLToPath(import.meta.url);
|
|
1118
|
-
const content = readFileSync(bundlePath, "utf-8");
|
|
1119
|
-
return createHash("sha256").update(content).digest("hex");
|
|
1120
|
-
} catch {
|
|
1121
|
-
return null;
|
|
1122
|
-
}
|
|
1123
|
-
}
|
|
1124
|
-
_baselineHash = _captureBaseline();
|
|
1125
|
-
function checkBundleIntegrity() {
|
|
1126
|
-
if (_baselineHash === null) {
|
|
1127
|
-
return true;
|
|
1128
|
-
}
|
|
1129
|
-
try {
|
|
1130
|
-
const bundlePath = fileURLToPath(import.meta.url);
|
|
1131
|
-
const content = readFileSync(bundlePath, "utf-8");
|
|
1132
|
-
const current = createHash("sha256").update(content).digest("hex");
|
|
1133
|
-
return current === _baselineHash;
|
|
1134
|
-
} catch {
|
|
1135
|
-
return true;
|
|
1136
|
-
}
|
|
1137
|
-
}
|
|
1138
|
-
|
|
1139
|
-
// src/lib/license/pro-guard.ts
|
|
1140
|
-
var _P = String.fromCharCode(112, 114, 111);
|
|
1141
|
-
function _verify(status) {
|
|
1142
|
-
return status === _P;
|
|
1143
|
-
}
|
|
1144
|
-
function verifyPro(license, status) {
|
|
1145
|
-
if (isDebuggerAttached()) return false;
|
|
1146
|
-
if (!checkBundleIntegrity()) return false;
|
|
1147
|
-
if (!verifyStoreIntegrity()) return false;
|
|
1148
|
-
if (!checkCanaries()) return false;
|
|
1149
|
-
const directCheck = status === "pro";
|
|
1150
|
-
const indirectCheck = _verify(status);
|
|
1151
|
-
if (!directCheck || !indirectCheck) return false;
|
|
1152
|
-
if (license) {
|
|
1153
|
-
const level = getDegradationLevel(license);
|
|
1154
|
-
if (level === "expired" || level === "limited") return false;
|
|
1155
|
-
}
|
|
1156
|
-
return true;
|
|
1157
|
-
}
|
|
1158
|
-
function requirePro(license, status) {
|
|
1159
|
-
if (!verifyPro(license, status)) {
|
|
1160
|
-
throw new Error("Pro license required");
|
|
1161
|
-
}
|
|
1162
|
-
}
|
|
1163
|
-
|
|
1164
756
|
export {
|
|
1165
757
|
useLocaleStore,
|
|
758
|
+
getLocale,
|
|
1166
759
|
t,
|
|
1167
760
|
tp,
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
ensureDataDirs,
|
|
1171
|
-
fetchWithTimeout,
|
|
1172
|
-
loadLicense,
|
|
1173
|
-
activate,
|
|
1174
|
-
deactivate,
|
|
1175
|
-
useLicenseStore,
|
|
1176
|
-
verifyPro,
|
|
1177
|
-
requirePro
|
|
761
|
+
logger,
|
|
762
|
+
fetchWithTimeout
|
|
1178
763
|
};
|
|
764
|
+
//# sourceMappingURL=chunk-PTLSNG2N.js.map
|