llm-tuner 0.1.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ronald Daniel Acuña Arias
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.es.md ADDED
@@ -0,0 +1,123 @@
1
+ # LLM Tuner
2
+
3
+ [English](README.md) · **Español**
4
+
5
+ Carga un LLM local con la mejor configuración para el contexto que necesitas, en LM Studio u Ollama, en Windows, macOS y Linux. Tú eliges qué significa «mejor» con un perfil de carga: velocidad, un equilibrio o calidad de las respuestas.
6
+
7
+ Ningún LLM decide la configuración. La app lee tu hardware y los metadatos del modelo, estima qué cabe en la VRAM, mide de verdad las mejores opciones y guarda la ganadora como **preset**. La próxima vez que cargues ese modelo con ese contexto y ese perfil se usa el preset directamente, sin volver a medir.
8
+
9
+ ## Flujo
10
+
11
+ 1. **Motor**: eliges LM Studio u Ollama. Si no está instalado, la app lo instala con el método oficial (winget, Homebrew o el script de instalación).
12
+ 2. **Modelo y contexto**: eliges uno de tus modelos y escribes el contexto que quieres.
13
+ 3. **Perfil**: eliges qué optimizar: `speed`, `balanced` o `quality`. La app marca uno como recomendado.
14
+ 4. **Cargar**:
15
+ - Si hay un preset para ese modelo, contexto, perfil y hardware, se aplica y se carga el modelo (unos 20 s).
16
+ - Si no lo hay, se prueban 3 configuraciones (2–3 min), se guarda la mejor como preset, se aplica y se carga.
17
+
18
+ Solo se vuelve a medir cuando cambias el contexto o el perfil, cambia el hardware (GPU, VRAM, CPU o enlace PCIe) o cambia el archivo del modelo. Con `speed` y `quality` también se vuelve a medir cuando descargas o eliminas una variante del modelo. También puedes forzarlo con «Volver a medir aunque exista un preset» o con `--force`.
19
+
20
+ ## Perfiles de carga
21
+
22
+ Más tokens por segundo no significa mejores respuestas. El perfil decide cuánta calidad de las respuestas se puede ceder a cambio de velocidad o memoria.
23
+
24
+ | Perfil | Qué optimiza | Qué cuesta |
25
+ |---|---|---|
26
+ | `speed` (Velocidad) | Los tokens por segundo. Usa la variante más ligera del modelo que tengas descargada y cualquier tipo de caché KV. | Calidad de las respuestas: se admiten los pesos y la caché más comprimidos. |
27
+ | `balanced` (Equilibrado) | La velocidad ponderada por la calidad de la caché KV, con la variante que has seleccionado. Es el perfil predeterminado. | Un término medio: ni el más rápido ni el más preciso. |
28
+ | `quality` (Calidad) | La menor pérdida que aún se ejecuta por completo en GPU: la variante descargada más pesada que cabe, con una caché KV precisa (`f16` o `q8_0`). | Velocidad. Si nada cabe por completo en GPU, lo indica y elige la ganadora como `balanced`. |
29
+
30
+ Las optimizaciones que no pierden nada (reparto de capas entre GPUs, orden de las GPUs, hilos) se aplican en todos los perfiles.
31
+
32
+ Antes de cargar, la app también te indica:
33
+
34
+ - **Qué perfil se recomienda**: el de mayor calidad que aún se ejecuta por completo en tus GPUs.
35
+ - **Si cabría una variante más pesada**: por ejemplo, que Q8_0 no está descargada y cabría por completo en tus GPUs. El tamaño es una estimación, y la app nunca descarga nada.
36
+ - **Si la vista previa es conservadora**: se calcula con la VRAM que está libre en ese momento, así que la app te avisa cuando la VRAM está en uso. Al cargar, primero se descargan de la memoria los modelos del motor y después se hace la elección real.
37
+
38
+ Los motores se diferencian en una cosa. **Ollama** permite que la app elija la variante (etiqueta) para el perfil y la cargue, entre las etiquetas que puede identificar como el mismo modelo. **LM Studio** no puede cargar una variante concreta a petición, así que ahí el perfil se aplica a la variante seleccionada en LM Studio, y la app te indica cuál seleccionar cuando otra se ajusta mejor al perfil.
39
+
40
+ ## Instalación
41
+
42
+ ```bash
43
+ npm install -g llm-tuner
44
+ llm-tuner # asistente en la terminal
45
+ llm-tuner-desktop # app de escritorio
46
+ ```
47
+
48
+ Necesita Node.js 22 o posterior. La app de escritorio descarga su entorno de ejecución (Electron) la primera vez que ejecutas `llm-tuner-desktop`, así que esa primera ejecución necesita conexión a la red y tarda más. El asistente de terminal funciona sin él.
49
+
50
+ ## Uso
51
+
52
+ Modo no interactivo:
53
+
54
+ ```bash
55
+ llm-tuner --engine lmstudio --model qwen/qwen2.5-coder-32b --ctx 16384 --yes
56
+ llm-tuner --engine lmstudio --model qwen/qwen2.5-coder-32b --ctx 16384 --profile quality --yes
57
+ llm-tuner --presets # lista los presets guardados
58
+ llm-tuner --help
59
+ llm-tuner --lang <en|es> # idioma de la interfaz; se guarda para la próxima vez
60
+ ```
61
+
62
+ `--profile <speed|balanced|quality>` establece el perfil de carga para esa ejecución. El asistente lo pregunta y recuerda tu elección. Sin la opción, una ejecución con `--yes`, con `--json` o con motor, modelo y contexto indicados usa `balanced`.
63
+
64
+ La app de escritorio tiene un selector EN/ES en la barra lateral, y la elección se guarda en `llm-tuner/settings.json`, junto a los presets. El perfil que eliges en el asistente o en la app también se guarda ahí.
65
+
66
+ ## Qué se prueba
67
+
68
+ Para cada tipo de caché KV que admite el perfil (`f16`, `q8_0`, `q4_0`; `quality` excluye `q4_0`) el estimador calcula:
69
+
70
+ - **Memoria por capa**, leída del GGUF (tamaño real de cada tensor).
71
+ - **Caché de contexto** según capas con atención, cabezas KV, dimensión y ventana deslizante (SWA).
72
+ - **Reparto de capas entre GPUs**, con prioridad a la GPU con el enlace PCIe más ancho y sin monitor, y el margen libre igualado entre tarjetas.
73
+ - **En modelos MoE que no caben**, cuántas capas de expertos pueden quedarse en la RAM (`--n-cpu-moe`).
74
+ - **Velocidad esperada**, según el ancho de banda de memoria.
75
+
76
+ Las mejores opciones se miden con un prompt corto y otro que llena ~50 % del contexto. El perfil decide la ganadora: en `balanced`, la más rápida con contexto lleno, ponderada por la calidad del tipo de KV.
77
+
78
+ - **LM Studio**: se ejecuta el mismo `llama-server` que usa LM Studio con los parámetros exactos. El resultado se escribe en la config del modelo (`~/.lmstudio/.internal/user-concrete-model-default-config/…`) y en `hardware-config.json`. LM Studio solo se reinicia si cambia la config de hardware.
79
+ - **Ollama**: cada opción se mide en un `ollama serve` privado en otro puerto, porque el tipo de KV y Flash Attention son globales. Se crea el modelo `<modelo>-tuned-16k` con `num_ctx`, `num_gpu` y `num_thread`, y se configuran las variables del servidor (systemd en Linux, `setx` en Windows, `launchctl` en macOS).
80
+
81
+ Antes de modificar cualquier archivo se guarda una copia `*.bak-llm-tuner-*`.
82
+
83
+ ## Dónde se guardan los presets
84
+
85
+ Hay un preset por motor, modelo, contexto y perfil.
86
+
87
+ - Linux: `~/.config/llm-tuner/presets/`
88
+ - macOS: `~/Library/Application Support/llm-tuner/presets/`
89
+ - Windows: `%APPDATA%\llm-tuner\presets\`
90
+
91
+ ## Estado por plataforma
92
+
93
+ | | Probado | Notas |
94
+ |---|---|---|
95
+ | Linux + NVIDIA + LM Studio | ✅ 2× RTX 5070, Ryzen 7 5800X | Calibrado con mediciones reales |
96
+ | Linux + Ollama | Pruebas unitarias | Falta probarlo con Ollama instalado |
97
+ | Windows / macOS | Sin probar | Rutas, instaladores y detección implementados |
98
+ | AMD (ROCm) / Apple Silicon / Intel | Sin probar | Detección de VRAM incluida; ancho de banda por tabla |
99
+
100
+ Los perfiles de carga están cubiertos por pruebas unitarias y se comprobaron con una carga real por perfil en LM Studio (Linux, 2× RTX 5070). La elección entre varias variantes descargadas de un modelo, y los perfiles en Ollama, aún no se han probado en hardware real.
101
+
102
+ ## Desarrollo
103
+
104
+ ```bash
105
+ git clone https://github.com/ronald32acunadev/llmtuner.git
106
+ cd llmtuner
107
+ npm install
108
+ npm start # asistente en la terminal
109
+ npm run desktop # app de escritorio
110
+ ```
111
+
112
+ ## Pruebas
113
+
114
+ ```bash
115
+ npm test
116
+ ```
117
+
118
+ Incluye como regresión los resultados reales de 2× RTX 5070 con Qwen2.5-Coder-32B: 16K y 20K con q8_0 caben enteros, 24K no; 32K cabe solo con q4_0.
119
+
120
+ ## Notas
121
+
122
+ - Electron no se descarga al instalar el paquete: lo descarga la primera ejecución de la app de escritorio. Si esa descarga falla, extrae `~/.cache/electron/*/electron-*.zip` en la carpeta `dist` del paquete `electron` instalado y crea `path.txt` junto a ella con el texto `electron`. La app de escritorio desactiva el sandbox de Chromium solo cuando no puede funcionar (no hay `chrome-sandbox` con setuid de root).
123
+ - Los archivos internos de LM Studio no son una API pública. Esta versión está verificada con LM Studio 0.4.25.
package/README.md ADDED
@@ -0,0 +1,123 @@
1
+ # LLM Tuner
2
+
3
+ **English** · [Español](README.es.md)
4
+
5
+ Load a local LLM with the best configuration for the context you need, on LM Studio or Ollama, on Windows, macOS and Linux. You choose what "best" means with a load profile: speed, a balance, or answer quality.
6
+
7
+ No LLM decides the configuration. The app reads your hardware and the model's metadata, estimates what fits in VRAM, actually measures the best options and saves the winner as a **preset**. The next time you load that model with that context and profile, the preset is used directly, without measuring again.
8
+
9
+ ## Flow
10
+
11
+ 1. **Engine**: you choose LM Studio or Ollama. If it isn't installed, the app installs it with the official method (winget, Homebrew or the install script).
12
+ 2. **Model and context**: you choose one of your models and enter the context you want.
13
+ 3. **Profile**: you choose what to optimize for: `speed`, `balanced` or `quality`. The app marks one as recommended.
14
+ 4. **Load**:
15
+ - If there's a preset for that model, context, profile and hardware, it's applied and the model is loaded (about 20 s).
16
+ - If there isn't one, 3 configurations are tried (2–3 min), the best one is saved as a preset, applied and loaded.
17
+
18
+ It only measures again when you change the context or the profile, the hardware changes (GPU, VRAM, CPU or PCIe link) or the model file changes. With `speed` and `quality` it also measures again when you download or remove a variant of the model. You can also force it with "Measure again even if a preset exists" or with `--force`.
19
+
20
+ ## Load profiles
21
+
22
+ More tokens per second does not mean better answers. The profile decides how much answer quality may be traded for speed or memory.
23
+
24
+ | Profile | What it optimizes | What it costs |
25
+ |---|---|---|
26
+ | `speed` | Tokens per second. It uses the lightest variant of the model you have downloaded and any KV cache type. | Answer quality: the most compressed weights and cache are allowed. |
27
+ | `balanced` | Speed weighted by the quality of the KV cache, with the variant you selected. It is the default. | A middle ground: neither the fastest nor the most precise. |
28
+ | `quality` | The least loss that still runs fully on GPU: the heaviest downloaded variant that fits, with a precise KV cache (`f16` or `q8_0`). | Speed. If nothing fits fully on GPU, it says so and picks the winner like `balanced`. |
29
+
30
+ Optimizations that lose nothing (layer split across GPUs, GPU order, threads) are applied in every profile.
31
+
32
+ Before you load, the app also tells you:
33
+
34
+ - **Which profile is recommended**: the highest-quality one that still runs fully on your GPUs.
35
+ - **Whether a heavier variant would fit**: for example, that Q8_0 is not downloaded and would fit fully on your GPUs. The size is an estimate, and the app never downloads anything.
36
+ - **Whether the preview is conservative**: it is computed with the VRAM that is free at that moment, so the app warns you when VRAM is in use. Loading unloads the engine's models first and then makes the real choice.
37
+
38
+ The engines differ in one thing. **Ollama** lets the app pick the variant (tag) for the profile and load it, among the tags it can tell are the same model. **LM Studio** cannot load a specific variant on request, so there the profile applies to the variant selected in LM Studio, and the app tells you which one to select when another suits the profile better.
39
+
40
+ ## Install
41
+
42
+ ```bash
43
+ npm install -g llm-tuner
44
+ llm-tuner # terminal wizard
45
+ llm-tuner-desktop # desktop app
46
+ ```
47
+
48
+ It needs Node.js 22 or later. The desktop app downloads its runtime (Electron) the first time you run `llm-tuner-desktop`, so that first run needs a network connection and takes longer. The terminal wizard works without it.
49
+
50
+ ## Usage
51
+
52
+ Non-interactive mode:
53
+
54
+ ```bash
55
+ llm-tuner --engine lmstudio --model qwen/qwen2.5-coder-32b --ctx 16384 --yes
56
+ llm-tuner --engine lmstudio --model qwen/qwen2.5-coder-32b --ctx 16384 --profile quality --yes
57
+ llm-tuner --presets # lists the saved presets
58
+ llm-tuner --help
59
+ llm-tuner --lang <en|es> # UI language; saved for next time
60
+ ```
61
+
62
+ `--profile <speed|balanced|quality>` sets the load profile for that run. The wizard asks for it and remembers your choice. Without the flag, a run with `--yes`, with `--json`, or with engine, model and context all given uses `balanced`.
63
+
64
+ The desktop app has an EN/ES selector in the side rail, and the choice is saved in `llm-tuner/settings.json`, next to the presets. The profile you choose in the wizard or in the app is saved there too.
65
+
66
+ ## What gets tried
67
+
68
+ For each KV cache type the profile allows (`f16`, `q8_0`, `q4_0`; `quality` leaves out `q4_0`) the estimator computes:
69
+
70
+ - **Memory per layer**, read from the GGUF (the real size of each tensor).
71
+ - **Context cache** based on layers with attention, KV heads, dimension and sliding window (SWA).
72
+ - **Layer split across GPUs**, prioritizing the GPU with the widest PCIe link and no monitor attached, with free headroom balanced evenly across cards.
73
+ - **For MoE models that don't fit**, how many expert layers can stay in RAM (`--n-cpu-moe`).
74
+ - **Expected speed**, based on memory bandwidth.
75
+
76
+ The best options are measured with a short prompt and another that fills ~50% of the context. The profile decides the winner: in `balanced`, the fastest one with a full context, weighted by the quality of the KV type.
77
+
78
+ - **LM Studio**: it runs the same `llama-server` that LM Studio uses, with the exact same parameters. The result is written to the model's config (`~/.lmstudio/.internal/user-concrete-model-default-config/…`) and to `hardware-config.json`. LM Studio only restarts if the hardware config changes.
79
+ - **Ollama**: each option is measured on a private `ollama serve` on another port, because the KV type and Flash Attention are global. The model `<model>-tuned-16k` is created with `num_ctx`, `num_gpu` and `num_thread`, and the server variables are configured (systemd on Linux, `setx` on Windows, `launchctl` on macOS).
80
+
81
+ A `*.bak-llm-tuner-*` copy is saved before modifying any file.
82
+
83
+ ## Where presets are stored
84
+
85
+ There is one preset per engine, model, context and profile.
86
+
87
+ - Linux: `~/.config/llm-tuner/presets/`
88
+ - macOS: `~/Library/Application Support/llm-tuner/presets/`
89
+ - Windows: `%APPDATA%\llm-tuner\presets\`
90
+
91
+ ## Platform status
92
+
93
+ | | Tested | Notes |
94
+ |---|---|---|
95
+ | Linux + NVIDIA + LM Studio | ✅ 2× RTX 5070, Ryzen 7 5800X | Calibrated with real measurements |
96
+ | Linux + Ollama | Unit tests | Still needs testing with Ollama installed |
97
+ | Windows / macOS | Untested | Paths, installers and detection implemented |
98
+ | AMD (ROCm) / Apple Silicon / Intel | Untested | VRAM detection included; bandwidth from table |
99
+
100
+ Load profiles are covered by unit tests and were checked with one real load per profile on LM Studio (Linux, 2× RTX 5070). Choosing between several downloaded variants of a model, and the profiles on Ollama, have not been exercised on real hardware yet.
101
+
102
+ ## Development
103
+
104
+ ```bash
105
+ git clone https://github.com/ronald32acunadev/llmtuner.git
106
+ cd llmtuner
107
+ npm install
108
+ npm start # terminal wizard
109
+ npm run desktop # desktop app
110
+ ```
111
+
112
+ ## Tests
113
+
114
+ ```bash
115
+ npm test
116
+ ```
117
+
118
+ Includes as regression the real results of 2× RTX 5070 with Qwen2.5-Coder-32B: 16K and 20K with q8_0 fit entirely, 24K doesn't; 32K only fits with q4_0.
119
+
120
+ ## Notes
121
+
122
+ - Electron is not downloaded when the package is installed: the first run of the desktop app downloads it. If that download fails, extract `~/.cache/electron/*/electron-*.zip` into the `dist` folder of the installed `electron` package and create `path.txt` next to it with the text `electron`. The desktop app disables the Chromium sandbox only when it can't work (no `chrome-sandbox` with root setuid).
123
+ - LM Studio's internal files are not a public API. This version is verified with LM Studio 0.4.25.
Binary file
Binary file
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env node
2
+ // Starts the desktop app. On Linux, Chromium's SUID sandbox helper must be
3
+ // root-owned with mode 4755; that's impossible on NTFS/exFAT drives or in an
4
+ // npm install, so the sandbox is disabled only in that case. The
5
+ // window only loads this app's own local server.
6
+ import fs from 'node:fs';
7
+ import path from 'node:path';
8
+ import { spawn } from 'node:child_process';
9
+ import { createRequire } from 'node:module';
10
+ import { fileURLToPath } from 'node:url';
11
+ import os from 'node:os';
12
+
13
+ // Says why the desktop app could not start, in the saved UI language, and exits.
14
+ async function fail(error) {
15
+ const { readSettings } = await import('../src/core/settings.js');
16
+ const { t } = await import('../src/i18n/index.js');
17
+ const { lang } = await readSettings();
18
+ console.error(t(lang, 'cli.desktopStartFailed', { error: error?.message ?? String(error) }));
19
+ process.exit(1);
20
+ }
21
+
22
+ // Requiring `electron` returns the path of its executable. It downloads the
23
+ // runtime first when it is missing, which happens on the first launch.
24
+ let electron;
25
+ try {
26
+ electron = createRequire(import.meta.url)('electron');
27
+ } catch (error) {
28
+ await fail(error);
29
+ }
30
+ const env = { ...process.env };
31
+
32
+ if (process.platform === 'linux') {
33
+ try {
34
+ const st = fs.statSync(path.join(path.dirname(electron), 'chrome-sandbox'));
35
+ if (st.uid !== 0 || !(st.mode & 0o4000)) env.ELECTRON_DISABLE_SANDBOX = '1';
36
+ } catch {
37
+ env.ELECTRON_DISABLE_SANDBOX = '1';
38
+ }
39
+
40
+ // Ensure Linux desktop entry exists
41
+ ensureLinuxDesktopEntry();
42
+ }
43
+
44
+ const main = path.join(path.dirname(fileURLToPath(import.meta.url)), 'main.js');
45
+ const child = spawn(electron, [main], { stdio: 'inherit', env });
46
+ child.on('error', fail);
47
+ child.on('exit', (code) => process.exit(code ?? 0));
48
+
49
+ function ensureLinuxDesktopEntry() {
50
+ try {
51
+ const appsDir = process.env.XDG_DATA_HOME
52
+ ? path.join(process.env.XDG_DATA_HOME, 'applications')
53
+ : path.join(os.homedir(), '.local', 'share', 'applications');
54
+
55
+ fs.mkdirSync(appsDir, { recursive: true });
56
+
57
+ const desktopFilePath = path.join(appsDir, 'llm-tuner.desktop');
58
+ const iconPath = path.join(path.dirname(fileURLToPath(import.meta.url)), 'icon.png');
59
+
60
+ const desktopContent = `[Desktop Entry]
61
+ Type=Application
62
+ Name=LLM Tuner
63
+ Comment=Find the fastest configuration to run local LLMs
64
+ Exec=${process.execPath} ${fileURLToPath(import.meta.url)}
65
+ Icon=${iconPath}
66
+ Terminal=false
67
+ StartupWMClass=llm-tuner
68
+ Categories=Development;Utility;
69
+ `;
70
+
71
+ // Only write if file doesn't exist or content differs
72
+ let currentContent = '';
73
+ try {
74
+ currentContent = fs.readFileSync(desktopFilePath, 'utf8');
75
+ } catch {
76
+ // File doesn't exist, so we'll write it
77
+ }
78
+
79
+ if (currentContent !== desktopContent) {
80
+ fs.writeFileSync(desktopFilePath, desktopContent);
81
+ }
82
+ } catch {
83
+ // Failures are ignored to avoid preventing app launch
84
+ }
85
+ }
@@ -0,0 +1,63 @@
1
+ import { app, BrowserWindow, shell, nativeImage } from 'electron';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { startServer } from '../src/web/server.js';
5
+
6
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
+
8
+ // Set app name early
9
+ app.name = 'LLM Tuner';
10
+
11
+ if (process.platform === 'win32') {
12
+ app.setAppUserModelId('dev.llm-tuner.app');
13
+ } else if (process.platform === 'linux') {
14
+ if (typeof app.setDesktopName === 'function') {
15
+ app.setDesktopName('llm-tuner.desktop');
16
+ }
17
+ }
18
+
19
+ // The desktop app is the web UI in a native window, served on a random local port.
20
+ let server;
21
+
22
+ async function createWindow() {
23
+ const started = await startServer({ port: 0 });
24
+ server = started.server;
25
+
26
+ const iconFile = process.platform === 'win32' ? 'icon.ico' : 'icon.png';
27
+ const icon = nativeImage.createFromPath(path.join(__dirname, iconFile));
28
+
29
+ const win = new BrowserWindow({
30
+ width: 1200,
31
+ height: 860,
32
+ minWidth: 420,
33
+ title: 'LLM Tuner',
34
+ icon: !icon.isEmpty() ? icon : path.join(__dirname, iconFile),
35
+ autoHideMenuBar: true,
36
+ webPreferences: { contextIsolation: true, sandbox: true },
37
+ });
38
+
39
+ // External links open in the system browser.
40
+ win.webContents.setWindowOpenHandler(({ url }) => {
41
+ shell.openExternal(url);
42
+ return { action: 'deny' };
43
+ });
44
+
45
+ await win.loadURL(started.url);
46
+ }
47
+
48
+ app.whenReady().then(async () => {
49
+ if (process.platform === 'darwin' && app.dock) {
50
+ const iconFile = 'icon.png';
51
+ const icon = nativeImage.createFromPath(path.join(__dirname, iconFile));
52
+ if (!icon.isEmpty()) {
53
+ app.dock.setIcon(icon);
54
+ }
55
+ }
56
+
57
+ await createWindow();
58
+ });
59
+
60
+ app.on('window-all-closed', () => {
61
+ server?.close();
62
+ app.quit();
63
+ });
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "llm-tuner",
3
+ "version": "0.1.0",
4
+ "description": "Detects your hardware and finds the fastest configuration to run local LLMs with LM Studio or Ollama.",
5
+ "keywords": [
6
+ "llm",
7
+ "lm-studio",
8
+ "ollama",
9
+ "gguf",
10
+ "llama.cpp",
11
+ "benchmark",
12
+ "gpu",
13
+ "vram"
14
+ ],
15
+ "homepage": "https://github.com/ronald32acunadev/llmtuner#readme",
16
+ "bugs": {
17
+ "url": "https://github.com/ronald32acunadev/llmtuner/issues"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/ronald32acunadev/llmtuner.git"
22
+ },
23
+ "license": "MIT",
24
+ "type": "module",
25
+ "main": "src/core/index.js",
26
+ "bin": {
27
+ "llm-tuner": "src/cli/index.js",
28
+ "llm-tuner-desktop": "electron/launch.js"
29
+ },
30
+ "files": [
31
+ "src/",
32
+ "electron/",
33
+ "README.es.md"
34
+ ],
35
+ "scripts": {
36
+ "start": "node src/cli/index.js",
37
+ "desktop": "node electron/launch.js",
38
+ "test": "node --test test/*.test.js"
39
+ },
40
+ "engines": {
41
+ "node": ">=22"
42
+ },
43
+ "dependencies": {
44
+ "@inquirer/prompts": "^7.0.0",
45
+ "electron": "^44.5.1",
46
+ "systeminformation": "^5.23.0"
47
+ }
48
+ }