brew-tui 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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:',
@@ -231,17 +235,18 @@ var en = {
231
235
  account_nameLabel: "Name:",
232
236
  account_planLabel: "Plan:",
233
237
  account_monthlyPrice: "9\u20AC/month",
234
- account_yearlyPrice: "49\u20AC/year",
238
+ account_lifetimePrice: "29\u20AC lifetime",
235
239
  account_keyLabel: "Key:",
236
240
  account_expiresLabel: "Expires:",
237
241
  account_activatedLabel: "Activated:",
238
242
  account_upgradeTitle: "Upgrade to Brew-TUI Pro",
239
243
  account_unlockDesc: "Unlock Profiles, Smart Cleanup, History, Security Audit, and BrewBar (macOS menu bar companion).",
240
- account_pricing: "9\u20AC/month or 49\u20AC/year",
244
+ account_pricing: "9\u20AC/month or 29\u20AC lifetime",
241
245
  account_runActivate: "Run:",
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",
@@ -252,10 +257,12 @@ var en = {
252
257
  upgrade_historyDesc: "Track every install, uninstall, and upgrade with timestamps. Search and filter your package management history.",
253
258
  upgrade_security: "Security Audit",
254
259
  upgrade_securityDesc: "Scan installed packages against known vulnerabilities (CVEs). See severity levels, affected versions, and available fixes.",
255
- upgrade_pricing: "9\u20AC/month or 49\u20AC/year",
256
- upgrade_activateWith: "Activate with:",
260
+ upgrade_pricing: "9\u20AC/month or 29\u20AC lifetime",
261
+ upgrade_buyAt: "Buy at:",
262
+ upgrade_buyUrl: "https://buy.polar.sh/polar_cl_QW1ZJ9887bU74drGr7JfujQfm3RKYnn1fuvc53DqD6D",
263
+ upgrade_activateWith: "Then activate with:",
257
264
  upgrade_activateCmd: "brew-tui activate <your-license-key>",
258
- upgrade_proLabel: "Brew-TUI Pro \u2014 9\u20AC/month or 49\u20AC/year \u2014 Includes BrewBar for macOS",
265
+ upgrade_proLabel: "Brew-TUI Pro \u2014 9\u20AC/month or 29\u20AC lifetime \u2014 Includes BrewBar for macOS",
259
266
  // ── Progress Log ──
260
267
  progress_noOutput: "No output yet",
261
268
  // ── Search Input ──
@@ -275,11 +282,16 @@ var en = {
275
282
  cli_deactivated: "\u2714 License deactivated.",
276
283
  cli_planFree: "Plan: Free",
277
284
  cli_planPro: "Plan: Pro",
285
+ cli_planExpired: "Plan: Expired",
278
286
  cli_confirmDeactivate: "Deactivate your Pro license on this machine? (y/N): ",
279
287
  cli_deactivateCancelled: "Deactivation cancelled.",
280
288
  cli_upgradeHint: "Run `brew-tui activate <key>` to upgrade to Pro.",
289
+ cli_revalidateHint: "Run `brew-tui revalidate` to refresh your current license.",
281
290
  cli_email: "Email: {{email}}",
282
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.",
283
295
  cli_rateLimited: "Too many activation attempts. Try again in {{minutes}} minutes.",
284
296
  cli_cooldown: "Please wait before trying again.",
285
297
  cli_brewbarInstalling: "Downloading BrewBar...",
@@ -288,8 +300,10 @@ var en = {
288
300
  cli_brewbarUninstalled: "\u2714 BrewBar removed from /Applications.",
289
301
  cli_brewbarNotInstalled: "BrewBar is not installed.",
290
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",
291
304
  cli_brewbarMacOnly: "\u2718 BrewBar is only available on macOS.",
292
305
  cli_brewbarDownloadFailed: "\u2718 Failed to download BrewBar: {{error}}",
306
+ cli_deactivateRemoteFailed: "\u26A0 Warning: Could not reach the server to deactivate remotely. The license was removed locally but may still count as active.",
293
307
  // ── License degradation (Layer 15) ──
294
308
  license_offlineWarning: "Your license has not been validated for {{days}} days. Please connect to the internet.",
295
309
  // ── Plurals ──
@@ -299,7 +313,39 @@ var en = {
299
313
  plural_warnings_other: "{{count}} warnings",
300
314
  // ── Scroll indicators ──
301
315
  scroll_moreAbove: "\u2191 {{count}} more",
302
- 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."
303
349
  };
304
350
  var en_default = en;
305
351
 
@@ -409,6 +455,8 @@ var es = {
409
455
  dashboard_updated: "Actualizado:",
410
456
  dashboard_outdatedPackages: "Paquetes Desactualizados",
411
457
  dashboard_serviceErrors: "Errores de Servicios",
458
+ dashboard_partialData: "Algunas secciones de Homebrew no pudieron cargarse:",
459
+ dashboard_statError: "ERR",
412
460
  // ── Installed ──
413
461
  installed_formulaeCount: "Formulae ({{count}})",
414
462
  installed_casksCount: "Casks ({{count}})",
@@ -425,6 +473,7 @@ var es = {
425
473
  search_formulaeHeader: "=== Formulae ({{count}})",
426
474
  search_casksHeader: "=== Casks ({{count}})",
427
475
  search_noResults: "Sin resultados",
476
+ search_minChars: "Escribe al menos 2 caracteres para buscar.",
428
477
  // ── Outdated ──
429
478
  outdated_title: "Paquetes Desactualizados ({{count}})",
430
479
  outdated_upgrading: "Actualizando...",
@@ -473,6 +522,7 @@ var es = {
473
522
  profiles_title: "Perfiles de Paquetes ({{count}})",
474
523
  profiles_importTitle: "Importando perfil...",
475
524
  profiles_importComplete: "Importaci\xF3n completa. Presiona cualquier tecla.",
525
+ profiles_importPartial: "Importaci\xF3n finalizada con errores. Revisa el registro arriba.",
476
526
  profiles_createName: "Crear Perfil \u2014 Nombre:",
477
527
  profiles_namePlaceholder: "ej. trabajo, personal, proyecto-x",
478
528
  profiles_createDesc: 'Crear Perfil "{{name}}" \u2014 Descripci\xF3n:',
@@ -533,17 +583,18 @@ var es = {
533
583
  account_nameLabel: "Nombre:",
534
584
  account_planLabel: "Plan:",
535
585
  account_monthlyPrice: "9\u20AC/mes",
536
- account_yearlyPrice: "49\u20AC/a\xF1o",
586
+ account_lifetimePrice: "29\u20AC de por vida",
537
587
  account_keyLabel: "Clave:",
538
588
  account_expiresLabel: "Expira:",
539
589
  account_activatedLabel: "Activado:",
540
590
  account_upgradeTitle: "Actualiza a Brew-TUI Pro",
541
591
  account_unlockDesc: "Desbloquea Perfiles, Limpieza Inteligente, Historial, Auditor\xEDa de Seguridad y BrewBar (barra de men\xFA macOS).",
542
- account_pricing: "9\u20AC/mes o 49\u20AC/a\xF1o",
592
+ account_pricing: "9\u20AC/mes o 29\u20AC de por vida",
543
593
  account_runActivate: "Ejecuta:",
544
594
  account_activateCmd: "brew-tui activate <clave>",
545
595
  account_licenseExpired: "Tu licencia ha expirado. Renueva para seguir usando las funciones Pro.",
546
596
  account_deactivating: "Desactivando...",
597
+ account_loading: "Cargando estado de la licencia...",
547
598
  // ── Upgrade Prompt ──
548
599
  upgrade_proFeature: "{{title}} \u2014 Funci\xF3n Pro",
549
600
  upgrade_profiles: "Perfiles de Paquetes",
@@ -554,10 +605,12 @@ var es = {
554
605
  upgrade_historyDesc: "Rastrea cada instalaci\xF3n, desinstalaci\xF3n y actualizaci\xF3n con marcas de tiempo. Busca y filtra tu historial de gesti\xF3n de paquetes.",
555
606
  upgrade_security: "Auditor\xEDa de Seguridad",
556
607
  upgrade_securityDesc: "Escanea paquetes instalados contra vulnerabilidades conocidas (CVEs). Ve niveles de severidad, versiones afectadas y correcciones disponibles.",
557
- upgrade_pricing: "9\u20AC/mes o 49\u20AC/a\xF1o",
558
- upgrade_activateWith: "Activa con:",
608
+ upgrade_pricing: "9\u20AC/mes o 29\u20AC de por vida",
609
+ upgrade_buyAt: "Compra en:",
610
+ upgrade_buyUrl: "https://buy.polar.sh/polar_cl_QW1ZJ9887bU74drGr7JfujQfm3RKYnn1fuvc53DqD6D",
611
+ upgrade_activateWith: "Luego activa con:",
559
612
  upgrade_activateCmd: "brew-tui activate <tu-clave-de-licencia>",
560
- upgrade_proLabel: "Brew-TUI Pro \u2014 9\u20AC/mes o 49\u20AC/a\xF1o \u2014 Incluye BrewBar para macOS",
613
+ upgrade_proLabel: "Brew-TUI Pro \u2014 9\u20AC/mes o 29\u20AC de por vida \u2014 Incluye BrewBar para macOS",
561
614
  // ── Progress Log ──
562
615
  progress_noOutput: "Sin salida a\xFAn",
563
616
  // ── Search Input ──
@@ -577,11 +630,16 @@ var es = {
577
630
  cli_deactivated: "\u2714 Licencia desactivada.",
578
631
  cli_planFree: "Plan: Gratis",
579
632
  cli_planPro: "Plan: Pro",
633
+ cli_planExpired: "Plan: Expirada",
580
634
  cli_confirmDeactivate: "\xBFDesactivar tu licencia Pro en esta m\xE1quina? (s/N): ",
581
635
  cli_deactivateCancelled: "Desactivaci\xF3n cancelada.",
582
636
  cli_upgradeHint: "Ejecuta `brew-tui activate <clave>` para actualizar a Pro.",
637
+ cli_revalidateHint: "Ejecuta `brew-tui revalidate` para refrescar tu licencia actual.",
583
638
  cli_email: "Email: {{email}}",
584
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.",
585
643
  cli_rateLimited: "Demasiados intentos de activaci\xF3n. Int\xE9ntalo en {{minutes}} minutos.",
586
644
  cli_cooldown: "Por favor espera antes de intentar de nuevo.",
587
645
  cli_brewbarInstalling: "Descargando BrewBar...",
@@ -590,8 +648,10 @@ var es = {
590
648
  cli_brewbarUninstalled: "\u2714 BrewBar eliminado de /Applications.",
591
649
  cli_brewbarNotInstalled: "BrewBar no est\xE1 instalado.",
592
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",
593
652
  cli_brewbarMacOnly: "\u2718 BrewBar solo est\xE1 disponible en macOS.",
594
653
  cli_brewbarDownloadFailed: "\u2718 Error al descargar BrewBar: {{error}}",
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.",
595
655
  // ── License degradation (Layer 15) ──
596
656
  license_offlineWarning: "Tu licencia no se ha validado en {{days}} d\xEDas. Por favor con\xE9ctate a internet.",
597
657
  // ── Plurals ──
@@ -601,7 +661,39 @@ var es = {
601
661
  plural_warnings_other: "{{count}} advertencias",
602
662
  // ── Scroll indicators ──
603
663
  scroll_moreAbove: "\u2191 {{count}} m\xE1s",
604
- 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."
605
697
  };
606
698
  var es_default = es;
607
699
 
@@ -625,6 +717,9 @@ var useLocaleStore = create((set) => ({
625
717
  locale: detectLocale(),
626
718
  setLocale: (locale) => set({ locale })
627
719
  }));
720
+ function getLocale() {
721
+ return useLocaleStore.getState().locale;
722
+ }
628
723
  function t(key, values) {
629
724
  const locale = useLocaleStore.getState().locale;
630
725
  let text = locales[locale][key] ?? locales["en"][key] ?? key;
@@ -640,501 +735,30 @@ function tp(baseKey, count, values) {
640
735
  return t(`${baseKey}${suffix}`, { count, ...values });
641
736
  }
642
737
 
643
- // src/lib/data-dir.ts
644
- import { homedir } from "os";
645
- import { join } from "path";
646
- import { mkdir } from "fs/promises";
647
- var DATA_DIR = join(homedir(), ".brew-tui");
648
- var PROFILES_DIR = join(DATA_DIR, "profiles");
649
- var LICENSE_PATH = join(DATA_DIR, "license.json");
650
- var HISTORY_PATH = join(DATA_DIR, "history.json");
651
- async function ensureDataDirs() {
652
- await mkdir(DATA_DIR, { recursive: true });
653
- await mkdir(PROFILES_DIR, { recursive: true });
654
- }
655
-
656
- // src/lib/license/license-manager.ts
657
- import { readFile, writeFile, rm } from "fs/promises";
658
- import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from "crypto";
659
-
660
- // src/lib/license/polar-api.ts
661
- import { hostname } from "os";
662
- var BASE_URL = "https://api.polar.sh/v1/customer-portal/license-keys";
663
- var POLAR_ORGANIZATION_ID = "b8f245c0-d116-4457-92fb-1bda47139f82";
664
- function validateApiUrl(url) {
665
- const parsed = new URL(url);
666
- if (parsed.protocol !== "https:") {
667
- throw new Error("HTTPS required for license API");
668
- }
669
- if (!parsed.hostname.endsWith("polar.sh")) {
670
- throw new Error("Invalid API host");
671
- }
672
- }
673
- async function post(endpoint, body, expectEmpty = false) {
674
- const url = `${BASE_URL}/${endpoint}`;
675
- validateApiUrl(url);
676
- const res = await fetch(url, {
677
- method: "POST",
678
- headers: { "Content-Type": "application/json" },
679
- body: JSON.stringify(body)
680
- });
681
- if (!res.ok) {
682
- let message = `Request failed with status ${res.status}`;
683
- try {
684
- const errBody = await res.json();
685
- if (typeof errBody.detail === "string") message = errBody.detail;
686
- else if (typeof errBody.error === "string") message = errBody.error;
687
- else if (typeof errBody.message === "string") message = errBody.message;
688
- } catch {
689
- }
690
- throw new Error(message);
691
- }
692
- if (expectEmpty || res.status === 204) return void 0;
693
- return res.json();
694
- }
695
- async function activateLicense(key) {
696
- const activation = await post("activate", {
697
- key,
698
- organization_id: POLAR_ORGANIZATION_ID,
699
- label: hostname()
700
- });
701
- let customerEmail = "";
702
- let customerName = "";
703
- try {
704
- const validated = await post("validate", {
705
- key,
706
- organization_id: POLAR_ORGANIZATION_ID,
707
- activation_id: activation.id
708
- });
709
- customerEmail = validated.customer?.email ?? "";
710
- customerName = validated.customer?.name ?? "";
711
- } catch {
712
- }
713
- return {
714
- activated: true,
715
- error: null,
716
- instance: { id: activation.id },
717
- license_key: {
718
- id: 0,
719
- status: activation.license_key.status,
720
- key,
721
- activation_limit: 0,
722
- activations_count: 0,
723
- expires_at: activation.license_key.expires_at
724
- },
725
- meta: { customer_email: customerEmail, customer_name: customerName }
726
- };
727
- }
728
- async function validateLicense(key, instanceId) {
729
- const res = await post("validate", {
730
- key,
731
- organization_id: POLAR_ORGANIZATION_ID,
732
- activation_id: instanceId
733
- });
734
- const notExpired = res.expires_at === null || new Date(res.expires_at) > /* @__PURE__ */ new Date();
735
- const valid = res.status === "granted" && notExpired;
736
- return {
737
- valid,
738
- error: valid ? null : `License ${res.status}`,
739
- license_key: {
740
- id: 0,
741
- status: res.status,
742
- key,
743
- expires_at: res.expires_at
744
- },
745
- instance: { id: instanceId }
746
- };
747
- }
748
- async function deactivateLicense(key, instanceId) {
749
- await post(
750
- "deactivate",
751
- { key, organization_id: POLAR_ORGANIZATION_ID, activation_id: instanceId },
752
- true
753
- );
754
- }
755
-
756
- // src/lib/license/license-manager.ts
757
- var REVALIDATION_INTERVAL_MS = 24 * 60 * 60 * 1e3;
758
- var GRACE_PERIOD_MS = 7 * 24 * 60 * 60 * 1e3;
759
- var ACTIVATION_COOLDOWN_MS = 3e4;
760
- var MAX_ATTEMPTS = 5;
761
- var LOCKOUT_MS = 15 * 60 * 1e3;
762
- var tracker = {
763
- attempts: 0,
764
- lastAttempt: 0,
765
- lockedUntil: 0
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 || "")
766
749
  };
767
- function checkRateLimit() {
768
- const now = Date.now();
769
- if (now < tracker.lockedUntil) {
770
- const remaining = Math.ceil((tracker.lockedUntil - now) / 6e4);
771
- throw new Error(t("cli_rateLimited", { minutes: remaining }));
772
- }
773
- if (now - tracker.lastAttempt < ACTIVATION_COOLDOWN_MS) {
774
- throw new Error(t("cli_cooldown"));
775
- }
776
- }
777
- function recordAttempt(success) {
778
- const now = Date.now();
779
- tracker.lastAttempt = now;
780
- if (success) {
781
- tracker.attempts = 0;
782
- return;
783
- }
784
- tracker.attempts++;
785
- if (tracker.attempts >= MAX_ATTEMPTS) {
786
- tracker.lockedUntil = now + LOCKOUT_MS;
787
- tracker.attempts = 0;
788
- }
789
- }
790
- var ENCRYPTION_SECRET = "brew-tui-license-aes256gcm-v1";
791
- var SCRYPT_SALT = "brew-tui-salt-v1";
792
- var _derivedKey = scryptSync(ENCRYPTION_SECRET, SCRYPT_SALT, 32);
793
- function deriveEncryptionKey() {
794
- return _derivedKey;
795
- }
796
- function encryptLicenseData(data) {
797
- const key = deriveEncryptionKey();
798
- const iv = randomBytes(12);
799
- const cipher = createCipheriv("aes-256-gcm", key, iv);
800
- const plaintext = JSON.stringify(data);
801
- const ciphertext = Buffer.concat([cipher.update(plaintext, "utf-8"), cipher.final()]);
802
- const tag = cipher.getAuthTag();
803
- return {
804
- encrypted: ciphertext.toString("base64"),
805
- iv: iv.toString("base64"),
806
- tag: tag.toString("base64")
807
- };
808
- }
809
- function decryptLicenseData(encrypted, iv, tag) {
810
- const key = deriveEncryptionKey();
811
- const decipher = createDecipheriv(
812
- "aes-256-gcm",
813
- key,
814
- Buffer.from(iv, "base64")
815
- );
816
- decipher.setAuthTag(Buffer.from(tag, "base64"));
817
- const plaintext = Buffer.concat([
818
- decipher.update(Buffer.from(encrypted, "base64")),
819
- decipher.final()
820
- ]);
821
- return JSON.parse(plaintext.toString("utf-8"));
822
- }
823
- async function loadLicense() {
824
- try {
825
- const raw = await readFile(LICENSE_PATH, "utf-8");
826
- const file = JSON.parse(raw);
827
- if (file.encrypted && file.iv && file.tag) {
828
- const data = decryptLicenseData(file.encrypted, file.iv, file.tag);
829
- return data;
830
- }
831
- if (file.license) {
832
- const data = file.license;
833
- await saveLicense(data);
834
- return data;
835
- }
836
- return null;
837
- } catch {
838
- return null;
839
- }
840
- }
841
- async function saveLicense(data) {
842
- await ensureDataDirs();
843
- const { encrypted, iv, tag } = encryptLicenseData(data);
844
- const file = { version: 1, encrypted, iv, tag };
845
- await writeFile(LICENSE_PATH, JSON.stringify(file, null, 2), { encoding: "utf-8", mode: 384 });
846
- }
847
- async function clearLicense() {
848
- try {
849
- await rm(LICENSE_PATH);
850
- } catch {
851
- }
852
- }
853
- function isExpired(license) {
854
- if (!license.expiresAt) return false;
855
- return new Date(license.expiresAt).getTime() < Date.now();
856
- }
857
- function needsRevalidation(license) {
858
- const lastValidated = new Date(license.lastValidatedAt).getTime();
859
- if (isNaN(lastValidated)) return true;
860
- return Date.now() - lastValidated > REVALIDATION_INTERVAL_MS;
861
- }
862
- function isWithinGracePeriod(license) {
863
- const lastValidated = new Date(license.lastValidatedAt).getTime();
864
- if (isNaN(lastValidated)) return false;
865
- return Date.now() - lastValidated < GRACE_PERIOD_MS;
866
- }
867
- function getDegradationLevel(license) {
868
- const lastValidated = new Date(license.lastValidatedAt).getTime();
869
- if (isNaN(lastValidated)) return "expired";
870
- const elapsed = Date.now() - lastValidated;
871
- if (elapsed < 0) return "none";
872
- const days = elapsed / (24 * 60 * 60 * 1e3);
873
- if (days <= 7) return "none";
874
- if (days <= 14) return "warning";
875
- if (days <= 30) return "limited";
876
- return "expired";
877
- }
878
- function validateLicenseKey(key) {
879
- if (key.length < 10 || key.length > 100) {
880
- throw new Error("Invalid license key format");
881
- }
882
- if (!/^[\w-]+$/.test(key)) {
883
- throw new Error("Invalid license key format");
884
- }
885
- }
886
- async function activate(key) {
887
- validateLicenseKey(key);
888
- checkRateLimit();
889
- let success = false;
890
- try {
891
- const res = await activateLicense(key);
892
- if (!res.activated) {
893
- throw new Error(res.error ?? "Activation failed");
894
- }
895
- const license = {
896
- key,
897
- instanceId: res.instance.id,
898
- status: "active",
899
- customerEmail: res.meta.customer_email,
900
- customerName: res.meta.customer_name,
901
- plan: "pro",
902
- activatedAt: (/* @__PURE__ */ new Date()).toISOString(),
903
- expiresAt: res.license_key.expires_at,
904
- lastValidatedAt: (/* @__PURE__ */ new Date()).toISOString()
905
- };
906
- await saveLicense(license);
907
- success = true;
908
- return license;
909
- } finally {
910
- recordAttempt(success);
911
- }
912
- }
913
- async function revalidate(license) {
914
- try {
915
- const res = await validateLicense(license.key, license.instanceId);
916
- if (res.valid) {
917
- const updated = {
918
- ...license,
919
- lastValidatedAt: (/* @__PURE__ */ new Date()).toISOString(),
920
- status: "active",
921
- expiresAt: res.license_key.expires_at
922
- };
923
- await saveLicense(updated);
924
- return true;
925
- }
926
- await saveLicense({ ...license, status: "expired" });
927
- return false;
928
- } catch {
929
- return isWithinGracePeriod(license);
930
- }
931
- }
932
- async function deactivate(license) {
933
- try {
934
- await deactivateLicense(license.key, license.instanceId);
935
- } catch {
936
- }
937
- await clearLicense();
938
- }
939
-
940
- // src/stores/license-store.ts
941
- import { create as create2 } from "zustand";
942
- var REVALIDATION_CHECK_MS = 60 * 60 * 1e3;
943
- var _revalidating = false;
944
- var useLicenseStore = create2((set, get) => ({
945
- status: "free",
946
- license: null,
947
- error: null,
948
- degradation: "none",
949
- initialize: async () => {
950
- await ensureDataDirs();
951
- const license = await loadLicense();
952
- if (!license) {
953
- set({ status: "free", license: null, degradation: "none" });
954
- return;
955
- }
956
- if (isExpired(license)) {
957
- set({ status: "expired", license, degradation: "expired" });
958
- return;
959
- }
960
- const level = getDegradationLevel(license);
961
- if (level === "expired") {
962
- set({ status: "expired", license, degradation: "expired" });
963
- return;
964
- }
965
- set({ status: "pro", license, degradation: level });
966
- if (needsRevalidation(license) && !_revalidating) {
967
- _revalidating = true;
968
- try {
969
- const valid = await revalidate(license);
970
- if (!valid) {
971
- set({ status: "expired", license: { ...license, status: "expired" } });
972
- } else {
973
- const updated = await loadLicense();
974
- if (updated) set({ license: updated });
975
- }
976
- } finally {
977
- _revalidating = false;
978
- }
979
- }
980
- setInterval(async () => {
981
- const current = get().license;
982
- if (!current || get().status !== "pro") return;
983
- if (!needsRevalidation(current)) return;
984
- if (_revalidating) return;
985
- _revalidating = true;
986
- try {
987
- const valid = await revalidate(current);
988
- if (!valid) {
989
- set({ status: "expired", license: { ...current, status: "expired" } });
990
- } else {
991
- const updated = await loadLicense();
992
- if (updated) set({ license: updated });
993
- }
994
- } finally {
995
- _revalidating = false;
996
- }
997
- }, REVALIDATION_CHECK_MS).unref();
998
- },
999
- activate: async (key) => {
1000
- set({ error: null });
1001
- try {
1002
- const license = await activate(key);
1003
- set({ status: "pro", license });
1004
- return true;
1005
- } catch (err) {
1006
- const msg = err instanceof Error ? err.message : String(err);
1007
- set({ error: msg });
1008
- return false;
1009
- }
1010
- },
1011
- deactivate: async () => {
1012
- const { license } = get();
1013
- if (license) {
1014
- await deactivate(license);
1015
- }
1016
- set({ status: "free", license: null, error: null });
1017
- },
1018
- isPro: () => get().status === "pro"
1019
- }));
1020
-
1021
- // src/lib/license/anti-debug.ts
1022
- import inspector from "inspector";
1023
- function isDebuggerAttached() {
1024
- if (process.env.VITEST || process.env.CI) return false;
1025
- if (inspector.url()) return true;
1026
- if (process.execArgv.some((a) => a.includes("--inspect") || a.includes("--debug"))) return true;
1027
- if (process.env.NODE_OPTIONS?.includes("--inspect")) return true;
1028
- return false;
1029
- }
1030
-
1031
- // src/lib/license/anti-tamper.ts
1032
- var _originalIsPro = useLicenseStore.getState().isPro;
1033
- var _originalGetState = useLicenseStore.getState;
1034
- function verifyStoreIntegrity() {
1035
- const state = useLicenseStore.getState();
1036
- if (state.isPro !== _originalIsPro) return false;
1037
- if (useLicenseStore.getState !== _originalGetState) return false;
1038
- if (state.status === "free" && state.isPro()) return false;
1039
- return true;
1040
- }
1041
-
1042
- // src/lib/license/canary.ts
1043
- var _canaryTripped = false;
1044
- function isProUnlocked() {
1045
- return false;
1046
- }
1047
- function hasProAccess() {
1048
- return false;
1049
- }
1050
- function isLicenseValid() {
1051
- return false;
1052
- }
1053
- function checkCanaries() {
1054
- if (isProUnlocked()) {
1055
- _canaryTripped = true;
1056
- return false;
1057
- }
1058
- if (hasProAccess()) {
1059
- _canaryTripped = true;
1060
- return false;
1061
- }
1062
- if (isLicenseValid()) {
1063
- _canaryTripped = true;
1064
- return false;
1065
- }
1066
- return !_canaryTripped;
1067
- }
1068
750
 
1069
- // src/lib/license/integrity.ts
1070
- import { readFileSync } from "fs";
1071
- import { createHash } from "crypto";
1072
- import { fileURLToPath } from "url";
1073
- var _baselineHash = null;
1074
- function _captureBaseline() {
1075
- try {
1076
- const bundlePath = fileURLToPath(import.meta.url);
1077
- const content = readFileSync(bundlePath, "utf-8");
1078
- return createHash("sha256").update(content).digest("hex");
1079
- } catch {
1080
- return null;
1081
- }
1082
- }
1083
- _baselineHash = _captureBaseline();
1084
- function checkBundleIntegrity() {
1085
- if (_baselineHash === null) {
1086
- return true;
1087
- }
1088
- try {
1089
- const bundlePath = fileURLToPath(import.meta.url);
1090
- const content = readFileSync(bundlePath, "utf-8");
1091
- const current = createHash("sha256").update(content).digest("hex");
1092
- return current === _baselineHash;
1093
- } catch {
1094
- return true;
1095
- }
1096
- }
1097
-
1098
- // src/lib/license/pro-guard.ts
1099
- var _S = String.fromCharCode(115, 116, 97, 116, 117, 115);
1100
- var _P = String.fromCharCode(112, 114, 111);
1101
- function _verify() {
1102
- const state = useLicenseStore.getState();
1103
- return state[_S] === _P;
1104
- }
1105
- function verifyPro() {
1106
- if (isDebuggerAttached()) return false;
1107
- if (!checkBundleIntegrity()) return false;
1108
- if (!verifyStoreIntegrity()) return false;
1109
- if (!checkCanaries()) return false;
1110
- const directCheck = useLicenseStore.getState().status === "pro";
1111
- const indirectCheck = _verify();
1112
- if (!directCheck || !indirectCheck) return false;
1113
- const license = useLicenseStore.getState().license;
1114
- if (license) {
1115
- const level = getDegradationLevel(license);
1116
- if (level === "expired" || level === "limited") return false;
1117
- }
1118
- return true;
1119
- }
1120
- function requirePro() {
1121
- if (!verifyPro()) {
1122
- throw new Error("Pro license required");
1123
- }
751
+ // src/lib/fetch-timeout.ts
752
+ function fetchWithTimeout(url, options = {}, timeoutMs = 15e3) {
753
+ return fetch(url, { ...options, signal: AbortSignal.timeout(timeoutMs) });
1124
754
  }
1125
755
 
1126
756
  export {
1127
757
  useLocaleStore,
758
+ getLocale,
1128
759
  t,
1129
760
  tp,
1130
- PROFILES_DIR,
1131
- HISTORY_PATH,
1132
- ensureDataDirs,
1133
- loadLicense,
1134
- activate,
1135
- deactivate,
1136
- useLicenseStore,
1137
- verifyPro,
1138
- requirePro
761
+ logger,
762
+ fetchWithTimeout
1139
763
  };
1140
- //# sourceMappingURL=chunk-P6PTN4HR.js.map
764
+ //# sourceMappingURL=chunk-PTLSNG2N.js.map