nothumanallowed 16.0.28 → 16.0.29

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.28",
3
+ "version": "16.0.29",
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.28';
8
+ export const VERSION = '16.0.29';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -363,10 +363,22 @@ class SandboxManager {
363
363
  // The pre-scan in Phase 2 catches most cases. Tier 1 here is the safety
364
364
  // net for files generated AFTER pre-scan (e.g. user added a require()
365
365
  // during chat) or for transitive crashes that surface only at runtime.
366
- const missingModules = [...stderrBuf.matchAll(/Cannot find module ['"]([^'"]+)['"]/g)]
366
+ const rawMissing = [...stderrBuf.matchAll(/Cannot find module ['"]([^'"]+)['"]/g)]
367
367
  .map(m => m[1])
368
368
  .filter(m => !m.startsWith('.') && !m.startsWith('/') && !m.startsWith('node:'))
369
- .map(m => m.startsWith('@') ? m.split('/').slice(0, 2).join('/') : m.split('/')[0]);
369
+ .map(m => m.startsWith('@') ? m.split('/').slice(0, 2).join('/') : m.split('/')[0])
370
+ // Drop Node.js built-ins — they're in the runtime, can't be installed
371
+ .filter(m => !_NODE_BUILTINS.has(m));
372
+ // Resolve LLM hallucinated aliases (jwt → jsonwebtoken, bcrypt → bcryptjs)
373
+ const missingModules = rawMissing.map(m => _PACKAGE_ALIASES.get(m) || m);
374
+ // Detect aliases — if the shim already covers the resolved name, no install needed
375
+ const aliasedFromShim = rawMissing.filter(m => {
376
+ const real = _PACKAGE_ALIASES.get(m);
377
+ return real && _SHIMMED_MODULES.has(real);
378
+ });
379
+ if (aliasedFromShim.length > 0) {
380
+ emit({ type: 'warn', msg: `LLM hallucinated package names: ${aliasedFromShim.join(', ')} — the runtime shim already aliases these. If you still see this crash, the shim wasn't re-generated. Restart "nha ui".` });
381
+ }
370
382
  const uniqueMissing = [...new Set(missingModules)].filter(m => !_SHIMMED_MODULES.has(m));
371
383
  if (uniqueMissing.length > 0 && _attempt < MAX_RETRIES) {
372
384
  emit({ type: 'phase', phase: 'autofix', msg: `Missing module${uniqueMissing.length === 1 ? '' : 's'}: ${uniqueMissing.join(', ')} — batch installing...` });
@@ -3658,6 +3670,39 @@ function _installedDeps(projectDir) {
3658
3670
  } catch { return new Set(); }
3659
3671
  }
3660
3672
 
3673
+ // Node.js built-in modules — these are part of the runtime, not npm packages.
3674
+ // `require('fs')` always works without installation. The pre-scan MUST exclude
3675
+ // these or it tries to `npm install fs` which is meaningless (or worse, picks
3676
+ // up a malicious typosquat). Authoritative list from Node 22 LTS docs.
3677
+ const _NODE_BUILTINS = new Set([
3678
+ 'assert', 'async_hooks', 'buffer', 'child_process', 'cluster', 'console',
3679
+ 'constants', 'crypto', 'dgram', 'diagnostics_channel', 'dns', 'domain',
3680
+ 'events', 'fs', 'fs/promises', 'http', 'http2', 'https', 'inspector',
3681
+ 'inspector/promises', 'module', 'net', 'os', 'path', 'path/posix',
3682
+ 'path/win32', 'perf_hooks', 'process', 'punycode', 'querystring', 'readline',
3683
+ 'readline/promises', 'repl', 'stream', 'stream/consumers', 'stream/promises',
3684
+ 'stream/web', 'string_decoder', 'sys', 'timers', 'timers/promises', 'tls',
3685
+ 'trace_events', 'tty', 'url', 'util', 'util/types', 'v8', 'vm', 'wasi',
3686
+ 'worker_threads', 'zlib',
3687
+ ]);
3688
+
3689
+ // Common LLM hallucinations → real package name. The LLM sometimes invents
3690
+ // shorter aliases for popular packages. We map them transparently so the
3691
+ // generated code "just works" without npm install of fake packages.
3692
+ const _PACKAGE_ALIASES = new Map([
3693
+ ['jwt', 'jsonwebtoken'],
3694
+ ['bcrypt', 'bcryptjs'],
3695
+ ['mongo', 'mongoose'],
3696
+ ['postgres', 'pg'],
3697
+ ['postgresql', 'pg'],
3698
+ ['mysql', 'mysql2'],
3699
+ ['env', 'dotenv'],
3700
+ ['util-lodash', 'lodash'],
3701
+ ['express-cors', 'cors'],
3702
+ ['express-helmet', 'helmet'],
3703
+ ['express-body-parser', 'body-parser'],
3704
+ ]);
3705
+
3661
3706
  /** Walk project files and extract all bare-import module names. */
3662
3707
  export function _scanProjectImports(projectDir, maxFiles = 500, maxBytes = 200_000) {
3663
3708
  const found = new Set();
@@ -3690,9 +3735,16 @@ export function _scanProjectImports(projectDir, maxFiles = 500, maxBytes = 200_0
3690
3735
  let m;
3691
3736
  while ((m = re.exec(content)) !== null) {
3692
3737
  const spec = m[1];
3738
+ // Filter 1: node:fs, node:path etc.
3693
3739
  if (spec.startsWith('node:')) continue;
3740
+ // Get the bare package name (handle @scope/name and subpaths)
3694
3741
  const pkg = spec.startsWith('@') ? spec.split('/').slice(0, 2).join('/') : spec.split('/')[0];
3695
- if (pkg) found.add(pkg);
3742
+ if (!pkg) continue;
3743
+ // Filter 2: skip Node built-ins (fs, path, crypto, http, etc.)
3744
+ if (_NODE_BUILTINS.has(pkg)) continue;
3745
+ // Filter 3: resolve LLM-hallucinated aliases to real packages
3746
+ const real = _PACKAGE_ALIASES.get(pkg) || pkg;
3747
+ found.add(real);
3696
3748
  }
3697
3749
  }
3698
3750
  }
@@ -4357,12 +4409,26 @@ module.exports.default = proxy;
4357
4409
  // real implementation, and only falls back to our shim when resolution fails.
4358
4410
  // Also supports NHA_OFFLINE_SHIM=1 to no-op any unresolvable module.
4359
4411
  const shimList = JSON.stringify(Object.keys(shimFiles).filter(f => f !== 'noop.js').map(f => f.replace(/\.js$/, '')));
4412
+ // ALIAS map: bare names the LLM tends to invent → real package name we shim.
4413
+ // Must stay in sync with _PACKAGE_ALIASES in the parent module so pre-scan
4414
+ // and runtime shim agree on what to resolve.
4415
+ const aliasJson = JSON.stringify({
4416
+ 'bcrypt': 'bcryptjs',
4417
+ 'jwt': 'jsonwebtoken',
4418
+ 'mongo': 'mongoose',
4419
+ 'postgres': 'pg',
4420
+ 'postgresql': 'pg',
4421
+ 'env': 'dotenv',
4422
+ 'express-cors': 'cors',
4423
+ 'express-helmet': 'helmet',
4424
+ 'express-body-parser': 'body-parser',
4425
+ });
4360
4426
  const shimIndex = `
4361
4427
  const Module = require('module');
4362
4428
  const path = require('path');
4363
4429
  const __shimDir = ${JSON.stringify(shimDir)};
4364
4430
  const SHIM_NAMES = new Set(${shimList});
4365
- const ALIAS = { 'bcrypt': 'bcryptjs' };
4431
+ const ALIAS = ${aliasJson};
4366
4432
  const OFFLINE = process.env.NHA_OFFLINE_SHIM === '1';
4367
4433
  const _original = Module._resolveFilename.bind(Module);
4368
4434