nothumanallowed 13.5.182 → 13.5.184

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nothumanallowed",
3
- "version": "13.5.182",
3
+ "version": "13.5.184",
4
4
  "description": "NotHumanAllowed — 38 AI agents, 80 tools, Studio (visual agentic workflows). Email, calendar, browser automation, screen capture, canvas, cron/heartbeat, Alexandria E2E messaging, GitHub, Notion, Slack, voice chat, free AI (Liara), 28 languages. Zero-dependency CLI.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/constants.mjs CHANGED
@@ -5,7 +5,7 @@ import { fileURLToPath } from 'url';
5
5
  const __filename = fileURLToPath(import.meta.url);
6
6
  const __dirname = path.dirname(__filename);
7
7
 
8
- export const VERSION = '13.5.182';
8
+ export const VERSION = '13.5.184';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -13,6 +13,10 @@ import { buildSystemPrompt, parseActions, executeTool, TOOL_DEFINITIONS } from '
13
13
  import https from 'https';
14
14
  import http from 'http';
15
15
  import { URL } from 'url';
16
+ import fs from 'fs';
17
+ import path from 'path';
18
+ import os from 'os';
19
+ import { VERSION } from '../constants.mjs';
16
20
 
17
21
  // ── Agent Routing (keyword-based, zero LLM calls) ───────────────────────────
18
22
 
@@ -190,6 +194,71 @@ async function callAgentWithTools(config, agentName, userMessage) {
190
194
 
191
195
  // ── Telegram Bot (Long Polling via native fetch) ─────────────────────────────
192
196
 
197
+ // ── User store for Telegram chat IDs (for broadcast notifications) ──────────
198
+
199
+ const TELEGRAM_USERS_FILE = path.join(os.homedir(), '.nha', 'telegram-users.json');
200
+
201
+ function loadTelegramUsers() {
202
+ try {
203
+ const raw = fs.readFileSync(TELEGRAM_USERS_FILE, 'utf8');
204
+ return JSON.parse(raw);
205
+ } catch {
206
+ return {};
207
+ }
208
+ }
209
+
210
+ function saveTelegramUsers(users) {
211
+ try {
212
+ fs.mkdirSync(path.dirname(TELEGRAM_USERS_FILE), { recursive: true });
213
+ fs.writeFileSync(TELEGRAM_USERS_FILE, JSON.stringify(users, null, 2));
214
+ } catch {}
215
+ }
216
+
217
+ function touchTelegramUser(chatId, username, firstName) {
218
+ const users = loadTelegramUsers();
219
+ const id = String(chatId);
220
+ const now = new Date().toISOString();
221
+ users[id] = {
222
+ chatId: id,
223
+ username: username || null,
224
+ firstName: firstName || null,
225
+ firstSeen: users[id]?.firstSeen || now,
226
+ lastSeen: now,
227
+ };
228
+ saveTelegramUsers(users);
229
+ }
230
+
231
+ function getAllTelegramChatIds() {
232
+ const users = loadTelegramUsers();
233
+ return Object.keys(users);
234
+ }
235
+
236
+ // ── npm update check ─────────────────────────────────────────────────────────
237
+
238
+ function compareSemver(a, b) {
239
+ const pa = a.split('.').map(Number);
240
+ const pb = b.split('.').map(Number);
241
+ for (let i = 0; i < 3; i++) {
242
+ if ((pa[i] || 0) > (pb[i] || 0)) return 1;
243
+ if ((pa[i] || 0) < (pb[i] || 0)) return -1;
244
+ }
245
+ return 0;
246
+ }
247
+
248
+ async function checkNpmVersion() {
249
+ const res = await fetch('https://registry.npmjs.org/nothumanallowed/latest', {
250
+ signal: AbortSignal.timeout(8000),
251
+ headers: { 'Accept': 'application/json' },
252
+ });
253
+ const data = await res.json();
254
+ const latest = data.version;
255
+ const current = VERSION;
256
+ const updateAvailable = compareSemver(latest, current) > 0;
257
+ return { current, latest, updateAvailable };
258
+ }
259
+
260
+ // ── Telegram Bot (Long Polling via native fetch) ─────────────────────────────
261
+
193
262
  class TelegramResponder {
194
263
  constructor(config, log, wsBroadcast) {
195
264
  this.config = config;
@@ -203,6 +272,8 @@ class TelegramResponder {
203
272
  this.abortController = null;
204
273
  this.pendingRequests = 0;
205
274
  this.maxConcurrent = 3;
275
+ this._updateCheckTimer = null;
276
+ this._lastNotifiedVersion = null;
206
277
  }
207
278
 
208
279
  get enabled() {
@@ -214,6 +285,8 @@ class TelegramResponder {
214
285
  this.running = true;
215
286
  this.log('[Telegram] Responder started — polling for messages');
216
287
  this._pollLoop();
288
+ // Check for npm updates after 60s, then every 24h
289
+ this._updateCheckTimer = setTimeout(() => this._scheduleUpdateCheck(), 60 * 1000);
217
290
  }
218
291
 
219
292
  stop() {
@@ -222,9 +295,57 @@ class TelegramResponder {
222
295
  this.abortController.abort();
223
296
  this.abortController = null;
224
297
  }
298
+ if (this._updateCheckTimer) {
299
+ clearTimeout(this._updateCheckTimer);
300
+ clearInterval(this._updateCheckTimer);
301
+ this._updateCheckTimer = null;
302
+ }
225
303
  this.log('[Telegram] Responder stopped');
226
304
  }
227
305
 
306
+ async _scheduleUpdateCheck() {
307
+ await this._checkAndNotifyUpdate();
308
+ // Then every 24h
309
+ this._updateCheckTimer = setInterval(() => this._checkAndNotifyUpdate(), 24 * 60 * 60 * 1000);
310
+ }
311
+
312
+ async _checkAndNotifyUpdate() {
313
+ try {
314
+ const { latest, updateAvailable } = await checkNpmVersion();
315
+ if (!updateAvailable) return;
316
+ if (this._lastNotifiedVersion === latest) return; // Already notified for this version
317
+
318
+ this._lastNotifiedVersion = latest;
319
+ const chatIds = getAllTelegramChatIds();
320
+ if (chatIds.length === 0) return;
321
+
322
+ const msg =
323
+ `🆕 *NHA v${latest} disponibile!*\n\n` +
324
+ `Una nuova versione di NotHumanAllowed è stata pubblicata.\n\n` +
325
+ `Aggiorna con:\n` +
326
+ `\`npm install -g nothumanallowed@latest\`\n\n` +
327
+ `Poi riavvia il bot con: \`nha responder restart\``;
328
+
329
+ this.log(`[Telegram] Broadcasting update notification v${latest} to ${chatIds.length} users`);
330
+
331
+ for (const chatId of chatIds) {
332
+ try {
333
+ await this._telegramCall('sendMessage', {
334
+ chat_id: parseInt(chatId, 10),
335
+ text: msg,
336
+ parse_mode: 'Markdown',
337
+ });
338
+ } catch {
339
+ // User blocked bot or chat no longer exists — ignore
340
+ }
341
+ // Small delay to avoid Telegram rate limits
342
+ await this._sleep(300);
343
+ }
344
+ } catch (err) {
345
+ this.log(`[Telegram] Update check failed: ${err.message}`);
346
+ }
347
+ }
348
+
228
349
  async _pollLoop() {
229
350
  while (this.running) {
230
351
  try {
@@ -284,10 +405,29 @@ class TelegramResponder {
284
405
  if (!audioRes.ok) throw new Error(`Download failed: ${audioRes.status}`);
285
406
  const audioBuffer = Buffer.from(await audioRes.arrayBuffer());
286
407
 
287
- // Step 3: transcribe — prefer Groq (free, fast), fallback to OpenAI
408
+ // Step 3: transcribe — priority: NHA proxy (no user key needed) Groq local key → OpenAI local key
288
409
  const groqKey = this.config.llm?.groqKey;
289
410
  const openaiKey = this.config.llm?.openaiKey || (this.config.llm?.provider === 'openai' ? this.config.llm?.apiKey : null);
290
411
 
412
+ // Option A: NHA voice proxy (server-side Groq key, free for all users)
413
+ try {
414
+ const proxyForm = new FormData();
415
+ proxyForm.append('audio', new Blob([audioBuffer], { type: 'audio/ogg' }), 'voice.ogg');
416
+ const proxyRes = await fetch('https://nothumanallowed.com/api/v1/voice/transcribe', {
417
+ method: 'POST',
418
+ body: proxyForm,
419
+ signal: AbortSignal.timeout(30000),
420
+ });
421
+ if (proxyRes.ok) {
422
+ const d = await proxyRes.json();
423
+ if (d.text) return d.text;
424
+ }
425
+ // If proxy returned rate limit or error, fall through to local keys
426
+ } catch {
427
+ // Network error — fall through to local keys
428
+ }
429
+
430
+ // Option B: local Groq key
291
431
  const boundary = '----NHAVoice' + Date.now().toString(36);
292
432
  const crlf = '\r\n';
293
433
  const filename = 'voice.ogg';
@@ -314,6 +454,7 @@ class TelegramResponder {
314
454
  return d.text || '';
315
455
  }
316
456
 
457
+ // Option C: local OpenAI key
317
458
  if (openaiKey) {
318
459
  const modelPartOAI = Buffer.from(
319
460
  `${crlf}--${boundary}${crlf}` +
@@ -331,7 +472,7 @@ class TelegramResponder {
331
472
  return d.text || '';
332
473
  }
333
474
 
334
- throw new Error('No Groq or OpenAI key for voice transcription. Add groqKey to config.');
475
+ throw new Error('Voice transcription unavailable. The NHA proxy is temporarily unreachable.');
335
476
  }
336
477
 
337
478
  async _handleMessage(message) {
@@ -343,6 +484,9 @@ class TelegramResponder {
343
484
  return;
344
485
  }
345
486
 
487
+ // Track this user for broadcast notifications (update alerts, etc.)
488
+ touchTelegramUser(chatId, message.from?.username, message.from?.first_name);
489
+
346
490
  let rawText = message.text || '';
347
491
  let isVoice = false;
348
492