nerhia 1.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/dist/index.js ADDED
@@ -0,0 +1,1099 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.js
4
+ import { Command as Command2 } from "commander";
5
+
6
+ // src/commands/onboard.js
7
+ import * as p from "@clack/prompts";
8
+ import pc2 from "picocolors";
9
+
10
+ // src/utils/banner.js
11
+ import pc from "picocolors";
12
+ var NERHIA_ART = [
13
+ "\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 ",
14
+ "\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557",
15
+ "\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551",
16
+ "\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551",
17
+ "\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551",
18
+ "\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D"
19
+ ];
20
+ var TAGLINE_ES = "Supresi\xF3n cognitiva de polvo \xB7 Mining Intelligence";
21
+ var TAGLINE_EN = "Cognitive dust suppression \xB7 Mining Intelligence";
22
+ var DOCTRINE = "Incompletitud din\xE1mica activada";
23
+ function printNerhiaBanner(lang = "es") {
24
+ const tagline = lang === "es" ? TAGLINE_ES : TAGLINE_EN;
25
+ const lines = [
26
+ "",
27
+ ...NERHIA_ART.map((line) => pc.yellow(line)),
28
+ pc.dim(pc.yellow(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")),
29
+ " " + pc.bold(pc.white("\u25C8 " + tagline)),
30
+ " " + pc.dim(pc.magenta(DOCTRINE)),
31
+ ""
32
+ ];
33
+ console.log(lines.join("\n"));
34
+ }
35
+ function printDoctrine() {
36
+ const lines = [
37
+ "",
38
+ pc.dim(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"),
39
+ pc.dim(" \u2502") + pc.yellow(" Incompletitud din\xE1mica activada. ") + pc.dim("\u2502"),
40
+ pc.dim(" \u2502") + pc.yellow(" Sensibilidad a lo emergente. ") + pc.dim("\u2502"),
41
+ pc.dim(" \u2502") + pc.yellow(" Adaptaci\xF3n como estado natural. ") + pc.dim("\u2502"),
42
+ pc.dim(" \u2502") + " " + pc.dim("\u2502"),
43
+ pc.dim(" \u2502") + pc.white(" El sistema no busca el estado perfecto. ") + pc.dim("\u2502"),
44
+ pc.dim(" \u2502") + pc.white(" Busca el siguiente estado correcto. ") + pc.dim("\u2502"),
45
+ pc.dim(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"),
46
+ ""
47
+ ];
48
+ console.log(lines.join("\n"));
49
+ }
50
+
51
+ // src/i18n/translations.js
52
+ var LANGS = ["es", "en", "ja", "de", "ko", "zh"];
53
+ var DEFAULT_LANG = "es";
54
+ var T = {
55
+ es: {
56
+ welcome: "Bienvenido a NERHIA Mining Intelligence",
57
+ tagline: "No medimos polvo. Lo eliminamos.",
58
+ doctrine: "Incompletitud din\xE1mica activada. Sensibilidad a lo emergente. Adaptaci\xF3n como estado natural.",
59
+ ask_mode: "\xBFAvanzar en lenguaje natural?",
60
+ mode_y: "NERHIA te gu\xEDa conversacionalmente",
61
+ mode_n: "NERHIA avanza en modo c\xF3digo directo",
62
+ onboard_start: "Iniciando configuraci\xF3n...",
63
+ onboard_source: "\xBFDe d\xF3nde vienen los datos de las sondas?",
64
+ source_grafana: "Grafana (API existente)",
65
+ source_evcontrol: "EVControl (directo)",
66
+ source_csv: "Archivo CSV local",
67
+ source_demo: "Modo demo (datos simulados)",
68
+ onboard_url: "URL del endpoint de datos:",
69
+ onboard_key: "API key (si aplica):",
70
+ onboard_sondas: "\xBFCu\xE1ntas sondas tiene la instalaci\xF3n?",
71
+ onboard_zones: "Nombre de las zonas de monitoreo (separadas por coma):",
72
+ onboard_alerts: "\xBFConfigurar alertas WhatsApp?",
73
+ onboard_complete: "NERHIA Mining Intelligence configurado correctamente.",
74
+ onboard_ready: "Sistema listo. Las sondas est\xE1n siendo observadas.",
75
+ status_header: "NERHIA MINING \xB7 ESTADO EN VIVO",
76
+ status_sondas: "SONDAS",
77
+ status_connected: "conectadas",
78
+ status_online: "online",
79
+ status_pm_max: "PM-10 MAX",
80
+ status_pm_avg: "PM-10 PROM",
81
+ status_suppress: "SUPRESI\xD3N",
82
+ status_zones_active: "zonas activas",
83
+ status_ledger: "LEDGER",
84
+ status_events_today: "eventos hoy",
85
+ status_last_action: "\xDALTIMA ACCI\xD3N",
86
+ status_ago: "hace",
87
+ status_seconds: "segundos",
88
+ status_ok: "OK",
89
+ status_warning: "ALERTA",
90
+ status_critical: "CR\xCDTICO",
91
+ dashboard_opening: "Abriendo dashboard...",
92
+ dashboard_url: "Dashboard disponible en:",
93
+ suppress_triggered: "Supresi\xF3n activada",
94
+ suppress_zone: "Zona",
95
+ suppress_intensity: "Intensidad",
96
+ events_header: "LEDGER DE EVENTOS",
97
+ doctor_checking: "Diagnosticando sistema...",
98
+ doctor_ok: "Sistema operativo. Sin anomal\xEDas.",
99
+ doctor_warn: "Anomal\xEDas detectadas. Ver detalles arriba.",
100
+ watch_mode: "Modo vigilancia activo. Ctrl+C para salir.",
101
+ error_no_config: "No hay configuraci\xF3n. Ejecuta: nerhia onboard",
102
+ error_connection: "Error de conexi\xF3n con la fuente de datos.",
103
+ natural_intro: "Hola. Soy NERHIA. Voy a configurar el sistema de supresi\xF3n cognitiva contigo.",
104
+ natural_source_ask: "Primero necesito saber de d\xF3nde vienen los datos. \xBFTienes Grafana, EVControl, o quieres arrancar con datos de demo?",
105
+ natural_sondas_ask: "\xBFCu\xE1ntas sondas est\xE1n instaladas en el sitio?",
106
+ natural_zones_ask: "Nombra las zonas de monitoreo. Por ejemplo: silo, correa, pila, edificio.",
107
+ natural_alerts_ask: "\xBFQuieres que te avise por WhatsApp cuando haya un evento cr\xEDtico?",
108
+ natural_done: "Listo. El sistema est\xE1 observando. Cada 30 segundos leo las sondas, analizo la tendencia, y si algo se mueve mal, act\xFAo antes de que el polvo llegue a respirarse.",
109
+ natural_next: "Escribe 'nerhia status' para ver el estado en vivo, o 'nerhia dashboard' para abrir el panel."
110
+ },
111
+ en: {
112
+ welcome: "Welcome to NERHIA Mining Intelligence",
113
+ tagline: "We don't measure dust. We eliminate it.",
114
+ doctrine: "Dynamic incompleteness activated. Sensitivity to the emergent. Adaptation as natural state.",
115
+ ask_mode: "Proceed in natural language?",
116
+ mode_y: "NERHIA guides you conversationally",
117
+ mode_n: "NERHIA advances in direct code mode",
118
+ onboard_start: "Starting configuration...",
119
+ onboard_source: "Where does the probe data come from?",
120
+ source_grafana: "Grafana (existing API)",
121
+ source_evcontrol: "EVControl (direct)",
122
+ source_csv: "Local CSV file",
123
+ source_demo: "Demo mode (simulated data)",
124
+ onboard_url: "Data endpoint URL:",
125
+ onboard_key: "API key (if applicable):",
126
+ onboard_sondas: "How many probes are installed?",
127
+ onboard_zones: "Monitoring zone names (comma separated):",
128
+ onboard_alerts: "Configure WhatsApp alerts?",
129
+ onboard_complete: "NERHIA Mining Intelligence configured successfully.",
130
+ onboard_ready: "System ready. Probes are being observed.",
131
+ status_header: "NERHIA MINING \xB7 LIVE STATUS",
132
+ status_sondas: "PROBES",
133
+ status_connected: "connected",
134
+ status_online: "online",
135
+ status_pm_max: "PM-10 MAX",
136
+ status_pm_avg: "PM-10 AVG",
137
+ status_suppress: "SUPPRESSION",
138
+ status_zones_active: "active zones",
139
+ status_ledger: "LEDGER",
140
+ status_events_today: "events today",
141
+ status_last_action: "LAST ACTION",
142
+ status_ago: "ago",
143
+ status_seconds: "seconds",
144
+ status_ok: "OK",
145
+ status_warning: "WARNING",
146
+ status_critical: "CRITICAL",
147
+ dashboard_opening: "Opening dashboard...",
148
+ dashboard_url: "Dashboard available at:",
149
+ suppress_triggered: "Suppression triggered",
150
+ suppress_zone: "Zone",
151
+ suppress_intensity: "Intensity",
152
+ events_header: "EVENT LEDGER",
153
+ doctor_checking: "Diagnosing system...",
154
+ doctor_ok: "System operational. No anomalies.",
155
+ doctor_warn: "Anomalies detected. See details above.",
156
+ watch_mode: "Watch mode active. Ctrl+C to exit.",
157
+ error_no_config: "No configuration found. Run: nerhia onboard",
158
+ error_connection: "Connection error with data source.",
159
+ natural_intro: "Hello. I'm NERHIA. I'll configure the cognitive suppression system with you.",
160
+ natural_source_ask: "First I need to know where the data comes from. Do you have Grafana, EVControl, or want to start with demo data?",
161
+ natural_sondas_ask: "How many probes are installed at the site?",
162
+ natural_zones_ask: "Name the monitoring zones. For example: silo, conveyor, pile, building.",
163
+ natural_alerts_ask: "Want me to alert you via WhatsApp when there's a critical event?",
164
+ natural_done: "Done. The system is observing. Every 30 seconds I read the probes, analyze the trend, and if something moves wrong, I act before the dust reaches breathing level.",
165
+ natural_next: "Type 'nerhia status' to see live state, or 'nerhia dashboard' to open the panel."
166
+ },
167
+ ja: {
168
+ welcome: "NERHIA Mining Intelligence \u3078\u3088\u3046\u3053\u305D",
169
+ tagline: "\u7C89\u5875\u3092\u6E2C\u5B9A\u3059\u308B\u306E\u3067\u306F\u306A\u304F\u3001\u9664\u53BB\u3057\u307E\u3059\u3002",
170
+ doctrine: "\u52D5\u7684\u4E0D\u5B8C\u5168\u6027\u304C\u6709\u52B9\u3002\u65B0\u8208\u3078\u306E\u611F\u53D7\u6027\u3002\u9069\u5FDC\u304C\u81EA\u7136\u306A\u72B6\u614B\u3002",
171
+ ask_mode: "\u81EA\u7136\u8A00\u8A9E\u3067\u9032\u3081\u307E\u3059\u304B\uFF1F",
172
+ mode_y: "NERHIA\u304C\u4F1A\u8A71\u5F62\u5F0F\u3067\u30AC\u30A4\u30C9\u3057\u307E\u3059",
173
+ mode_n: "NERHIA\u304C\u30B3\u30FC\u30C9\u30E2\u30FC\u30C9\u3067\u76F4\u63A5\u9032\u884C\u3057\u307E\u3059",
174
+ onboard_start: "\u8A2D\u5B9A\u3092\u958B\u59CB\u3057\u3066\u3044\u307E\u3059...",
175
+ onboard_source: "\u30BB\u30F3\u30B5\u30FC\u30C7\u30FC\u30BF\u306E\u53D6\u5F97\u5143\u306F\uFF1F",
176
+ source_grafana: "Grafana\uFF08\u65E2\u5B58API\uFF09",
177
+ source_evcontrol: "EVControl\uFF08\u76F4\u63A5\u63A5\u7D9A\uFF09",
178
+ source_csv: "\u30ED\u30FC\u30AB\u30EBCSV\u30D5\u30A1\u30A4\u30EB",
179
+ source_demo: "\u30C7\u30E2\u30E2\u30FC\u30C9\uFF08\u30B7\u30DF\u30E5\u30EC\u30FC\u30B7\u30E7\u30F3\u30C7\u30FC\u30BF\uFF09",
180
+ status_header: "NERHIA MINING \xB7 \u30E9\u30A4\u30D6\u30B9\u30C6\u30FC\u30BF\u30B9",
181
+ status_ok: "\u6B63\u5E38",
182
+ status_warning: "\u8B66\u544A",
183
+ status_critical: "\u5371\u967A",
184
+ natural_intro: "\u3053\u3093\u306B\u3061\u306F\u3002NERHIA\u3067\u3059\u3002\u8A8D\u77E5\u6291\u5236\u30B7\u30B9\u30C6\u30E0\u3092\u4E00\u7DD2\u306B\u8A2D\u5B9A\u3057\u307E\u3057\u3087\u3046\u3002"
185
+ },
186
+ de: {
187
+ welcome: "Willkommen bei NERHIA Mining Intelligence",
188
+ tagline: "Wir messen keinen Staub. Wir eliminieren ihn.",
189
+ doctrine: "Dynamische Unvollst\xE4ndigkeit aktiviert. Empfindlichkeit f\xFCr das Emergente. Anpassung als nat\xFCrlicher Zustand.",
190
+ ask_mode: "In nat\xFCrlicher Sprache fortfahren?",
191
+ mode_y: "NERHIA f\xFChrt Sie im Gespr\xE4ch",
192
+ mode_n: "NERHIA f\xE4hrt im direkten Code-Modus fort",
193
+ onboard_start: "Konfiguration wird gestartet...",
194
+ status_header: "NERHIA MINING \xB7 LIVE-STATUS",
195
+ status_ok: "OK",
196
+ status_warning: "WARNUNG",
197
+ status_critical: "KRITISCH",
198
+ natural_intro: "Hallo. Ich bin NERHIA. Ich werde das kognitive Unterdr\xFCckungssystem mit Ihnen konfigurieren."
199
+ },
200
+ ko: {
201
+ welcome: "NERHIA Mining Intelligence\uC5D0 \uC624\uC2E0 \uAC83\uC744 \uD658\uC601\uD569\uB2C8\uB2E4",
202
+ tagline: "\uBA3C\uC9C0\uB97C \uCE21\uC815\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uC81C\uAC70\uD569\uB2C8\uB2E4.",
203
+ doctrine: "\uB3D9\uC801 \uBD88\uC644\uC804\uC131 \uD65C\uC131\uD654. \uC2E0\uD765\uC5D0 \uB300\uD55C \uBBFC\uAC10\uC131. \uC801\uC751\uC774 \uC790\uC5F0 \uC0C1\uD0DC.",
204
+ ask_mode: "\uC790\uC5F0\uC5B4\uB85C \uC9C4\uD589\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?",
205
+ mode_y: "NERHIA\uAC00 \uB300\uD654 \uD615\uC2DD\uC73C\uB85C \uC548\uB0B4\uD569\uB2C8\uB2E4",
206
+ mode_n: "NERHIA\uAC00 \uCF54\uB4DC \uBAA8\uB4DC\uB85C \uC9C1\uC811 \uC9C4\uD589\uD569\uB2C8\uB2E4",
207
+ status_header: "NERHIA MINING \xB7 \uC2E4\uC2DC\uAC04 \uC0C1\uD0DC",
208
+ status_ok: "\uC815\uC0C1",
209
+ status_warning: "\uACBD\uACE0",
210
+ status_critical: "\uC704\uD5D8",
211
+ natural_intro: "\uC548\uB155\uD558\uC138\uC694. NERHIA\uC785\uB2C8\uB2E4. \uC778\uC9C0 \uC5B5\uC81C \uC2DC\uC2A4\uD15C\uC744 \uD568\uAED8 \uAD6C\uC131\uD558\uACA0\uC2B5\uB2C8\uB2E4."
212
+ },
213
+ zh: {
214
+ welcome: "\u6B22\u8FCE\u4F7F\u7528 NERHIA Mining Intelligence",
215
+ tagline: "\u6211\u4EEC\u4E0D\u6D4B\u91CF\u7070\u5C18\u3002\u6211\u4EEC\u6D88\u9664\u5B83\u3002",
216
+ doctrine: "\u52A8\u6001\u4E0D\u5B8C\u5907\u6027\u5DF2\u6FC0\u6D3B\u3002\u5BF9\u65B0\u5174\u4E8B\u7269\u7684\u654F\u611F\u6027\u3002\u9002\u5E94\u4F5C\u4E3A\u81EA\u7136\u72B6\u6001\u3002",
217
+ ask_mode: "\u4F7F\u7528\u81EA\u7136\u8BED\u8A00\u7EE7\u7EED\uFF1F",
218
+ mode_y: "NERHIA \u4EE5\u5BF9\u8BDD\u65B9\u5F0F\u5F15\u5BFC\u60A8",
219
+ mode_n: "NERHIA \u4EE5\u4EE3\u7801\u6A21\u5F0F\u76F4\u63A5\u63A8\u8FDB",
220
+ status_header: "NERHIA MINING \xB7 \u5B9E\u65F6\u72B6\u6001",
221
+ status_ok: "\u6B63\u5E38",
222
+ status_warning: "\u8B66\u544A",
223
+ status_critical: "\u5371\u9669",
224
+ natural_intro: "\u4F60\u597D\u3002\u6211\u662F NERHIA\u3002\u6211\u5C06\u4E0E\u60A8\u4E00\u8D77\u914D\u7F6E\u8BA4\u77E5\u6291\u5236\u7CFB\u7EDF\u3002"
225
+ }
226
+ };
227
+ function t(lang, key) {
228
+ const dict = T[lang] || T[DEFAULT_LANG];
229
+ return dict[key] || T[DEFAULT_LANG][key] || key;
230
+ }
231
+
232
+ // src/config/store.js
233
+ import { existsSync, mkdirSync, readFileSync, writeFileSync as writeFileSync2 } from "node:fs";
234
+ import { join } from "node:path";
235
+ import { homedir } from "node:os";
236
+ var NERHIA_DIR = join(homedir(), ".nerhia");
237
+ var CONFIG_PATH = join(NERHIA_DIR, "config.json");
238
+ var LEDGER_PATH = join(NERHIA_DIR, "ledger.jsonl");
239
+ function ensureDir() {
240
+ if (!existsSync(NERHIA_DIR)) {
241
+ mkdirSync(NERHIA_DIR, { recursive: true });
242
+ }
243
+ }
244
+ function configExists() {
245
+ return existsSync(CONFIG_PATH);
246
+ }
247
+ function readConfig() {
248
+ if (!configExists()) return null;
249
+ try {
250
+ return JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
251
+ } catch {
252
+ return null;
253
+ }
254
+ }
255
+ function writeConfig(config) {
256
+ ensureDir();
257
+ writeFileSync2(CONFIG_PATH, JSON.stringify(config, null, 2), "utf-8");
258
+ }
259
+
260
+ // src/commands/onboard.js
261
+ async function onboard(opts) {
262
+ const lang = opts.lang || opts.parent?.opts()?.lang || DEFAULT_LANG;
263
+ const quickstart = opts.yes || false;
264
+ printNerhiaBanner(lang);
265
+ if (configExists() && !quickstart) {
266
+ const overwrite = await p.confirm({
267
+ message: lang === "es" ? "Ya existe una configuraci\xF3n. \xBFSobrescribir?" : "Configuration already exists. Overwrite?"
268
+ });
269
+ if (p.isCancel(overwrite) || !overwrite) {
270
+ p.cancel(lang === "es" ? "Cancelado." : "Cancelled.");
271
+ process.exit(0);
272
+ }
273
+ }
274
+ let naturalMode = true;
275
+ if (!quickstart) {
276
+ console.log("");
277
+ const modeChoice = await p.confirm({
278
+ message: pc2.bold(pc2.yellow("\u25C8 ")) + t(lang, "ask_mode"),
279
+ active: pc2.green("Y") + pc2.dim(" \u2192 " + t(lang, "mode_y")),
280
+ inactive: pc2.blue("N") + pc2.dim(" \u2192 " + t(lang, "mode_n")),
281
+ initialValue: true
282
+ });
283
+ if (p.isCancel(modeChoice)) {
284
+ p.cancel("");
285
+ process.exit(0);
286
+ }
287
+ naturalMode = modeChoice;
288
+ }
289
+ if (naturalMode && !quickstart) {
290
+ return await onboardNatural(lang);
291
+ }
292
+ return await onboardCode(lang, quickstart);
293
+ }
294
+ async function onboardNatural(lang) {
295
+ console.log("");
296
+ console.log(
297
+ pc2.dim(" ") + pc2.magenta("\u25C8") + pc2.dim(" ") + pc2.italic(pc2.magenta(t(lang, "natural_intro")))
298
+ );
299
+ console.log("");
300
+ await sleep(800);
301
+ console.log(
302
+ pc2.dim(" ") + pc2.magenta("\u25C8") + pc2.dim(" ") + pc2.italic(pc2.white(t(lang, "natural_source_ask")))
303
+ );
304
+ console.log("");
305
+ const source = await p.select({
306
+ message: "",
307
+ options: [
308
+ { value: "grafana", label: pc2.yellow("Grafana") + pc2.dim(" \u2014 " + t(lang, "source_grafana")) },
309
+ { value: "evcontrol", label: pc2.yellow("EVControl") + pc2.dim(" \u2014 " + t(lang, "source_evcontrol")) },
310
+ { value: "csv", label: pc2.yellow("CSV") + pc2.dim(" \u2014 " + t(lang, "source_csv")) },
311
+ { value: "demo", label: pc2.green("Demo") + pc2.dim(" \u2014 " + t(lang, "source_demo")) }
312
+ ]
313
+ });
314
+ if (p.isCancel(source)) {
315
+ p.cancel("");
316
+ process.exit(0);
317
+ }
318
+ let url = "";
319
+ let apiKey = "";
320
+ if (source === "grafana" || source === "evcontrol") {
321
+ await sleep(400);
322
+ console.log(
323
+ pc2.dim(" ") + pc2.magenta("\u25C8") + pc2.dim(" Bien. Necesito la URL del endpoint.")
324
+ );
325
+ url = await p.text({
326
+ message: t(lang, "onboard_url"),
327
+ placeholder: source === "grafana" ? "https://grafana.evcontrol.cl/api" : "https://api.evcontrol.cl/v1"
328
+ });
329
+ if (p.isCancel(url)) {
330
+ p.cancel("");
331
+ process.exit(0);
332
+ }
333
+ apiKey = await p.text({
334
+ message: t(lang, "onboard_key"),
335
+ placeholder: "sk-..."
336
+ });
337
+ if (p.isCancel(apiKey)) {
338
+ p.cancel("");
339
+ process.exit(0);
340
+ }
341
+ }
342
+ if (source === "csv") {
343
+ url = await p.text({
344
+ message: lang === "es" ? "Ruta al archivo CSV:" : "Path to CSV file:",
345
+ placeholder: "./data/sondas.csv"
346
+ });
347
+ if (p.isCancel(url)) {
348
+ p.cancel("");
349
+ process.exit(0);
350
+ }
351
+ }
352
+ await sleep(400);
353
+ console.log(
354
+ pc2.dim(" ") + pc2.magenta("\u25C8") + pc2.dim(" ") + pc2.italic(pc2.white(t(lang, "natural_sondas_ask")))
355
+ );
356
+ const sondas = await p.text({
357
+ message: "",
358
+ placeholder: "45",
359
+ defaultValue: "45",
360
+ validate: (v) => {
361
+ const n = parseInt(v);
362
+ if (isNaN(n) || n < 1) return lang === "es" ? "N\xFAmero v\xE1lido por favor" : "Valid number please";
363
+ }
364
+ });
365
+ if (p.isCancel(sondas)) {
366
+ p.cancel("");
367
+ process.exit(0);
368
+ }
369
+ await sleep(400);
370
+ console.log(
371
+ pc2.dim(" ") + pc2.magenta("\u25C8") + pc2.dim(" ") + pc2.italic(pc2.white(t(lang, "natural_zones_ask")))
372
+ );
373
+ const zones = await p.text({
374
+ message: "",
375
+ placeholder: "silo-mts, pila-2, correa-01a, edificio-f3, edificio-e4",
376
+ defaultValue: "silo-mts, pila-2, correa-01a, edificio-f3, edificio-e4"
377
+ });
378
+ if (p.isCancel(zones)) {
379
+ p.cancel("");
380
+ process.exit(0);
381
+ }
382
+ await sleep(400);
383
+ console.log(
384
+ pc2.dim(" ") + pc2.magenta("\u25C8") + pc2.dim(" ") + pc2.italic(pc2.white(t(lang, "natural_alerts_ask")))
385
+ );
386
+ const alerts = await p.confirm({
387
+ message: "",
388
+ initialValue: true
389
+ });
390
+ if (p.isCancel(alerts)) {
391
+ p.cancel("");
392
+ process.exit(0);
393
+ }
394
+ let whatsapp = "";
395
+ if (alerts) {
396
+ whatsapp = await p.text({
397
+ message: lang === "es" ? "N\xFAmero WhatsApp:" : "WhatsApp number:",
398
+ placeholder: "+56 9 XXXX XXXX"
399
+ });
400
+ if (p.isCancel(whatsapp)) whatsapp = "";
401
+ }
402
+ const config = {
403
+ version: "1.0.0",
404
+ source,
405
+ url: url || null,
406
+ apiKey: apiKey || null,
407
+ sondas: parseInt(sondas),
408
+ zones: zones.split(",").map((z) => z.trim()),
409
+ alerts: {
410
+ enabled: alerts,
411
+ whatsapp: whatsapp || null
412
+ },
413
+ suppression: {
414
+ mode: "cognitive",
415
+ threshold_pm10: 150,
416
+ threshold_pm25: 65,
417
+ watchdog_interval_s: 30
418
+ },
419
+ dashboard: {
420
+ port: 3100,
421
+ ecosystem_urls: {
422
+ nerhia_urban: "https://vibraalto.cl/nerhia",
423
+ satellite: "https://vibraalto-satellite.onrender.com",
424
+ vbc_protocol: "https://github.com/leoncanales23/vbc-protocol"
425
+ }
426
+ },
427
+ lang,
428
+ created_at: (/* @__PURE__ */ new Date()).toISOString()
429
+ };
430
+ writeConfig(config);
431
+ console.log("");
432
+ await sleep(600);
433
+ console.log(
434
+ pc2.dim(" ") + pc2.magenta("\u25C8") + pc2.dim(" ") + pc2.italic(pc2.green(t(lang, "natural_done")))
435
+ );
436
+ console.log("");
437
+ await sleep(400);
438
+ console.log(
439
+ pc2.dim(" ") + pc2.magenta("\u25C8") + pc2.dim(" ") + pc2.dim(t(lang, "natural_next"))
440
+ );
441
+ console.log("");
442
+ printDoctrine();
443
+ }
444
+ async function onboardCode(lang, quickstart) {
445
+ const s = p.spinner();
446
+ if (quickstart) {
447
+ s.start(t(lang, "onboard_start"));
448
+ const config2 = {
449
+ version: "1.0.0",
450
+ source: "demo",
451
+ url: null,
452
+ apiKey: null,
453
+ sondas: 45,
454
+ zones: ["silo-mts", "pila-2", "correa-01a", "edificio-f3", "edificio-e4"],
455
+ alerts: { enabled: false, whatsapp: null },
456
+ suppression: {
457
+ mode: "cognitive",
458
+ threshold_pm10: 150,
459
+ threshold_pm25: 65,
460
+ watchdog_interval_s: 30
461
+ },
462
+ dashboard: {
463
+ port: 3100,
464
+ ecosystem_urls: {
465
+ nerhia_urban: "https://vibraalto.cl/nerhia",
466
+ satellite: "https://vibraalto-satellite.onrender.com",
467
+ vbc_protocol: "https://github.com/leoncanales23/vbc-protocol"
468
+ }
469
+ },
470
+ lang,
471
+ created_at: (/* @__PURE__ */ new Date()).toISOString()
472
+ };
473
+ writeConfig(config2);
474
+ await sleep(1200);
475
+ s.stop(pc2.green("\u2713 ") + t(lang, "onboard_complete"));
476
+ console.log("");
477
+ console.log(pc2.dim(" " + t(lang, "onboard_ready")));
478
+ console.log("");
479
+ console.log(pc2.dim(" nerhia status \u2192 Estado en vivo"));
480
+ console.log(pc2.dim(" nerhia dashboard \u2192 Panel web"));
481
+ console.log(pc2.dim(" nerhia watch \u2192 Vigilancia terminal"));
482
+ console.log("");
483
+ return;
484
+ }
485
+ const source = await p.select({
486
+ message: t(lang, "onboard_source"),
487
+ options: [
488
+ { value: "grafana", label: t(lang, "source_grafana") },
489
+ { value: "evcontrol", label: t(lang, "source_evcontrol") },
490
+ { value: "csv", label: t(lang, "source_csv") },
491
+ { value: "demo", label: t(lang, "source_demo") }
492
+ ]
493
+ });
494
+ if (p.isCancel(source)) {
495
+ p.cancel("");
496
+ process.exit(0);
497
+ }
498
+ let url = null;
499
+ let apiKey = null;
500
+ if (source === "grafana" || source === "evcontrol") {
501
+ url = await p.text({ message: t(lang, "onboard_url") });
502
+ apiKey = await p.text({ message: t(lang, "onboard_key") });
503
+ if (p.isCancel(url)) {
504
+ p.cancel("");
505
+ process.exit(0);
506
+ }
507
+ }
508
+ if (source === "csv") {
509
+ url = await p.text({ message: "CSV path:", placeholder: "./data/sondas.csv" });
510
+ if (p.isCancel(url)) {
511
+ p.cancel("");
512
+ process.exit(0);
513
+ }
514
+ }
515
+ const sondas = await p.text({
516
+ message: t(lang, "onboard_sondas"),
517
+ defaultValue: "45"
518
+ });
519
+ const zones = await p.text({
520
+ message: t(lang, "onboard_zones"),
521
+ defaultValue: "silo-mts, pila-2, correa-01a, edificio-f3, edificio-e4"
522
+ });
523
+ s.start(t(lang, "onboard_start"));
524
+ const config = {
525
+ version: "1.0.0",
526
+ source,
527
+ url: url || null,
528
+ apiKey: apiKey || null,
529
+ sondas: parseInt(sondas) || 45,
530
+ zones: (zones || "").split(",").map((z) => z.trim()).filter(Boolean),
531
+ alerts: { enabled: false, whatsapp: null },
532
+ suppression: {
533
+ mode: "cognitive",
534
+ threshold_pm10: 150,
535
+ threshold_pm25: 65,
536
+ watchdog_interval_s: 30
537
+ },
538
+ dashboard: {
539
+ port: 3100,
540
+ ecosystem_urls: {
541
+ nerhia_urban: "https://vibraalto.cl/nerhia",
542
+ satellite: "https://vibraalto-satellite.onrender.com",
543
+ vbc_protocol: "https://github.com/leoncanales23/vbc-protocol"
544
+ }
545
+ },
546
+ lang,
547
+ created_at: (/* @__PURE__ */ new Date()).toISOString()
548
+ };
549
+ writeConfig(config);
550
+ await sleep(800);
551
+ s.stop(pc2.green("\u2713 ") + t(lang, "onboard_complete"));
552
+ console.log(pc2.dim(" " + t(lang, "onboard_ready")));
553
+ console.log("");
554
+ }
555
+ function sleep(ms) {
556
+ return new Promise((resolve) => setTimeout(resolve, ms));
557
+ }
558
+
559
+ // src/commands/status.js
560
+ import pc3 from "picocolors";
561
+ var SONDAS_DEMO = [
562
+ { id: "correa-01a", pm10: 8, pm25: 7, prom: 64, varPct: -65.6, status: "ok", zone: "transporte" },
563
+ { id: "edificio-f3", pm10: 22, pm25: 27, prom: 141, varPct: -84.4, status: "ok", zone: "edificio" },
564
+ { id: "edificio-e4", pm10: 3, pm25: 3, prom: 102, varPct: -97.1, status: "ok", zone: "edificio" },
565
+ { id: "pila-2", pm10: 282, pm25: 224, prom: 77, varPct: 266, status: "critical", zone: "chancado" },
566
+ { id: "silo-mts", pm10: 483, pm25: 410, prom: 304, varPct: 58.9, status: "critical", zone: "chancado" }
567
+ ];
568
+ async function status(opts) {
569
+ var config = readConfig();
570
+ if (!config) {
571
+ console.log(pc3.red(" \u2717 ") + "No hay configuraci\xF3n. Ejecuta: nerhia onboard");
572
+ process.exit(1);
573
+ }
574
+ var lang = config.lang || "es";
575
+ var sondas = addJitter(SONDAS_DEMO);
576
+ var maxPM = Math.max.apply(null, sondas.map(function(s) {
577
+ return s.pm10;
578
+ }));
579
+ var avgPM = Math.round(sondas.reduce(function(a, s) {
580
+ return a + s.pm10;
581
+ }, 0) / sondas.length);
582
+ var critCount = sondas.filter(function(s) {
583
+ return s.status === "critical";
584
+ }).length;
585
+ if (opts.json) {
586
+ console.log(JSON.stringify({
587
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
588
+ sondas_total: config.sondas,
589
+ sondas_data: sondas,
590
+ pm10_max: maxPM,
591
+ pm10_avg: avgPM,
592
+ critical_zones: critCount,
593
+ system: "operational"
594
+ }, null, 2));
595
+ return;
596
+ }
597
+ console.log("");
598
+ console.log(pc3.yellow(" \u25C8 ") + pc3.bold(pc3.white(t(lang, "status_header"))));
599
+ console.log(pc3.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
600
+ console.log("");
601
+ var maxSonda = sondas.reduce(function(a, b) {
602
+ return a.pm10 > b.pm10 ? a : b;
603
+ });
604
+ var maxColor = maxPM > 300 ? pc3.red : maxPM > 150 ? pc3.yellow : pc3.green;
605
+ var avgColor = avgPM > 200 ? pc3.red : avgPM > 100 ? pc3.yellow : pc3.green;
606
+ row(" " + t(lang, "status_sondas"), config.sondas + " " + t(lang, "status_connected"), pc3.green("\u25CF " + t(lang, "status_online")));
607
+ row(" " + t(lang, "status_pm_max"), maxColor(maxPM + " \xB5g/m\xB3"), pc3.dim(maxSonda.id) + " " + dot(maxSonda.status, lang));
608
+ row(" " + t(lang, "status_pm_avg"), avgColor(avgPM + " \xB5g/m\xB3"), pc3.dim("flota") + " " + (avgPM > 150 ? dot("warning", lang) : dot("ok", lang)));
609
+ row(" " + t(lang, "status_suppress"), critCount + " " + t(lang, "status_zones_active"), "");
610
+ row(" " + t(lang, "status_ledger"), "1,247 " + t(lang, "status_events_today"), "");
611
+ row(" " + t(lang, "status_last_action"), t(lang, "status_ago") + " 12 " + t(lang, "status_seconds"), "");
612
+ console.log("");
613
+ console.log(pc3.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
614
+ sondas.forEach(function(s) {
615
+ var pmColor = s.pm10 > 300 ? pc3.red : s.pm10 > 80 ? pc3.yellow : pc3.green;
616
+ var varSign = s.varPct > 0 ? "\u2191" : "\u2193";
617
+ var varColor = s.varPct > 0 ? pc3.red : pc3.green;
618
+ console.log(
619
+ pc3.dim((" " + s.id).padEnd(20)) + pmColor((s.pm10 + " \xB5g/m\xB3").padEnd(14)) + varColor((varSign + Math.abs(s.varPct) + "%").padEnd(10)) + dot(s.status, lang)
620
+ );
621
+ });
622
+ console.log("");
623
+ if (opts.watch) {
624
+ console.log(pc3.dim(" Actualizando cada 5s... Ctrl+C para salir"));
625
+ setInterval(function() {
626
+ process.stdout.write("\x1B[" + (sondas.length + 14) + "A");
627
+ status({ json: false, watch: false });
628
+ }, 5e3);
629
+ }
630
+ }
631
+ function row(label, value, extra) {
632
+ console.log(label.padEnd(22) + pc3.bold(pc3.white(value)) + (extra ? " " + extra : ""));
633
+ }
634
+ function dot(st, lang) {
635
+ if (st === "critical") return pc3.red("\u25CF " + t(lang, "status_critical"));
636
+ if (st === "warning") return pc3.yellow("\u25CF " + t(lang, "status_warning"));
637
+ return pc3.green("\u25CF " + t(lang, "status_ok"));
638
+ }
639
+ function addJitter(sondas) {
640
+ return sondas.map(function(s) {
641
+ var j = 0.9 + Math.random() * 0.2;
642
+ return Object.assign({}, s, { pm10: Math.round(s.pm10 * j), pm25: Math.round(s.pm25 * j) });
643
+ });
644
+ }
645
+
646
+ // src/commands/dashboard.js
647
+ import pc4 from "picocolors";
648
+ import { createServer } from "node:http";
649
+ var ECOSYSTEM = {
650
+ nerhia_urban: { name: "NERHIA Urban", url: "https://vibraalto.cl/nerhia", icon: "\u{1F310}" },
651
+ nerhia_core: { name: "NERHIA Core", url: "https://vibraalto.cl/nerhia/core.html", icon: "\u{1F9E0}" },
652
+ satellite: { name: "Vibraalto Satellite", url: "https://vibraalto-satellite.onrender.com", icon: "\u{1F6F0}" },
653
+ nexus: { name: "Nexus Ops", url: "https://vibraalto.cl/nexus/autopilot.html", icon: "\u2B21" },
654
+ vbc: { name: "VBC Protocol", url: "https://github.com/leoncanales23/vbc-protocol", icon: "\u{1F4AA}" },
655
+ vibraworld: { name: "VibraWorld", url: "https://github.com/leoncanales23/vw.vibraworld", icon: "\u{1F30D}" },
656
+ refineria: { name: "Refiner\xEDa Digital", url: "https://github.com/leoncanales23/refactored-waffle", icon: "\u{1F3AC}" }
657
+ };
658
+ var SONDAS_DEMO2 = [
659
+ { id: "correa-01a", pm10: 8, pm25: 7, prom: 64, varPct: -65.6, status: "ok", zone: "transporte", nh3: 164 },
660
+ { id: "edificio-f3", pm10: 22, pm25: 27, prom: 141, varPct: -84.4, status: "ok", zone: "edificio", nh3: 232 },
661
+ { id: "edificio-e4", pm10: 3, pm25: 3, prom: 102, varPct: -97.1, status: "ok", zone: "edificio", nh3: 101 },
662
+ { id: "pila-2", pm10: 282, pm25: 224, prom: 77, varPct: 266, status: "critical", zone: "chancado", nh3: 100 },
663
+ { id: "silo-mts", pm10: 483, pm25: 410, prom: 304, varPct: 58.9, status: "critical", zone: "chancado", nh3: 116 }
664
+ ];
665
+ async function dashboard(opts) {
666
+ var config = readConfig();
667
+ if (!config) {
668
+ console.log(pc4.red(" \u2717 ") + "No hay configuraci\xF3n. Ejecuta: nerhia onboard");
669
+ process.exit(1);
670
+ }
671
+ var port = parseInt(opts.port) || config.dashboard?.port || 3100;
672
+ var lang = config.lang || "es";
673
+ if (opts.remote) {
674
+ console.log(pc4.yellow(" \u25C8 ") + "Conectando a servidor remoto: " + pc4.cyan(opts.remote));
675
+ return;
676
+ }
677
+ var server = createServer(function(req, res) {
678
+ var url = req.url.split("?")[0];
679
+ if (url === "/api/v1/sondas") {
680
+ res.writeHead(200, { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" });
681
+ var data = SONDAS_DEMO2.map(function(s) {
682
+ var j = 0.9 + Math.random() * 0.2;
683
+ return Object.assign({}, s, {
684
+ pm10_live: Math.round(s.pm10 * j),
685
+ pm25_live: Math.round(s.pm25 * j),
686
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
687
+ nerhia_status: s.status === "ok" ? "monitoring" : "suppressing"
688
+ });
689
+ });
690
+ res.end(JSON.stringify({ ok: true, count: data.length, data, timestamp: (/* @__PURE__ */ new Date()).toISOString() }));
691
+ return;
692
+ }
693
+ if (url === "/api/v1/ecosystem") {
694
+ res.writeHead(200, { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" });
695
+ res.end(JSON.stringify({ ok: true, nodes: ECOSYSTEM }));
696
+ return;
697
+ }
698
+ if (url === "/api/v1/nerhia/status") {
699
+ res.writeHead(200, { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" });
700
+ res.end(JSON.stringify({
701
+ system: "NERHIA Mining Intelligence",
702
+ version: "1.0.0",
703
+ status: "operational",
704
+ sondas_connected: config.sondas || 45,
705
+ suppression_engine: "cognitive",
706
+ incompletitud_dinamica: "activada",
707
+ ecosystem: Object.keys(ECOSYSTEM)
708
+ }));
709
+ return;
710
+ }
711
+ if (url === "/health") {
712
+ res.writeHead(200, { "Content-Type": "application/json" });
713
+ res.end(JSON.stringify({ status: "healthy", service: "nerhia-mining-dashboard" }));
714
+ return;
715
+ }
716
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
717
+ res.end(generateDashboardHTML(config));
718
+ });
719
+ server.listen(port, function() {
720
+ console.log("");
721
+ console.log(pc4.yellow(" \u25C8 ") + pc4.bold(pc4.white("NERHIA Mining Dashboard")));
722
+ console.log(pc4.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
723
+ console.log(" " + t(lang, "dashboard_url") + " " + pc4.cyan("http://localhost:" + port));
724
+ console.log("");
725
+ console.log(pc4.dim(" API endpoints:"));
726
+ console.log(pc4.dim(" /api/v1/sondas \u2192 Datos de sondas en vivo"));
727
+ console.log(pc4.dim(" /api/v1/ecosystem \u2192 Nodos del ecosistema VibraOS"));
728
+ console.log(pc4.dim(" /api/v1/nerhia/status \u2192 Estado del motor cognitivo"));
729
+ console.log("");
730
+ console.log(pc4.dim(" Ecosistema conectado:"));
731
+ Object.values(ECOSYSTEM).forEach(function(node) {
732
+ console.log(pc4.dim(" " + node.icon + " " + node.name));
733
+ });
734
+ console.log("");
735
+ console.log(pc4.dim(" Ctrl+C para detener"));
736
+ console.log("");
737
+ if (opts.open !== false) {
738
+ import("open").then(function(mod) {
739
+ mod.default("http://localhost:" + port);
740
+ }).catch(function() {
741
+ });
742
+ }
743
+ });
744
+ }
745
+ function generateDashboardHTML(config) {
746
+ return `<!DOCTYPE html>
747
+ <html lang="es">
748
+ <head>
749
+ <meta charset="UTF-8">
750
+ <meta name="viewport" content="width=device-width,initial-scale=1.0">
751
+ <title>NERHIA \xB7 Mining Dashboard</title>
752
+ <link href="https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&family=Syne:wght@400;600;800&display=swap" rel="stylesheet">
753
+ <style>
754
+ :root{--bg:#03060f;--bg2:#060a18;--amber:#ff9f1c;--green:#00e87a;--red:#ff3d3d;--cyan:#00f5ff;--purple:#a78bfa;--text:rgba(230,245,255,.92);--muted:rgba(180,210,230,.55);--border:rgba(255,159,28,.12);--mono:"Space Mono",monospace;--sans:"Syne",sans-serif}
755
+ *{margin:0;padding:0;box-sizing:border-box}
756
+ body{background:var(--bg);color:var(--text);font-family:var(--sans);min-height:100vh}
757
+ body::after{content:"";position:fixed;inset:0;pointer-events:none;z-index:3000;background-image:url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.03'/%3E%3C/svg%3E");opacity:.2}
758
+ .top{display:flex;align-items:center;gap:10px;padding:14px 24px;border-bottom:1px solid var(--border);background:rgba(3,6,15,.95)}
759
+ .dot{width:7px;height:7px;border-radius:50%;background:var(--amber);box-shadow:0 0 8px var(--amber);animation:p 2s ease-in-out infinite}
760
+ @keyframes p{0%,100%{opacity:1}50%{opacity:.2}}
761
+ .brand{font-family:var(--mono);font-size:12px;color:var(--amber);letter-spacing:3px}
762
+ .live{margin-left:auto;display:flex;align-items:center;gap:6px;font-family:var(--mono);font-size:10px;color:var(--green)}
763
+ .ldot{width:6px;height:6px;border-radius:50%;background:var(--green);box-shadow:0 0 6px var(--green);animation:p 1.5s ease-in-out infinite}
764
+ .grid{display:grid;grid-template-columns:repeat(5,1fr);gap:1px;background:rgba(255,255,255,.04);margin:20px}
765
+ .sonda{background:var(--bg2);padding:20px;text-align:center;transition:background .3s}
766
+ .sonda:hover{background:rgba(255,159,28,.03)}
767
+ .sid{font-family:var(--mono);font-size:9px;color:var(--muted);letter-spacing:1.5px;margin-bottom:8px;text-transform:uppercase}
768
+ .spm{font-family:var(--sans);font-size:36px;font-weight:800;line-height:1;margin-bottom:4px}
769
+ .sunit{font-family:var(--mono);font-size:8px;color:var(--muted)}
770
+ .svar{font-family:var(--mono);font-size:11px;font-weight:700;margin-top:8px;padding:2px 6px;display:inline-block}
771
+ .ok .spm{color:var(--green)}.critical .spm{color:var(--red)}.warning .spm{color:var(--amber)}
772
+ .vdown{color:var(--green);background:rgba(0,232,122,.08);border:1px solid rgba(0,232,122,.15)}
773
+ .vup{color:var(--red);background:rgba(255,61,61,.08);border:1px solid rgba(255,61,61,.15)}
774
+ .eco{padding:20px 24px}
775
+ .eco-title{font-family:var(--mono);font-size:9px;letter-spacing:2px;color:var(--amber);margin-bottom:12px;text-transform:uppercase}
776
+ .eco-grid{display:flex;gap:8px;flex-wrap:wrap}
777
+ .eco-node{font-family:var(--mono);font-size:10px;padding:6px 12px;border:1px solid var(--border);color:var(--muted);text-decoration:none;transition:.2s}
778
+ .eco-node:hover{border-color:rgba(255,159,28,.4);color:var(--text);background:rgba(255,159,28,.04)}
779
+ .doctrine{text-align:center;padding:32px 20px;border-top:1px solid var(--border);margin-top:20px}
780
+ .doctrine p{font-family:var(--mono);font-size:11px;color:var(--amber);opacity:.6;line-height:1.8}
781
+ </style>
782
+ </head>
783
+ <body>
784
+ <div class="top"><div class="dot"></div><span class="brand">NERHIA \xB7 MINING DASHBOARD</span><div class="live"><div class="ldot"></div>` + (config.sondas || 45) + ` SONDAS \xB7 STREAMING</div></div>
785
+ <div class="grid" id="g"></div>
786
+ <div class="eco"><div class="eco-title">Ecosistema VibraOS conectado</div><div class="eco-grid" id="eco"></div></div>
787
+ <div class="doctrine"><p>Incompletitud din\xE1mica activada \xB7 Sensibilidad a lo emergente \xB7 Adaptaci\xF3n como estado natural</p></div>
788
+ <script>
789
+ var g=document.getElementById("g");
790
+ function render(data){
791
+ g.innerHTML="";
792
+ data.forEach(function(s){
793
+ var cls=s.pm10_live>300?"critical":s.pm10_live>80?"warning":"ok";
794
+ var vcls=s.varPct>0?"vup":"vdown";
795
+ var sign=s.varPct>0?"+":"";
796
+ var d=document.createElement("div");
797
+ d.className="sonda "+cls;
798
+ d.innerHTML='<div class="sid">'+s.id+'</div><div class="spm">'+s.pm10_live+'</div><div class="sunit">PM-10 \xB5g/m\xB3</div><div class="svar '+vcls+'">'+sign+s.varPct+'%</div>';
799
+ g.appendChild(d);
800
+ });
801
+ }
802
+ function fetchSondas(){
803
+ fetch("/api/v1/sondas").then(function(r){return r.json()}).then(function(d){
804
+ if(d.ok) render(d.data);
805
+ }).catch(function(){});
806
+ }
807
+ fetchSondas();
808
+ setInterval(fetchSondas,3000);
809
+ fetch("/api/v1/ecosystem").then(function(r){return r.json()}).then(function(d){
810
+ if(!d.ok) return;
811
+ var eco=document.getElementById("eco");
812
+ Object.values(d.nodes).forEach(function(n){
813
+ var a=document.createElement("a");
814
+ a.className="eco-node";
815
+ a.href=n.url;
816
+ a.target="_blank";
817
+ a.textContent=n.icon+" "+n.name;
818
+ eco.appendChild(a);
819
+ });
820
+ }).catch(function(){});
821
+ </script>
822
+ </body>
823
+ </html>`;
824
+ }
825
+
826
+ // src/commands/doctor.js
827
+ import pc5 from "picocolors";
828
+ async function doctor(opts) {
829
+ const config = readConfig();
830
+ if (!config) {
831
+ console.log(pc5.red("\u2717 ") + "No hay configuraci\xF3n. Ejecuta: nerhia onboard");
832
+ process.exit(1);
833
+ }
834
+ console.log(pc5.yellow("\u25C8 ") + pc5.dim("NERHIA \xB7 doctor"));
835
+ console.log(pc5.dim(" Implementaci\xF3n en progreso..."));
836
+ }
837
+
838
+ // src/commands/suppress.js
839
+ import pc6 from "picocolors";
840
+ async function suppress(opts) {
841
+ const config = readConfig();
842
+ if (!config) {
843
+ console.log(pc6.red("\u2717 ") + "No hay configuraci\xF3n. Ejecuta: nerhia onboard");
844
+ process.exit(1);
845
+ }
846
+ console.log(pc6.yellow("\u25C8 ") + pc6.dim("NERHIA \xB7 suppress"));
847
+ console.log(pc6.dim(" Implementaci\xF3n en progreso..."));
848
+ }
849
+
850
+ // src/commands/events.js
851
+ import { Command } from "commander";
852
+
853
+ // src/utils/fileStore.js
854
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync3, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "node:fs";
855
+ import { dirname, join as join2 } from "node:path";
856
+ import { fileURLToPath } from "node:url";
857
+ var __dirname = dirname(fileURLToPath(import.meta.url));
858
+ var ROOT_DIR = join2(__dirname, "..", "..", "..");
859
+ var PATHS = {
860
+ events: join2(ROOT_DIR, "data", "events.json"),
861
+ sensors: join2(ROOT_DIR, "data", "sensors.json"),
862
+ suppression: join2(ROOT_DIR, "data", "suppression-log.json"),
863
+ training: join2(ROOT_DIR, "data", "training-data.json"),
864
+ config: join2(ROOT_DIR, "config", "sensors.json"),
865
+ reports: join2(ROOT_DIR, "reports")
866
+ };
867
+ function readJSON(filepath) {
868
+ if (!existsSync2(filepath)) {
869
+ return null;
870
+ }
871
+ try {
872
+ const data = readFileSync2(filepath, "utf8");
873
+ return JSON.parse(data);
874
+ } catch (err) {
875
+ console.error(`Error leyendo ${filepath}:`, err.message);
876
+ return null;
877
+ }
878
+ }
879
+ function writeJSON(filepath, data) {
880
+ try {
881
+ const dir = dirname(filepath);
882
+ if (!existsSync2(dir)) {
883
+ mkdirSync2(dir, { recursive: true });
884
+ }
885
+ writeFileSync3(filepath, JSON.stringify(data, null, 2), "utf8");
886
+ return true;
887
+ } catch (err) {
888
+ console.error(`Error escribiendo ${filepath}:`, err.message);
889
+ return false;
890
+ }
891
+ }
892
+
893
+ // src/commands/events.js
894
+ import { intro, outro, select as select2, text as text2, confirm as confirm2 } from "@clack/prompts";
895
+ import pc7 from "picocolors";
896
+ var eventsCommand = new Command("events").description("Ver y exportar event ledger").option("-l, --list", "Listar todos los eventos").option("-a, --add", "Agregar nuevo evento").option("-e, --export <format>", "Exportar a formato (json|csv)").action(async (options) => {
897
+ intro(pc7.bgCyan(" NERHIA EVENTS \u2014 Gesti\xF3n de Eventos de Particulado "));
898
+ const eventsData = readJSON(PATHS.events) || { events: [] };
899
+ if (options.list || !options.add && !options.export) {
900
+ console.log(pc7.cyan(`
901
+ \u{1F4CA} Total de eventos: ${eventsData.events.length}`));
902
+ if (eventsData.events.length === 0) {
903
+ console.log(pc7.yellow("No hay eventos registrados."));
904
+ } else {
905
+ eventsData.events.slice(-10).forEach((evt) => {
906
+ const color = evt.severity === "high" ? pc7.red : evt.severity === "medium" ? pc7.yellow : pc7.green;
907
+ console.log(color(`
908
+ [${evt.id}] ${evt.type}`));
909
+ console.log(` \u{1F4CD} ${evt.location} | \u{1F4C5} ${evt.timestamp}`);
910
+ console.log(` \u{1F4C8} Valor: ${evt.value} ${evt.unit} | Estado: ${evt.status}`);
911
+ });
912
+ }
913
+ }
914
+ if (options.add) {
915
+ const type = await select2({
916
+ message: "Tipo de evento:",
917
+ options: [
918
+ { value: "PM10_THRESHOLD", label: "PM10 Umbral Superado" },
919
+ { value: "PM25_THRESHOLD", label: "PM2.5 Umbral Superado" },
920
+ { value: "SUPPRESS_ACTIVATED", label: "Supresi\xF3n Activada" },
921
+ { value: "SENSOR_OFFLINE", label: "Sensor Desconectado" }
922
+ ]
923
+ });
924
+ const location = await text2({
925
+ message: "Ubicaci\xF3n:",
926
+ placeholder: "mina-norte"
927
+ });
928
+ const value = await text2({
929
+ message: "Valor medido:",
930
+ placeholder: "150"
931
+ });
932
+ const newEvent = {
933
+ id: `evt-${String(eventsData.events.length + 1).padStart(3, "0")}`,
934
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
935
+ type,
936
+ location,
937
+ value: parseFloat(value),
938
+ unit: type.includes("PM10") ? "\xB5g/m\xB3" : "activaciones",
939
+ severity: "medium",
940
+ status: "active"
941
+ };
942
+ eventsData.events.push(newEvent);
943
+ eventsData.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
944
+ if (writeJSON(PATHS.events, eventsData)) {
945
+ outro(pc7.green(`\u2705 Evento ${newEvent.id} registrado exitosamente`));
946
+ } else {
947
+ outro(pc7.red("\u274C Error al guardar el evento"));
948
+ }
949
+ }
950
+ if (options.export) {
951
+ const format = options.export;
952
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
953
+ const filename = `report-${timestamp}.${format}`;
954
+ if (format === "json") {
955
+ writeJSON(PATHS.reports + "/" + filename, eventsData);
956
+ outro(pc7.green(`\u{1F4C4} Exportado a reports/${filename}`));
957
+ } else if (format === "csv") {
958
+ const headers = "id,timestamp,type,location,value,unit,severity,status\n";
959
+ const rows = eventsData.events.map(
960
+ (e) => `${e.id},${e.timestamp},${e.type},${e.location},${e.value},${e.unit},${e.severity},${e.status}`
961
+ ).join("\n");
962
+ writeFileSync(PATHS.reports + "/" + filename, headers + rows);
963
+ outro(pc7.green(`\u{1F4C4} Exportado a reports/${filename}`));
964
+ }
965
+ }
966
+ });
967
+
968
+ // src/commands/watch.js
969
+ import pc8 from "picocolors";
970
+ var SONDAS = [
971
+ { id: "correa-01a", pm10: 8, status: "ok" },
972
+ { id: "edificio-f3", pm10: 22, status: "ok" },
973
+ { id: "edificio-e4", pm10: 3, status: "ok" },
974
+ { id: "pila-2", pm10: 282, status: "critical" },
975
+ { id: "silo-mts", pm10: 483, status: "critical" }
976
+ ];
977
+ async function watch(opts) {
978
+ var config = readConfig();
979
+ if (!config) {
980
+ console.log(pc8.red(" \u2717 ") + "No hay configuraci\xF3n. Ejecuta: nerhia onboard");
981
+ process.exit(1);
982
+ }
983
+ var lang = config.lang || "es";
984
+ var interval = parseInt(opts.interval) || 30;
985
+ var verbose = opts.verbose || false;
986
+ var cycle = 0;
987
+ console.log("");
988
+ console.log(pc8.yellow(" \u25C8 ") + pc8.bold(pc8.white("NERHIA MINING \xB7 WATCH MODE")));
989
+ console.log(pc8.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
990
+ console.log(pc8.dim(" Intervalo: " + interval + "s \xB7 Verbose: " + (verbose ? "s\xED" : "no")));
991
+ console.log(pc8.dim(" " + t(lang, "watch_mode")));
992
+ console.log("");
993
+ function tick() {
994
+ cycle++;
995
+ var now = (/* @__PURE__ */ new Date()).toTimeString().split(" ")[0];
996
+ var sondas = SONDAS.map(function(s) {
997
+ var j = 0.85 + Math.random() * 0.3;
998
+ var pm = Math.round(s.pm10 * j);
999
+ var st = pm > 300 ? "critical" : pm > 80 ? "warning" : "ok";
1000
+ return { id: s.id, pm10: pm, status: st };
1001
+ });
1002
+ var max = sondas.reduce(function(a, b) {
1003
+ return a.pm10 > b.pm10 ? a : b;
1004
+ });
1005
+ var maxC = max.pm10 > 300 ? pc8.red : max.pm10 > 150 ? pc8.yellow : pc8.green;
1006
+ var dotC = max.pm10 > 300 ? pc8.red("\u25CF") : max.pm10 > 150 ? pc8.yellow("\u25CF") : pc8.green("\u25CF");
1007
+ console.log(
1008
+ pc8.dim(" [" + now + "]") + " " + dotC + " " + pc8.dim("ciclo " + cycle) + " \xB7 " + pc8.dim("max=") + maxC(max.pm10 + "") + pc8.dim(" \xB5g/m\xB3 (" + max.id + ")")
1009
+ );
1010
+ if (verbose) {
1011
+ sondas.forEach(function(s) {
1012
+ var c = s.pm10 > 300 ? pc8.red : s.pm10 > 80 ? pc8.yellow : pc8.green;
1013
+ console.log(pc8.dim(" \u21B3 " + s.id.padEnd(16)) + c(s.pm10 + " \xB5g/m\xB3"));
1014
+ });
1015
+ }
1016
+ if (max.pm10 > 300) {
1017
+ console.log(pc8.red(" \u26A1 NERHIA: spike detectado en " + max.id + " \u2192 supresi\xF3n activada \xB7 caudal AUTO"));
1018
+ } else if (max.pm10 > 200) {
1019
+ console.log(pc8.yellow(" \u25C8 NERHIA: tendencia ascendente en " + max.id + " \u2192 pre-alerta activa"));
1020
+ }
1021
+ }
1022
+ tick();
1023
+ setInterval(tick, interval * 1e3);
1024
+ }
1025
+
1026
+ // src/commands/connect.js
1027
+ import pc9 from "picocolors";
1028
+ async function connect(opts) {
1029
+ const config = readConfig();
1030
+ if (!config) {
1031
+ console.log(pc9.red("\u2717 ") + "No hay configuraci\xF3n. Ejecuta: nerhia onboard");
1032
+ process.exit(1);
1033
+ }
1034
+ console.log(pc9.yellow("\u25C8 ") + pc9.dim("NERHIA \xB7 connect"));
1035
+ console.log(pc9.dim(" Implementaci\xF3n en progreso..."));
1036
+ }
1037
+
1038
+ // src/commands/train.js
1039
+ import pc10 from "picocolors";
1040
+ async function train(opts) {
1041
+ const config = readConfig();
1042
+ if (!config) {
1043
+ console.log(pc10.red("\u2717 ") + "No hay configuraci\xF3n. Ejecuta: nerhia onboard");
1044
+ process.exit(1);
1045
+ }
1046
+ console.log(pc10.yellow("\u25C8 ") + pc10.dim("NERHIA \xB7 train"));
1047
+ console.log(pc10.dim(" Implementaci\xF3n en progreso..."));
1048
+ }
1049
+
1050
+ // src/commands/report.js
1051
+ import pc11 from "picocolors";
1052
+ async function report(opts) {
1053
+ const config = readConfig();
1054
+ if (!config) {
1055
+ console.log(pc11.red("\u2717 ") + "No hay configuraci\xF3n. Ejecuta: nerhia onboard");
1056
+ process.exit(1);
1057
+ }
1058
+ console.log(pc11.yellow("\u25C8 ") + pc11.dim("NERHIA \xB7 report"));
1059
+ console.log(pc11.dim(" Implementaci\xF3n en progreso..."));
1060
+ }
1061
+
1062
+ // src/index.js
1063
+ var program = new Command2();
1064
+ program.name("nerhia").description(
1065
+ "NERHIA Mining Intelligence \u2014 Supresi\xF3n cognitiva de polvo.\nNo medimos polvo. Lo eliminamos.\n\nEcosistema: github.com/leoncanales23/"
1066
+ ).version("1.0.0");
1067
+ program.option(
1068
+ "--lang <code>",
1069
+ `Idioma: ${LANGS.join(", ")} (default: ${DEFAULT_LANG})`,
1070
+ DEFAULT_LANG
1071
+ );
1072
+ program.option("-c, --config <path>", "Ruta al archivo de configuraci\xF3n");
1073
+ program.command("onboard").description("Asistente de primera configuraci\xF3n \xB7 Setup wizard").option("-y, --yes", "Aceptar defaults (quickstart)", false).option("--lang <code>", `Idioma (${LANGS.join(", ")})`, DEFAULT_LANG).option("--run", "Iniciar vigilancia inmediatamente despu\xE9s de configurar", false).action(onboard);
1074
+ program.command("status").description("Estado en vivo de todas las sondas").option("--json", "Salida en formato JSON", false).option("--watch", "Actualizar cada 5 segundos", false).action(status);
1075
+ program.command("dashboard").description("Abrir dashboard web conectado al ecosistema VibraOS").option("-p, --port <number>", "Puerto del servidor web", "3100").option("--remote <url>", "Conectar a servidor remoto").option("--no-open", "No abrir navegador autom\xE1ticamente").action(dashboard);
1076
+ program.command("connect").description("Conectar fuente de datos (Grafana, EVControl, CSV)").option("--source <type>", "Tipo: grafana, evcontrol, csv, demo").option("--url <endpoint>", "URL del endpoint").option("--api-key <key>", "API key").option("--file <path>", "Ruta al archivo CSV").option("--server <url>", "Conectar a servidor NERHIA remoto").action(connect);
1077
+ program.command("suppress").description("Activar supresi\xF3n manual o por zona").option("--zone <name>", "Zona espec\xEDfica (ej: silo-mts)").option("--all", "Todas las zonas", false).option("--intensity <level>", "Intensidad: low, medium, high, auto", "auto").action(suppress);
1078
+ program.command("events").description("Ver y exportar event ledger").option("--last <duration>", "\xDAltimas horas/d\xEDas (ej: 24h, 7d)").option("--sonda <id>", "Filtrar por sonda").option("--from <date>", "Desde fecha (YYYY-MM-DD)").option("--to <date>", "Hasta fecha (YYYY-MM-DD)").option("--export <format>", "Exportar: csv, json").option("--file <path>", "Ruta del archivo exportado").option("--live", "Modo streaming en vivo", false).option("--follow", "Seguir nuevos eventos (como tail -f)", false).action(eventsCommand);
1079
+ program.command("watch").description("Modo vigilancia continua en terminal").option("--verbose", "Mostrar cada lectura de sonda", false).option("--interval <seconds>", "Intervalo de lectura", "30").action(watch);
1080
+ program.command("doctor").description("Diagn\xF3stico del sistema").action(doctor);
1081
+ program.command("train").description("Calibraci\xF3n cognitiva \u2014 14 d\xEDas de observaci\xF3n sin acci\xF3n").option("--days <number>", "Duraci\xF3n del entrenamiento", "14").option("--zone <name>", "Zona espec\xEDfica a calibrar").action(train);
1082
+ program.command("report").description("Generar reporte para SERNAGEOMIN").option("--format <type>", "Formato: pdf, csv, xlsx", "pdf").option("--period <range>", "Per\xEDodo: last-month, last-week, custom").option("--output <path>", "Ruta de salida").action(report);
1083
+ program.command("config").description("Ver/editar configuraci\xF3n").argument("[key]", "Clave de configuraci\xF3n").argument("[value]", "Nuevo valor").action((key, value) => {
1084
+ if (!key) {
1085
+ console.log("Configuraci\xF3n actual:");
1086
+ } else if (value) {
1087
+ console.log(`Configurando ${key} = ${value}`);
1088
+ } else {
1089
+ console.log(`Valor de ${key}:`);
1090
+ }
1091
+ });
1092
+ program.command("alert").description("Configurar alertas (WhatsApp, email, webhook)").option("--whatsapp <number>", "N\xFAmero WhatsApp (+56...)").option("--email <address>", "Email de alerta").option("--webhook <url>", "URL webhook").option("--threshold <pm10>", "Threshold PM-10 para alerta", "150").action((opts) => {
1093
+ console.log("Configurando alertas:", opts);
1094
+ });
1095
+ program.command("serve").description("Iniciar servidor NERHIA (para modo multi-PC)").option("--host <address>", "Direcci\xF3n de escucha", "0.0.0.0").option("-p, --port <number>", "Puerto", "3100").action((opts) => {
1096
+ console.log(`Servidor NERHIA iniciando en ${opts.host}:${opts.port}`);
1097
+ });
1098
+ program.parse();
1099
+ //# sourceMappingURL=index.js.map