brew-tui 0.2.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:',
@@ -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/lib/data-dir.ts
650
- import { homedir } from "os";
651
- import { join } from "path";
652
- import { mkdir } from "fs/promises";
653
- var DATA_DIR = join(homedir(), ".brew-tui");
654
- var PROFILES_DIR = join(DATA_DIR, "profiles");
655
- var LICENSE_PATH = join(DATA_DIR, "license.json");
656
- var HISTORY_PATH = join(DATA_DIR, "history.json");
657
- async function ensureDataDirs() {
658
- await mkdir(DATA_DIR, { recursive: true, mode: 448 });
659
- await mkdir(PROFILES_DIR, { recursive: true, mode: 448 });
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
- PROFILES_DIR,
1169
- HISTORY_PATH,
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