nothumanallowed 11.2.0 → 11.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nothumanallowed",
3
- "version": "11.2.0",
3
+ "version": "11.3.0",
4
4
  "description": "NotHumanAllowed — 38 AI agents, 53 tools. Email, calendar, browser automation, screen capture, canvas, cron/heartbeat, GitHub, Notion, Slack, voice chat, 28 languages. Zero-dependency CLI.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/bootstrap.mjs CHANGED
@@ -102,10 +102,14 @@ export async function bootstrap() {
102
102
  ok('NHA is ready!\n');
103
103
 
104
104
  if (!config.llm.apiKey) {
105
- console.log(` ${'\x1b[1;33m'}Next step:${'\x1b[0m'} Configure your LLM provider:\n`);
105
+ console.log(` ${'\x1b[1;33m'}Next step:${'\x1b[0m'} Choose your LLM provider:\n`);
106
+ console.log(` ${'\x1b[1;32m'}Option 1 — NHA Free (no API key needed):${'\x1b[0m'}`);
107
+ console.log(` nha config set provider nha`);
108
+ console.log(` ${'\x1b[2m'}Powered by Liara (Qwen3 32B). Free, slower (5-15s). No key required.${'\x1b[0m'}\n`);
109
+ console.log(` ${'\x1b[1;36m'}Option 2 — Your own API key (faster, more capable):${'\x1b[0m'}`);
106
110
  console.log(` nha config set provider anthropic`);
107
- console.log(` nha config set key sk-ant-api03-YOUR_KEY_HERE\n`);
108
- console.log(` ${'\x1b[2m'}Supported: anthropic, openai, gemini, deepseek, grok, mistral, cohere${'\x1b[0m'}\n`);
111
+ console.log(` nha config set key sk-ant-api03-YOUR_KEY_HERE`);
112
+ console.log(` ${'\x1b[2m'}Supported: anthropic, openai, gemini, deepseek, grok, mistral, cohere${'\x1b[0m'}\n`);
109
113
  }
110
114
 
111
115
  return true;
@@ -1088,7 +1088,9 @@ export async function cmdUI(args) {
1088
1088
  }
1089
1089
 
1090
1090
  if (!config.llm.apiKey) {
1091
- sendJSON(res, 200, { response: 'No API key configured. Run: nha config set key YOUR_KEY', error: 'no_api_key' });
1091
+ if (config.llm.provider !== 'nha') {
1092
+ sendJSON(res, 200, { response: 'No API key configured. Run: nha config set key YOUR_KEY\nOr use NHA Free (no key needed): nha config set provider nha', error: 'no_api_key' });
1093
+ }
1092
1094
  logRequest(method, pathname, 200, Date.now() - start);
1093
1095
  return;
1094
1096
  }
@@ -1496,7 +1498,7 @@ export async function cmdUI(args) {
1496
1498
  if (method === 'POST' && pathname === '/api/chat/stream') {
1497
1499
  const body = await parseBody(req);
1498
1500
  if (!body.message) { sendJSON(res, 400, { error: 'message required' }); logRequest(method, pathname, 400, Date.now() - start); return; }
1499
- if (!config.llm.apiKey) { sendJSON(res, 200, { error: 'no_api_key' }); logRequest(method, pathname, 200, Date.now() - start); return; }
1501
+ if (!config.llm.apiKey && config.llm.provider !== 'nha') { sendJSON(res, 200, { error: 'no_api_key' }); logRequest(method, pathname, 200, Date.now() - start); return; }
1500
1502
 
1501
1503
  const msg = body.message.trim();
1502
1504
  const convId = body.conversationId;
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 = '11.2.0';
8
+ export const VERSION = '11.3.0';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -232,7 +232,39 @@ export async function streamSSE(res, format) {
232
232
 
233
233
  // ── Router ─────────────────────────────────────────────────────────────────
234
234
 
235
+ /**
236
+ * NHA Free (Liara) — free LLM tier, no API key required.
237
+ * Connects to the NHA-hosted Qwen3 + LoRA on Hetzner GPU.
238
+ * OpenAI-compatible format. Slower than paid providers (5-15 seconds).
239
+ */
240
+ export async function callNHA(apiKey, model, systemPrompt, userMessage, stream = false) {
241
+ const body = {
242
+ model: model || 'nha-v1',
243
+ max_tokens: 4096,
244
+ messages: [
245
+ { role: 'system', content: systemPrompt },
246
+ { role: 'user', content: userMessage },
247
+ ],
248
+ stream,
249
+ };
250
+ const res = await fetch('https://liara.nothumanallowed.com/v1/chat/completions', {
251
+ method: 'POST',
252
+ headers: {
253
+ 'Content-Type': 'application/json',
254
+ },
255
+ body: JSON.stringify(body),
256
+ });
257
+ if (!res.ok) {
258
+ const err = await res.text();
259
+ throw new Error(`NHA Free ${res.status}: ${err}`);
260
+ }
261
+ if (stream) return streamSSE(res, 'openai'); // OpenAI-compatible SSE format
262
+ const data = await res.json();
263
+ return data.choices?.[0]?.message?.content || '';
264
+ }
265
+
235
266
  const PROVIDERS = {
267
+ nha: callNHA,
236
268
  anthropic: callAnthropic,
237
269
  openai: callOpenAI,
238
270
  gemini: callGemini,
@@ -247,6 +279,9 @@ export function getProviderCall(provider) {
247
279
  }
248
280
 
249
281
  export function getApiKey(config, provider) {
282
+ // NHA Free (Liara) doesn't need an API key
283
+ if (provider === 'nha') return 'nha-free-tier';
284
+
250
285
  const keyMap = {
251
286
  anthropic: config.llm.apiKey,
252
287
  openai: config.llm.openaiKey || config.llm.apiKey,
@@ -417,6 +452,7 @@ function buildRequestBody(provider, model, systemPrompt, userMessage, stream) {
417
452
  }
418
453
  // OpenAI-compatible format (OpenAI, DeepSeek, Grok, Mistral)
419
454
  const modelDefaults = {
455
+ nha: 'nha-v1',
420
456
  openai: 'gpt-4o',
421
457
  deepseek: 'deepseek-chat',
422
458
  grok: 'grok-3-latest',
@@ -424,7 +460,7 @@ function buildRequestBody(provider, model, systemPrompt, userMessage, stream) {
424
460
  };
425
461
  return {
426
462
  model: model || modelDefaults[provider] || 'gpt-4o',
427
- max_tokens: 8192,
463
+ max_tokens: provider === 'nha' ? 4096 : 8192,
428
464
  messages: [
429
465
  { role: 'system', content: systemPrompt },
430
466
  { role: 'user', content: userMessage },
@@ -436,6 +472,7 @@ function buildRequestBody(provider, model, systemPrompt, userMessage, stream) {
436
472
  /** Get provider API URL */
437
473
  function getProviderUrl(provider, model, apiKey) {
438
474
  const urls = {
475
+ nha: 'https://liara.nothumanallowed.com/v1/chat/completions',
439
476
  anthropic: 'https://api.anthropic.com/v1/messages',
440
477
  openai: 'https://api.openai.com/v1/chat/completions',
441
478
  deepseek: 'https://api.deepseek.com/v1/chat/completions',
@@ -454,6 +491,9 @@ function getProviderHeaders(provider, apiKey) {
454
491
  'anthropic-version': '2023-06-01',
455
492
  };
456
493
  }
494
+ if (provider === 'nha') {
495
+ return { 'Content-Type': 'application/json' }; // No auth needed for free tier
496
+ }
457
497
  return {
458
498
  'Content-Type': 'application/json',
459
499
  'Authorization': `Bearer ${apiKey}`,
@@ -46,13 +46,19 @@ function sendDesktop(title, body) {
46
46
  const platform = os.platform();
47
47
  try {
48
48
  if (platform === 'darwin') {
49
- const escaped = body.replace(/"/g, '\\"').slice(0, 200);
50
- execSync(`osascript -e 'display notification "${escaped}" with title "NHA: ${title.replace(/"/g, '\\"')}"'`);
49
+ const escaped = body.replace(/"/g, '\\"').replace(/'/g, "'").slice(0, 200);
50
+ const escapedTitle = 'NHA: ' + title.replace(/"/g, '\\"');
51
+ // Try terminal-notifier first (supports click-to-open)
52
+ try {
53
+ execSync(`which terminal-notifier`, { stdio: 'ignore' });
54
+ execSync(`terminal-notifier -title "${escapedTitle}" -message "${escaped}" -open "http://127.0.0.1:3847" -appIcon "" -group nha 2>/dev/null`);
55
+ } catch {
56
+ // Fallback to osascript — use "NHA" as subtitle so click goes nowhere confusing
57
+ execSync(`osascript -e 'display notification "${escaped}" with title "${escapedTitle}" subtitle "Open nha ui to see details"'`);
58
+ }
51
59
  } else if (platform === 'linux') {
52
60
  execSync(`notify-send "NHA: ${title}" "${body.slice(0, 200)}"`);
53
- }
54
- // Windows: PowerShell toast (best effort)
55
- else if (platform === 'win32') {
61
+ } else if (platform === 'win32') {
56
62
  execSync(`powershell -Command "New-BurntToastNotification -Text 'NHA: ${title}', '${body.slice(0, 200)}'" 2>NUL`);
57
63
  }
58
64
  } catch { /* non-fatal */ }
@@ -1979,9 +1979,10 @@ function renderSettings(el) {
1979
1979
  ['role', 'Role', 'e.g. Software Engineer'],
1980
1980
  ['profile-notes', 'Notes', 'Anything else agents should know about you'],
1981
1981
  ]) +
1982
- settingsSection('llm', 'LLM Provider', 'The AI model that powers your agents.', [
1983
- ['provider', 'Provider', 'anthropic / openai / gemini / deepseek / grok / mistral'],
1984
- ['key', 'API Key', 'sk-ant-api03-...', true],
1982
+ '<div style="padding:12px 16px;margin-bottom:16px;background:var(--amberdim);border:1px solid var(--amber3);border-radius:8px"><span style="font-family:var(--term);color:var(--amber);font-size:13px;font-weight:700">NHA Free (Liara)</span><div style="font-size:11px;color:var(--dim);margin:4px 0 8px">Powered by Qwen3 32B. Free, no API key needed. Slower (5-15s).</div><button onclick="apiPost(\\x27/api/config\\x27,{key:\\x27provider\\x27,value:\\x27nha\\x27}).then(function(){location.reload()})" style="padding:6px 16px;background:var(--amber3);color:var(--bg);border:none;border-radius:6px;cursor:pointer;font-family:var(--mono);font-size:11px;font-weight:700">Use NHA Free</button></div>' +
1983
+ settingsSection('llm', 'LLM Provider', 'Or use your own API key for faster, more capable responses.', [
1984
+ ['provider', 'Provider', 'nha (free) / anthropic / openai / gemini / deepseek / grok / mistral'],
1985
+ ['key', 'API Key', 'Not needed for NHA Free', true],
1985
1986
  ['model', 'Model', 'Leave empty for default'],
1986
1987
  ]) +
1987
1988
  settingsSection('responder', 'Message Responder', 'Auto-reply to Telegram and Discord messages.', [