nothumanallowed 16.0.49 → 16.0.50

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": "16.0.49",
3
+ "version": "16.0.50",
4
4
  "description": "Local AI assistant: 80 tools (Gmail, Calendar, Drive, GitHub, Slack, browser, code, files), 38 agents, visual workflows (Studio, AWF, WebCraft). Install with `npm i -g nothumanallowed`, run with `nha ui`. Free tier built-in (Liara), no API key required. Your data stays on your PC — OAuth tokens local, no cloud. Open-source MIT.",
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 = '16.0.49';
8
+ export const VERSION = '16.0.50';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -4330,19 +4330,30 @@ OUTPUT: ONLY the complete extended CSS file content. No markdown fences, no expl
4330
4330
  `- Hover/focus/transition states for buttons, links, cards\n` +
4331
4331
  `- Color contrast must pass WCAG AA: text-on-bg >= 4.5:1`;
4332
4332
 
4333
- if (emit) emit({ type: 'status', msg: `CSS coverage ${(analysis.coverage * 100).toFixed(0)}% (${analysis.missing.length} selectors missing). Auto-extending ${targetRel} via LLM (timeout 60s)...` });
4334
-
4335
- // Call LLM with timeout + progress heartbeat. Otherwise a slow/stuck Liara
4336
- // leaves the user staring at "Auto-extending..." forever with no feedback.
4333
+ const provider = config?.llm?.provider || 'unknown';
4334
+ const model = config?.llm?.model || config?.llm?.[provider]?.model || 'default';
4335
+ if (emit) emit({ type: 'status', msg: `CSS coverage ${(analysis.coverage * 100).toFixed(0)}% (${analysis.missing.length} selectors missing). Auto-extending ${targetRel} via ${provider}:${model} (timeout 60s)...` });
4336
+
4337
+ // Call LLM with timeout + progress heartbeat + early warning when no bytes arrive.
4338
+ // Different providers have different latencies: Claude is fast (~5-15s),
4339
+ // OpenAI similar, Liara/Qwen3 can be 20-40s under load, Gemini sometimes
4340
+ // stuck on long prompts. Adapt max_tokens to 4096 (down from 8192) to keep
4341
+ // Liara responsive.
4337
4342
  let body = '';
4338
4343
  let lastChunkAt = Date.now();
4339
4344
  const startedAt = Date.now();
4340
4345
  const timeoutMs = 60_000;
4346
+ let earlyWarningEmitted = false;
4341
4347
  const heartbeatInterval = setInterval(() => {
4342
4348
  if (!emit) return;
4343
4349
  const elapsed = ((Date.now() - startedAt) / 1000).toFixed(0);
4344
4350
  const sinceLast = ((Date.now() - lastChunkAt) / 1000).toFixed(0);
4345
4351
  emit({ type: 'status', msg: `LLM extend: ${elapsed}s elapsed, ${body.length} bytes received (${sinceLast}s since last chunk)` });
4352
+ // Early warning if no bytes at all after 15s — provider is likely stuck or rate-limited
4353
+ if (!earlyWarningEmitted && body.length === 0 && Date.now() - startedAt > 15_000) {
4354
+ earlyWarningEmitted = true;
4355
+ emit({ type: 'warn', msg: `Provider ${provider} hasn't sent any data in 15s. If this is Liara, the free tier may be under load — try switching to Anthropic/OpenAI in Settings, or check your API key.` });
4356
+ }
4346
4357
  }, 5_000);
4347
4358
 
4348
4359
  try {
@@ -4350,13 +4361,19 @@ OUTPUT: ONLY the complete extended CSS file content. No markdown fences, no expl
4350
4361
  callLLMStream(config, sys, user, (chunk) => {
4351
4362
  body += chunk;
4352
4363
  lastChunkAt = Date.now();
4353
- }, { max_tokens: 8192 }),
4354
- new Promise((_, reject) => setTimeout(() => reject(new Error('LLM timeout after ' + (timeoutMs / 1000) + 's')), timeoutMs)),
4364
+ }, { max_tokens: 4096 }),
4365
+ new Promise((_, reject) => setTimeout(() => reject(new Error('LLM timeout after ' + (timeoutMs / 1000) + 's — provider ' + provider + ' did not respond')), timeoutMs)),
4355
4366
  ]);
4356
4367
  } catch (e) {
4357
4368
  clearInterval(heartbeatInterval);
4358
- if (emit) emit({ type: 'warn', msg: `CSS extend failed: ${(e.message || e).slice(0, 200)}. Sandbox continues with current CSS — open chat to extend manually.` });
4359
- return { extended: false, reason: 'llm_failed', error: e.message, partialBytes: body.length };
4369
+ const errMsg = (e.message || String(e)).slice(0, 200);
4370
+ if (emit) {
4371
+ emit({ type: 'warn', msg: `CSS extend failed: ${errMsg}` });
4372
+ if (errMsg.includes('timeout') || body.length === 0) {
4373
+ emit({ type: 'warn', msg: `Provider ${provider} unresponsive. Open NHA Settings and switch to a different provider (Anthropic Claude is fastest), or check that your API key is valid. Sandbox continues with current CSS.` });
4374
+ }
4375
+ }
4376
+ return { extended: false, reason: 'llm_failed', error: errMsg, partialBytes: body.length };
4360
4377
  }
4361
4378
  clearInterval(heartbeatInterval);
4362
4379
 
@@ -5038,6 +5055,7 @@ export const _SHIMMED_MODULES = new Set([
5038
5055
  'dotenv', 'cors', 'morgan', 'body-parser', 'cookie-parser',
5039
5056
  'compression', 'express-rate-limit', 'jsonwebtoken', 'bcryptjs', 'bcrypt',
5040
5057
  'uuid', 'lodash', 'debug', 'chalk', 'multer', 'axios', 'express',
5058
+ 'marked', 'markdown-it',
5041
5059
  ]);
5042
5060
 
5043
5061
  /** Read declared dependencies from package.json (deps + devDeps + peer). */
@@ -5165,9 +5183,16 @@ export function _classifyInstallError(err) {
5165
5183
  return { reason: 'offline (npm registry unreachable)', offlineFallback: true,
5166
5184
  hint: 'Check VM network bridge/NAT, DNS, or corporate proxy (HTTP_PROXY/HTTPS_PROXY). Activating shim fallback.' };
5167
5185
  }
5168
- if (/e404|notarget|not found in the npm registry/.test(msg)) {
5186
+ // Distinguish "true E404" (package doesn't exist) from "offline cache miss"
5187
+ // (--prefer-offline can produce 404-looking errors when registry is unreachable).
5188
+ // True E404 also mentions "not in registry"; cache miss mentions "not in cache" or "ENETUNREACH".
5189
+ if (/not in.*(cache|local)|ENETUNREACH|prefer.?offline.*not.*found/i.test(msg)) {
5190
+ return { reason: 'offline cache miss (registry not reached, package not cached)', offlineFallback: true,
5191
+ hint: 'npm --prefer-offline could not find the package in local cache and registry is unreachable. Activating shim fallback.' };
5192
+ }
5193
+ if (/e404|"npm error code e404"|notarget|404 not found|not found in the npm registry|package.*does not exist/i.test(msg)) {
5169
5194
  return { reason: 'package does not exist on npm', offlineFallback: false,
5170
- hint: 'The package name from the LLM-generated code is likely a hallucination. Tier 2 LLM-rewrite will rename it.' };
5195
+ hint: 'The package name from the LLM-generated code is likely a hallucination, or you are offline. Tier 2 LLM-rewrite will rename it, or shim layer will take over if available.' };
5171
5196
  }
5172
5197
  if (/eacces|eperm|permission denied/.test(msg)) {
5173
5198
  return { reason: 'permissions denied', offlineFallback: false,
@@ -5843,6 +5868,74 @@ express.static = function (root, opts) {
5843
5868
  express.Router = function () { const r = createApp(); return r; };
5844
5869
  module.exports = express;
5845
5870
  module.exports.default = express;
5871
+ `;
5872
+
5873
+ // marked — minimal Markdown → HTML parser. Real `marked` is 200KB+ but we
5874
+ // only need basic parsing. Covers: headings, bold/italic, links, code blocks,
5875
+ // lists, paragraphs, line breaks. Good enough for blog-style content.
5876
+ const markedShim = `
5877
+ function escapeHtml(s) {
5878
+ return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
5879
+ }
5880
+ function parseMarkdown(md) {
5881
+ if (!md) return '';
5882
+ let html = String(md);
5883
+ // Code blocks (must come first)
5884
+ html = html.replace(/\\\`\\\`\\\`(\\\\w+)?\\n([\\s\\S]*?)\\n\\\`\\\`\\\`/g, (_, lang, code) => '<pre><code' + (lang ? ' class="language-' + lang + '"' : '') + '>' + escapeHtml(code) + '</code></pre>');
5885
+ // Inline code
5886
+ html = html.replace(/\\\`([^\\\`\\n]+)\\\`/g, '<code>$1</code>');
5887
+ // Headings
5888
+ html = html.replace(/^######\\s+(.+)$/gm, '<h6>$1</h6>');
5889
+ html = html.replace(/^#####\\s+(.+)$/gm, '<h5>$1</h5>');
5890
+ html = html.replace(/^####\\s+(.+)$/gm, '<h4>$1</h4>');
5891
+ html = html.replace(/^###\\s+(.+)$/gm, '<h3>$1</h3>');
5892
+ html = html.replace(/^##\\s+(.+)$/gm, '<h2>$1</h2>');
5893
+ html = html.replace(/^#\\s+(.+)$/gm, '<h1>$1</h1>');
5894
+ // Bold + italic
5895
+ html = html.replace(/\\*\\*\\*([^*]+)\\*\\*\\*/g, '<strong><em>$1</em></strong>');
5896
+ html = html.replace(/\\*\\*([^*]+)\\*\\*/g, '<strong>$1</strong>');
5897
+ html = html.replace(/\\*([^*]+)\\*/g, '<em>$1</em>');
5898
+ html = html.replace(/__([^_]+)__/g, '<strong>$1</strong>');
5899
+ html = html.replace(/_([^_]+)_/g, '<em>$1</em>');
5900
+ // Links + images
5901
+ html = html.replace(/!\\[([^\\]]*)\\]\\(([^)]+)\\)/g, '<img src="$2" alt="$1">');
5902
+ html = html.replace(/\\[([^\\]]+)\\]\\(([^)]+)\\)/g, '<a href="$2">$1</a>');
5903
+ // Blockquotes
5904
+ html = html.replace(/^>\\s+(.+)$/gm, '<blockquote>$1</blockquote>');
5905
+ // Horizontal rules
5906
+ html = html.replace(/^---+$/gm, '<hr>');
5907
+ // Lists (simple)
5908
+ html = html.replace(/^[\\*\\-]\\s+(.+)$/gm, '<li>$1</li>');
5909
+ html = html.replace(/(<li>[\\s\\S]*?<\\/li>\\n?)+/g, (m) => '<ul>' + m.replace(/\\n/g, '') + '</ul>');
5910
+ html = html.replace(/^\\d+\\.\\s+(.+)$/gm, '<li>$1</li>');
5911
+ // Paragraphs (lines not in other elements)
5912
+ const lines = html.split(/\\n\\n+/);
5913
+ html = lines.map(line => {
5914
+ line = line.trim();
5915
+ if (!line) return '';
5916
+ if (/^<(h[1-6]|ul|ol|pre|blockquote|hr|p|div)/.test(line)) return line;
5917
+ return '<p>' + line + '</p>';
5918
+ }).join('\\n');
5919
+ return html;
5920
+ }
5921
+ function marked(md, opts) {
5922
+ return parseMarkdown(md);
5923
+ }
5924
+ marked.parse = parseMarkdown;
5925
+ marked.setOptions = function () { return marked; };
5926
+ marked.use = function () { return marked; };
5927
+ marked.Renderer = function () {};
5928
+ marked.parseInline = function (md) {
5929
+ let html = String(md || '');
5930
+ html = html.replace(/\\*\\*([^*]+)\\*\\*/g, '<strong>$1</strong>');
5931
+ html = html.replace(/\\*([^*]+)\\*/g, '<em>$1</em>');
5932
+ html = html.replace(/\\\`([^\\\`]+)\\\`/g, '<code>$1</code>');
5933
+ html = html.replace(/\\[([^\\]]+)\\]\\(([^)]+)\\)/g, '<a href="$2">$1</a>');
5934
+ return html;
5935
+ };
5936
+ module.exports = marked;
5937
+ module.exports.marked = marked;
5938
+ module.exports.default = marked;
5846
5939
  `;
5847
5940
 
5848
5941
  // axios — minimal fetch-based replacement
@@ -5920,6 +6013,8 @@ module.exports.default = proxy;
5920
6013
  'multer.js': multerShim,
5921
6014
  'axios.js': axiosShim,
5922
6015
  'express.js': expressShim,
6016
+ 'marked.js': markedShim,
6017
+ 'markdown-it.js': markedShim,
5923
6018
  'noop.js': noopShim,
5924
6019
  };
5925
6020
  for (const [name, content] of Object.entries(shimFiles)) {