opencode-mask-j0k3r-dev-rgl 2.0.23 → 3.0.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/README.md ADDED
@@ -0,0 +1,79 @@
1
+ # Arch Mask para OpenCode AI 🚀
2
+
3
+ Esta es una **máscara TUI (Terminal User Interface)** personalizada para [OpenCode AI](https://opencode.ai), diseñada con una estética inspirada en Arch Linux y optimizada para desarrolladores que buscan un entorno de terminal elegante, funcional y altamente personalizable.
4
+
5
+ El proyecto ha sido creado como una **base abierta**: siéntete libre de tomar el código, editarlo y adaptarlo a tus propios gustos. Todo el código fuente está **extensamente comentado en español** para resolver dudas sobre el funcionamiento de los temas, el arte ASCII y la integración con la API de OpenCode.
6
+
7
+ ## ✨ Características
8
+
9
+ - 🎨 **Selector de Temas con Preview**: Cambia el estilo visual en tiempo real con `/mask` o `ctrl+m`.
10
+ - 🖼️ **Arte ASCII Dinámico**: Logo de Arch Linux que se adapta a los colores de cada tema.
11
+ - 📊 **Sidebar de Estadísticas**: Visualización reactiva de tokens, uso de contexto y costes de sesión.
12
+ - ☁️ **Transparencia Real**: Configurado para respetar el fondo y la opacidad de tu terminal.
13
+ - 📂 **Gestión de Plugins**: Configura automáticamente la barra lateral nativa de OpenCode.
14
+
15
+ ## 🛠️ Instalación
16
+
17
+ Existen dos formas principales de instalar y utilizar esta máscara en tu instancia de OpenCode:
18
+
19
+ ### Opción 1: Instalación vía NPM (Recomendado para usuarios)
20
+
21
+ Si el paquete está publicado en el registro de NPM, puedes agregarlo directamente a tu configuración global:
22
+
23
+ 1. Instala el plugin globalmente:
24
+ ```bash
25
+ opencode plugin nombre-del-paquete -g
26
+ ```
27
+ 2. OpenCode actualizará automáticamente tu archivo `~/.config/opencode/tui.json`.
28
+
29
+ ### Opción 2: Instalación Local (Modo Desarrollador)
30
+
31
+ Si prefieres tener el código en tu PC para editarlo o probarlo localmente, sigue estos pasos:
32
+
33
+ 1. Clona o descarga este repositorio en una carpeta de tu preferencia.
34
+ 2. Entra en la carpeta y asegúrate de instalar las dependencias:
35
+ ```bash
36
+ npm install
37
+ ```
38
+ 3. Abre tu archivo de configuración de OpenCode (`~/.config/opencode/tui.json`) y apunta el plugin directamente a la ruta de tu carpeta:
39
+
40
+ ```json
41
+ {
42
+ "plugin": [
43
+ [
44
+ "/home/tu-usuario/ruta/a/opencode-mask",
45
+ {
46
+ "enabled": true,
47
+ "theme": "tokyo-night-dev",
48
+ "set_theme": true,
49
+ "show_sidebar": true
50
+ }
51
+ ]
52
+ ]
53
+ }
54
+ ```
55
+
56
+ ## ⌨️ Atajos y Comandos
57
+
58
+ - **/mask**: Abre el selector de temas.
59
+ - **ctrl+m**: Atajo rápido para el selector de temas (puedes cambiarlo en `tui.tsx`).
60
+ - **onMove**: Navega por la lista de temas para ver una previsualización instantánea.
61
+
62
+ ## 🤝 Contribuciones y Personalización
63
+
64
+ Este repositorio es una base educativa. Si quieres crear tu propio tema:
65
+ 1. Añade un nuevo archivo `.json` en la carpeta `themes/` (usa `j0k3r-dev-rgl.json` como plantilla).
66
+ 2. Regístralo en el bloque de instalación de `tui.tsx`.
67
+ 3. ¡Disfruta de tu nueva interfaz!
68
+
69
+ ## 📜 Créditos y Agradecimientos
70
+
71
+ Este proyecto fue creado el **4 de abril de 2026** tomando como base e inspiración el excelente trabajo de **IrrealV** en su plugin:
72
+ 👉 [IrrealV/plugin-gentleman](https://github.com/IrrealV/plugin-gentleman)
73
+
74
+ ¡Muchas gracias por compartir tu conocimiento con la comunidad!
75
+
76
+ ---
77
+ Desarrollado con ❤️ por **j0k3r**
78
+
79
+ > **"El código es poesía, y el Open Source es nuestra forma de compartirla con el mundo. ¡VIVA EL OPEN SOURCE!"** 🐧✨
package/components.tsx CHANGED
@@ -11,33 +11,54 @@ import {
11
11
  import type { Cfg } from "./config";
12
12
  import { getOSName, getProviders } from "./detection";
13
13
 
14
- // ─── Home screen: Large Arch Linux logo + legend ─────────────────────────────
14
+ // ─── Pantalla Principal: Logo grande de Arch Linux + Leyenda ─────────────────
15
+ /**
16
+ * Componente que muestra el logo de Arch Linux a gran escala en la pantalla de inicio.
17
+ * Utiliza un sistema de mapeo por zonas para aplicar diferentes colores del tema actual
18
+ * a cada parte del arte ASCII, permitiendo que el logo se adapte visualmente a cualquier tema.
19
+ *
20
+ * @param props Contiene el tema actual de la TUI.
21
+ */
15
22
  export const HomeLogo = (props: { theme: TuiThemeCurrent }) => {
16
23
  const t = props.theme;
17
24
 
25
+ /**
26
+ * Mapea una zona de color definida en el frame ASCII a una propiedad del tema actual.
27
+ * @param zone Nombre de la zona (ej. 'hotPink', 'white', 'purple').
28
+ */
29
+ const getZoneColor = (zone: string) => {
30
+ if (zone === "hotPink") return t.secondary;
31
+ if (zone === "white") return t.primary;
32
+ if (zone === "purple") return t.accent;
33
+ if (zone === "neonBlue") return t.primary;
34
+ return t.primary;
35
+ };
36
+
18
37
  return (
19
38
  <box flexDirection="column" alignItems="center">
39
+ {/* Mapeo del arte ASCII línea por línea aplicando colores según su zona */}
20
40
  {archLogoHome.map((line, i) => {
21
41
  const zone = homeLogoZones[i];
22
- const color = zoneColors[zone] || t.primary;
42
+ const color = getZoneColor(zone);
23
43
  return <text fg={color}>{line}</text>;
24
44
  })}
25
45
 
26
46
  <text> </text>
47
+ {/* Leyenda personalizada estilo CLI debajor del logo */}
27
48
  <box flexDirection="row" gap={0}>
28
- <text fg="#ff2d78" bold={true}>
49
+ <text fg={t.secondary} bold={true}>
29
50
  j0k3r
30
51
  </text>
31
- <text fg="#9d4edd">-</text>
32
- <text fg="#00c8ff" bold={true}>
52
+ <text fg={t.accent}>-</text>
53
+ <text fg={t.primary} bold={true}>
33
54
  dev
34
55
  </text>
35
- <text fg="#9d4edd">-</text>
36
- <text fg="#4dd8ff" bold={true}>
56
+ <text fg={t.accent}>-</text>
57
+ <text fg={t.info} bold={true}>
37
58
  rgl
38
59
  </text>
39
- <text fg="#9d4edd">@</text>
40
- <text fg="#ffd166" bold={true}>
60
+ <text fg={t.accent}>@</text>
61
+ <text fg={t.warning} bold={true}>
41
62
  latest
42
63
  </text>
43
64
  </box>
@@ -61,53 +82,69 @@ export const HomeLogo = (props: { theme: TuiThemeCurrent }) => {
61
82
  );
62
83
  };
63
84
 
64
- // ─── Progress bar helper ──────────────────────────────────────────────────────
85
+ // ─── Utilidad de Barra de Progreso ───────────────────────────────────────────
86
+ /**
87
+ * Un componente visual que renderiza una barra de progreso estilo consola.
88
+ * Utiliza caracteres de bloque (█ y ░) para representar el llenado.
89
+ */
65
90
  const ProgressBar = (props: {
66
- value: number; // 0100
67
- width?: number;
68
- fillColor: string;
69
- emptyColor: string;
91
+ value: number; // Porcentaje (0-100)
92
+ width?: number; // Ancho total de la barra en caracteres
93
+ fillColor: string; // Color de la parte llena
94
+ emptyColor: string; // Color de la parte vacía
70
95
  theme: TuiThemeCurrent;
71
96
  }) => {
72
- const width = props.width ?? 12;
73
- const pct = Math.max(0, Math.min(100, props.value));
74
- const filled = Math.round((pct / 100) * width);
75
- const empty = width - filled;
76
- const fill = "".repeat(filled);
77
- const trail = "░".repeat(empty);
97
+ const width = () => props.width ?? 12;
98
+ const pct = () => Math.max(0, Math.min(100, props.value));
99
+
100
+ /**
101
+ * Calcula cuántos bloques de '' deben mostrarse.
102
+ * Garantiza que si el porcentaje es > 0, al menos se vea 1 bloque.
103
+ */
104
+ const filled = () => {
105
+ const f = Math.round((pct() / 100) * width());
106
+ return pct() > 0 && f === 0 ? 1 : f;
107
+ };
108
+ const empty = () => width() - filled();
78
109
 
79
110
  return (
80
111
  <box flexDirection="row" gap={0}>
81
112
  <text fg={props.theme.textMuted}>[</text>
82
- <text fg={props.fillColor}>{fill}</text>
83
- <text fg={props.emptyColor}>{trail}</text>
113
+ <text fg={props.fillColor}>{"█".repeat(filled())}</text>
114
+ <text fg={props.emptyColor}>{"░".repeat(empty())}</text>
84
115
  <text fg={props.theme.textMuted}>]</text>
85
116
  </box>
86
117
  );
87
118
  };
88
119
 
89
- // ─── Sidebar: Arch logo + stats panel ────────────────────────────────────────
120
+ // ─── Sidebar: Logo de Arch + Panel de Estadísticas ───────────────────────────
121
+ /**
122
+ * Componente principal del panel lateral.
123
+ * Muestra una versión mini del logo de Arch Linux, la rama de Git actual y
124
+ * estadísticas detalladas de uso del contexto (tokens y costo estimado).
125
+ */
90
126
  export const SidebarArch = (props: {
91
127
  theme: TuiThemeCurrent;
128
+ selectedTheme: string; // ID del tema activo
92
129
  config: Cfg;
93
- branch?: string;
94
- getMessages?: () => any[];
95
- contextLimit: number;
130
+ branch?: string; // Rama git actual detectada
131
+ getMessages?: () => any[]; // Función para obtener el historial de mensajes
132
+ contextLimit: number; // Límite de tokens del contexto
96
133
  }) => {
97
134
  if (!props.config.show_sidebar) return null;
98
135
 
99
136
  const t = props.theme;
100
137
 
101
- // ── Context — Reactivo y con la misma lógica interna de OpenCode ──────────
138
+ /**
139
+ * Calcula el total de tokens utilizados en el último mensaje del asistente.
140
+ */
102
141
  const getContextTokens = () => {
103
142
  const messages = props.getMessages ? props.getMessages() : [];
104
- // Buscar el último mensaje de asistente que tuvo output de tokens
105
143
  const last = [...messages]
106
144
  .reverse()
107
145
  .find((m: any) => m.role === "assistant" && m.tokens?.output > 0);
108
146
  if (!last) return 0;
109
147
 
110
- // Suma total de todos los tokens del contexto de ese mensaje exacto
111
148
  const tk = last.tokens;
112
149
  return (
113
150
  (tk.input ?? 0) +
@@ -118,6 +155,9 @@ export const SidebarArch = (props: {
118
155
  );
119
156
  };
120
157
 
158
+ /**
159
+ * Suma el costo acumulado de todos los mensajes en la sesión actual.
160
+ */
121
161
  const getTotalCost = () => {
122
162
  const messages = props.getMessages ? props.getMessages() : [];
123
163
  return messages.reduce(
@@ -126,95 +166,101 @@ export const SidebarArch = (props: {
126
166
  );
127
167
  };
128
168
 
169
+ /**
170
+ * Calcula el porcentaje de ocupación del contexto actual respecto al límite.
171
+ */
129
172
  const getContextPct = () => {
130
173
  const limit = props.contextLimit || 1_000_000;
131
- return Math.min(100, Math.round((getContextTokens() / limit) * 100));
174
+ const pct = Math.round((getContextTokens() / limit) * 100);
175
+ return Math.min(100, Math.max(0, pct));
132
176
  };
133
177
 
178
+ /**
179
+ * Calcula el porcentaje de presupuesto gastado (asume un máximo de $1 para visualización).
180
+ */
134
181
  const getCostPct = () => Math.min(100, Math.round(getTotalCost() * 100));
135
182
 
136
183
  const fmtTokens = (n: number) =>
137
184
  n >= 1000 ? `${(n / 1000).toFixed(1)}k` : `${n}`;
138
185
  const fmtCost = (n: number) => `$${n.toFixed(2)}`;
139
186
 
140
- // Color: verde → amarillo → rojo según el %
141
- const getCtxColor = () => {
142
- const pct = getContextPct();
143
- return pct < 50 ? "#00e5a0" : pct < 80 ? "#ffd166" : "#ff2d78";
144
- };
145
-
146
187
  return (
147
188
  <box flexDirection="column" alignItems="center">
148
- {/* Mini Arch logo */}
189
+ {/* Mini logo de Arch adaptado al tema */}
149
190
  {archLogoSidebar.map((line, i) => {
150
191
  const zone = sidebarLogoZones[i];
151
- const color = zoneColors[zone] || t.primary;
192
+ const color = zone === "hotPink" ? t.secondary : t.primary;
152
193
  return <text fg={color}>{line}</text>;
153
194
  })}
154
195
 
155
- <text fg={t.textMuted}>j0k3r@latest</text>
196
+ <text fg={t.textMuted} scale={0.9}>j0k3r@latest</text>
156
197
  <text> </text>
157
198
 
158
- {/* Git branch */}
199
+ {/* Indicador de rama Git */}
159
200
  {props.branch && (
160
201
  <box flexDirection="row" gap={1}>
161
- <text fg="#ffd166">⎇</text>
162
- <text fg={t.text}>{props.branch}</text>
202
+ <text fg={t.accent}>⎇</text>
203
+ <text fg={t.text} bold={true}>{props.branch}</text>
163
204
  </box>
164
205
  )}
165
206
 
166
- {/* ── Context (tokens + % used + cost) ── siempre visible */}
207
+ {/* ── Sección de Contexto (tokens + % usado + costo) ── */}
167
208
  <box flexDirection="column" alignItems="center" marginTop={1}>
168
- <text fg={t.textMuted} bold={true}>
169
- Context
170
- </text>
209
+ <text fg={t.textMuted} bold={true}>Contexto</text>
171
210
 
172
- {/* tokens */}
173
- <box flexDirection="row" gap={1}>
174
- <text fg={t.text}>{fmtTokens(getContextTokens())}</text>
211
+ {/* Barra de Tokens - Color Primario */}
212
+ <box flexDirection="row" gap={1} marginTop={0.5}>
213
+ <text fg={t.primary} bold={true}>{fmtTokens(getContextTokens())}</text>
175
214
  <text fg={t.textMuted}>tokens</text>
176
215
  </box>
177
216
  <ProgressBar
178
217
  value={getContextPct()}
179
218
  width={18}
180
- fillColor={getCtxColor()}
181
- emptyColor="#3a3a3a"
219
+ fillColor={t.primary}
220
+ emptyColor={t.borderSubtle}
182
221
  theme={t}
183
222
  />
184
223
 
185
- {/* % used */}
186
- <box flexDirection="row" gap={1}>
187
- <text fg={getCtxColor()}>{getContextPct()}%</text>
188
- <text fg={t.textMuted}>used</text>
224
+ {/* Barra de Uso - Color Secundario */}
225
+ <box flexDirection="row" gap={1} marginTop={0.5}>
226
+ <text fg={t.secondary} bold={true}>{getContextPct()}%</text>
227
+ <text fg={t.textMuted}>usado</text>
189
228
  </box>
190
229
  <ProgressBar
191
230
  value={getContextPct()}
192
231
  width={18}
193
- fillColor={getCtxColor()}
194
- emptyColor="#3a3a3a"
232
+ fillColor={t.secondary}
233
+ emptyColor={t.borderSubtle}
195
234
  theme={t}
196
235
  />
197
236
 
198
- {/* $ spent */}
199
- <box flexDirection="row" gap={1}>
200
- <text fg="#ffd166">{fmtCost(getTotalCost())}</text>
201
- <text fg={t.textMuted}>spent</text>
237
+ {/* Barra de Costo - Color de Advertencia (Warning) */}
238
+ <box flexDirection="row" gap={1} marginTop={0.5}>
239
+ <text fg={t.warning} bold={true}>{fmtCost(getTotalCost())}</text>
240
+ <text fg={t.textMuted}>gastado</text>
202
241
  </box>
203
242
  <ProgressBar
204
243
  value={getCostPct()}
205
244
  width={18}
206
- fillColor="#ffd166"
207
- emptyColor="#3a3a3a"
245
+ fillColor={t.warning}
246
+ emptyColor={t.borderSubtle}
208
247
  theme={t}
209
248
  />
210
249
  </box>
211
250
 
212
251
  <text> </text>
252
+ <text fg={t.textMuted} dimColor={true} scale={0.8}>
253
+ máscara: {props.selectedTheme.toUpperCase()}
254
+ </text>
213
255
  </box>
214
256
  );
215
257
  };
216
258
 
217
- // ─── Environment detection line ──────────────────────────────────────────────
259
+ // ─── Línea de detección de entorno ───────────────────────────────────────────
260
+ /**
261
+ * Muestra información sobre el sistema operativo y los proveedores de IA
262
+ * activos al final de la pantalla de inicio.
263
+ */
218
264
  export const DetectedEnv = (props: {
219
265
  theme: TuiThemeCurrent;
220
266
  providers: ReadonlyArray<{ id: string; name: string }> | undefined;
@@ -231,7 +277,7 @@ export const DetectedEnv = (props: {
231
277
 
232
278
  return (
233
279
  <box flexDirection="row" gap={1}>
234
- <text fg={props.theme.textMuted}>detected:</text>
280
+ <text fg={props.theme.textMuted}>detectado:</text>
235
281
  {os && <text fg={props.theme.text}>{os}</text>}
236
282
  {os && providers && <text fg={props.theme.textMuted}>·</text>}
237
283
  {providers && <text fg={props.theme.text}>{providers}</text>}
package/config.ts CHANGED
@@ -1,30 +1,53 @@
1
- // ─── Configuration types and parsing helpers ──────────────────────────────────
1
+ // ─── Tipos de configuración y funciones de ayuda para el parseo ───────────────
2
2
 
3
+ /**
4
+ * Define la estructura de configuración para el plugin Arch Mask.
5
+ * Controla qué elementos visuales se muestran y qué tema se utiliza.
6
+ */
3
7
  export type Cfg = {
4
- enabled: boolean
5
- theme: string
6
- set_theme: boolean
7
- show_detected: boolean
8
- show_os: boolean
9
- show_providers: boolean
10
- show_sidebar: boolean
8
+ enabled: boolean // Indica si el plugin está activo
9
+ theme: string // El ID del tema visual a utilizar
10
+ color_preset: "current" | "cyber" | "neon" | "overclock" // Ajustes preestablecidos de color
11
+ set_theme: boolean // Si debe aplicar el tema automáticamente al iniciar
12
+ show_detected: boolean // Mostrar/ocultar la línea de entorno detectado
13
+ show_os: boolean // Mostrar/ocultar el nombre del sistema operativo
14
+ show_providers: boolean // Mostrar/ocultar los proveedores de IA detectados
15
+ show_sidebar: boolean // Mostrar/ocultar el panel lateral personalizado
11
16
  }
12
17
 
18
+ /**
19
+ * Utilidad para validar y seleccionar un valor de cadena con un respaldo (fallback).
20
+ * @param value El valor a evaluar
21
+ * @param fallback El valor por defecto si 'value' es inválido
22
+ */
13
23
  const pick = (value: unknown, fallback: string): string => {
14
24
  if (typeof value !== "string") return fallback
15
25
  if (!value.trim()) return fallback
16
26
  return value
17
27
  }
18
28
 
29
+ /**
30
+ * Utilidad para validar valores booleanos con un respaldo (fallback).
31
+ * @param value El valor a evaluar
32
+ * @param fallback El valor por defecto si 'value' es inválido
33
+ */
19
34
  const bool = (value: unknown, fallback: boolean): boolean => {
20
35
  if (typeof value !== "boolean") return fallback
21
36
  return value
22
37
  }
23
38
 
39
+ /**
40
+ * Procesa las opciones proporcionadas y devuelve un objeto de configuración completo.
41
+ * Asegura que todas las propiedades tengan valores válidos mediante el uso de fallbacks.
42
+ *
43
+ * @param opts Diccionario de opciones crudas, usualmente provenientes de la configuración del plugin.
44
+ * @returns Un objeto de tipo Cfg con valores saneados.
45
+ */
24
46
  export const cfg = (opts: Record<string, unknown> | undefined): Cfg => {
25
47
  return {
26
48
  enabled: bool(opts?.enabled, true),
27
49
  theme: pick(opts?.theme, "j0k3r-dev-rgl"),
50
+ color_preset: (opts?.color_preset as any) || "current",
28
51
  set_theme: bool(opts?.set_theme, true),
29
52
  show_detected: bool(opts?.show_detected, true),
30
53
  show_os: bool(opts?.show_os, true),
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "opencode-mask-j0k3r-dev-rgl",
4
- "version": "2.0.23",
4
+ "version": "3.0.0",
5
5
  "description": "Arch Linux TUI mask for OpenCode — hot pink theme with prominent ASCII logo and j0k3r-dev-rgl@latest legend",
6
6
  "type": "module",
7
7
  "exports": {
package/tui.tsx CHANGED
@@ -6,25 +6,153 @@ import { cfg } from "./config";
6
6
 
7
7
  const id = "j0k3r-dev-rgl";
8
8
 
9
+ /**
10
+ * Función de utilidad para convertir objetos desconocidos en diccionarios planos.
11
+ */
9
12
  const rec = (value: unknown) => {
10
13
  if (!value || typeof value !== "object" || Array.isArray(value)) return;
11
14
  return Object.fromEntries(Object.entries(value));
12
15
  };
13
16
 
17
+ /**
18
+ * Punto de entrada principal para el plugin Arch Mask.
19
+ * Este plugin personaliza la interfaz de usuario (TUI) de OpenCode con una estética inspirada en Arch Linux.
20
+ *
21
+ * @param api La API de la TUI proporcionada por el sistema de plugins.
22
+ * @param options Opciones de configuración del usuario.
23
+ */
14
24
  const tui: TuiPlugin = async (api, options) => {
25
+ // Cargar y validar la configuración
15
26
  const boot = cfg(rec(options));
16
27
  if (!boot.enabled) return;
17
28
 
18
- // Theme setup
29
+ /**
30
+ * Resuelve la ruta absoluta de un archivo de tema JSON.
31
+ */
32
+ const resolveTheme = (name: string) =>
33
+ new URL(`./themes/${name}.json`, import.meta.url).pathname;
34
+
35
+ // ─── Instalación de Temas ────────────────────────────────────────────────
36
+ // Registra todos los esquemas de color disponibles en el sistema de la TUI.
19
37
  try {
20
- await api.theme.install("./themes/j0k3r-dev-rgl.json");
21
- if (boot.set_theme) api.theme.set(boot.theme);
22
- } catch (error) {
23
- // Silent fail
38
+ await api.theme.install(resolveTheme("j0k3r-dev-rgl"));
39
+ await api.theme.install(resolveTheme("arch-cyber"));
40
+ await api.theme.install(resolveTheme("arch-neon"));
41
+ await api.theme.install(resolveTheme("arch-overclock"));
42
+ await api.theme.install(resolveTheme("mask-cyber"));
43
+ await api.theme.install(resolveTheme("tokyo-night-dev"));
44
+ await api.theme.install(resolveTheme("arch-electric"));
45
+ await api.theme.install(resolveTheme("j0k3r-neon"));
46
+ } catch (e) {
47
+ console.error("Fallo al instalar temas", e);
24
48
  }
25
49
 
26
- // Asegurarnos de que los plugins internos estén ACTIVOS
27
- // (los reactivamos por si quedaron apagados de la sesión anterior)
50
+ // ─── Comandos y Atajos ───────────────────────────────────────────────────
51
+ api.command.register(() => [
52
+ {
53
+ title: "Cambiar Máscara",
54
+ value: "mask",
55
+ description: "Cambiar el estilo visual de Arch Mask",
56
+ keybind: "ctrl+m",
57
+ slash: { name: "mask" },
58
+ onSelect: () => {
59
+ // Guardamos el tema actual por si el usuario cancela la selección
60
+ const originalTheme = api.theme.selected;
61
+
62
+ // Abrir un diálogo de selección con previsualización en tiempo real
63
+ api.ui.dialog.replace(() => (
64
+ <api.ui.DialogSelect
65
+ title="Arch Mask: Theme Preview"
66
+ options={[
67
+ {
68
+ title: "Default (j0k3r-dev-rgl)",
69
+ value: "j0k3r-dev-rgl",
70
+ description: "Estilo original con acentos púrpuras y azul Arch."
71
+ },
72
+ {
73
+ title: "Tokyo Night Dev",
74
+ value: "tokyo-night-dev",
75
+ description: "Lindo tema dark profesional con fondo transparente (Recomendado)."
76
+ },
77
+ {
78
+ title: "Arch Electric",
79
+ value: "arch-electric",
80
+ description: "Tema neón azul eléctrico que combina con tu fondo de pantalla."
81
+ },
82
+ {
83
+ title: "j0k3r Neon",
84
+ value: "j0k3r-neon",
85
+ description: "Inspirado en tu logo de Arch y nombre en verde neón."
86
+ },
87
+ {
88
+ title: "Cyber Arch",
89
+ value: "arch-cyber",
90
+ description: "Inspirado en los colores clásicos de Arch Linux (Azules y Grises)."
91
+ },
92
+ {
93
+ title: "Semáforo Neón",
94
+ value: "arch-neon",
95
+ description: "Contraste extremo con verdes y azules neón."
96
+ },
97
+ {
98
+ title: "Overclock",
99
+ value: "arch-overclock",
100
+ description: "Estilo agresivo en rosa y blanco puro."
101
+ },
102
+ {
103
+ title: "Cyber Mask",
104
+ value: "mask-cyber",
105
+ description: "Futurismo puro: neón azul y rosa fuerte."
106
+ },
107
+ ]}
108
+ current={api.theme.selected}
109
+ /**
110
+ * Lógica de Previsualización:
111
+ * El evento 'onMove' se dispara cada vez que el usuario navega por la lista.
112
+ * Cambiamos el tema global instantáneamente para que el usuario vea el resultado.
113
+ */
114
+ onMove={(opt) => {
115
+ try {
116
+ api.theme.set(opt.value);
117
+ } catch (e) {}
118
+ }}
119
+ /**
120
+ * Lógica de Confirmación:
121
+ * Aplica el tema definitivamente y lo guarda en el almacenamiento persistente (KV).
122
+ */
123
+ onSelect={(opt) => {
124
+ try {
125
+ api.theme.set(opt.value);
126
+ api.kv.set("selected_theme", opt.value);
127
+ api.ui.dialog.clear();
128
+ api.ui.toast({
129
+ title: "Máscara Aplicada",
130
+ message: `Tema ${opt.title} configurado como predeterminado.`,
131
+ variant: "success",
132
+ });
133
+ } catch (e) {}
134
+ }}
135
+ /**
136
+ * Lógica de Cancelación:
137
+ * Si el usuario presiona ESC o cierra el diálogo, restauramos el tema que estaba antes.
138
+ */
139
+ onCancel={() => {
140
+ try {
141
+ api.theme.set(originalTheme);
142
+ } catch (e) {}
143
+ api.ui.dialog.clear();
144
+ }}
145
+ />
146
+ ));
147
+ },
148
+ },
149
+ ]);
150
+
151
+ // ─── Gestión de Plugins Internos ──────────────────────────────────────────
152
+ /**
153
+ * Configura qué plugins de la barra lateral deben estar activos o inactivos
154
+ * para mantener una interfaz limpia y coherente con el estilo 'Mask'.
155
+ */
28
156
  const enableInternal = async () => {
29
157
  try {
30
158
  await api.plugins.deactivate("internal:sidebar-context");
@@ -32,31 +160,21 @@ const tui: TuiPlugin = async (api, options) => {
32
160
  await api.plugins.activate("internal:sidebar-lsp");
33
161
  await api.plugins.activate("internal:sidebar-todo");
34
162
  await api.plugins.activate("internal:sidebar-files");
35
- } catch (e) {
36
- // Silent fail
37
- }
163
+ } catch (e) {}
38
164
  };
39
165
  enableInternal();
40
166
 
41
- // Resolver context window real del modelo activo
42
- const getContextLimit = (): number => {
43
- try {
44
- const modelStr = api.state.config?.model ?? "";
45
- const [providerID, modelID] = modelStr.split("/");
46
- const provider = api.state.provider.find((p) => p.id === providerID);
47
- const model = provider?.models?.[modelID];
48
- if (model?.limit?.context) return model.limit.context;
49
- } catch (_) {}
50
- return 1_000_000;
51
- };
52
-
53
- // Slot registration
167
+ // ─── Registro de Slots de la UI ──────────────────────────────────────────
168
+ /**
169
+ * Define dónde y cómo se renderizan los componentes del plugin en la interfaz.
170
+ */
54
171
  api.slots.register({
55
172
  slots: {
173
+ // Logo principal en la pantalla de bienvenida
56
174
  home_logo(ctx) {
57
175
  return <HomeLogo theme={ctx.theme.current} />;
58
176
  },
59
-
177
+ // Barra de estado/detección en la parte inferior de la home
60
178
  home_bottom(ctx) {
61
179
  return (
62
180
  <DetectedEnv
@@ -66,26 +184,23 @@ const tui: TuiPlugin = async (api, options) => {
66
184
  />
67
185
  );
68
186
  },
69
-
187
+ // Contenido personalizado para la barra lateral (Sidebar)
70
188
  sidebar_content(ctx, value) {
71
189
  const sessionID = value?.session_id;
72
-
73
- const branch = api.state.vcs?.branch;
74
- const contextLimit = getContextLimit();
75
-
76
190
  return (
77
191
  <SidebarArch
78
192
  theme={ctx.theme.current}
193
+ selectedTheme={api.theme.selected}
79
194
  config={boot}
80
- branch={branch}
195
+ branch={api.state.vcs?.branch}
81
196
  getMessages={() =>
82
197
  sessionID ? api.state.session.messages(sessionID) : []
83
198
  }
84
- contextLimit={contextLimit}
199
+ contextLimit={1000000}
85
200
  />
86
201
  );
87
202
  },
88
-
203
+ // Personalización del prompt en la sesión de chat
89
204
  session_prompt_right(ctx, value) {
90
205
  const t = ctx.theme.current;
91
206
  return (
@@ -100,9 +215,5 @@ const tui: TuiPlugin = async (api, options) => {
100
215
  });
101
216
  };
102
217
 
103
- const plugin: TuiPluginModule & { id: string } = {
104
- id,
105
- tui,
106
- };
107
-
218
+ const plugin: TuiPluginModule & { id: string } = { id, tui };
108
219
  export default plugin;