nothumanallowed 16.0.26 → 16.0.27

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.26",
3
+ "version": "16.0.27",
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.26';
8
+ export const VERSION = '16.0.27';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -142,6 +142,34 @@ class SandboxManager {
142
142
  return;
143
143
  }
144
144
 
145
+ // ── Phase 0: Pre-flight repair ───────────────────────────────────────
146
+ // Before anything else, check that package.json on disk is valid JSON.
147
+ // If a previous LLM generation wrote a corrupt file (e.g. an HTTP error
148
+ // response leaked as content), Node's package_json_reader would crash
149
+ // with SyntaxError before we even get to load shims. Repair in place.
150
+ const pkgPath = path.join(projectDir, 'package.json');
151
+ if (fs.existsSync(pkgPath)) {
152
+ const pkgRaw = fs.readFileSync(pkgPath, 'utf-8');
153
+ let needsRepair = false;
154
+ let reason = '';
155
+ if (_looksLikeLLMError(pkgRaw)) {
156
+ needsRepair = true;
157
+ reason = 'content looks like an LLM API error response (Access denied / Rate limit / HTML block page)';
158
+ } else {
159
+ try { JSON.parse(pkgRaw); }
160
+ catch (e) { needsRepair = true; reason = `invalid JSON: ${e.message}`; }
161
+ }
162
+ if (needsRepair) {
163
+ emit({ type: 'warn', msg: `package.json corrupt (${reason}) — auto-repairing with minimal fallback so the sandbox can boot.` });
164
+ const projectName = path.basename(projectDir);
165
+ fs.writeFileSync(pkgPath, _fallbackPackageJson(projectName), 'utf-8');
166
+ emit({ type: 'status', msg: `package.json repaired. Original corrupt content backed up to package.json.corrupt-${Date.now()}` });
167
+ try {
168
+ fs.writeFileSync(pkgPath + '.corrupt-' + Date.now(), pkgRaw, 'utf-8');
169
+ } catch {}
170
+ }
171
+ }
172
+
145
173
  // ── Phase 1: Shims ────────────────────────────────────────────────────
146
174
  emit({ type: 'phase', phase: 'shims', msg: 'Injecting runtime shims (pg, redis, mongoose, helmet...)' });
147
175
  const shimDir = path.join(projectDir, '.nha-shims');
@@ -647,7 +675,8 @@ const ProjectStore = {
647
675
  if (!_isSafePath(f.name)) continue;
648
676
  const abs = path.join(dir, f.name);
649
677
  ensureDir(path.dirname(abs));
650
- fs.writeFileSync(abs, f.content ?? '', 'utf-8');
678
+ const sanitized = _sanitizeGeneratedFile(f.name, f.content ?? '', projectName);
679
+ fs.writeFileSync(abs, sanitized, 'utf-8');
651
680
  }
652
681
  const meta = {
653
682
  description: description ?? '',
@@ -3449,6 +3478,74 @@ function _patchEntry(projectDir, entryFile, shimDir, port) {
3449
3478
  return '.nha-launcher.js';
3450
3479
  }
3451
3480
 
3481
+ // Detect LLM API error responses that leaked into the file content. When the
3482
+ // LLM endpoint returns 4xx/5xx with a plaintext body (Cloudflare block, rate
3483
+ // limit, "Access temporarily denied"), the response often ends up saved as
3484
+ // file content. Those strings are short, don't start with valid syntax for
3485
+ // the file type, and contain specific marker phrases.
3486
+ const _LLM_ERROR_MARKERS = [
3487
+ /^\s*Access (temporarily )?denied/i,
3488
+ /^\s*Rate ?limit(ed)?/i,
3489
+ /^\s*Service Unavailable/i,
3490
+ /^\s*Internal Server Error/i,
3491
+ /^\s*Too Many Requests/i,
3492
+ /^\s*Bad Gateway/i,
3493
+ /^\s*Gateway Timeout/i,
3494
+ /^\s*<!DOCTYPE html.*Cloudflare/is,
3495
+ /^\s*<html.*<title>.*(Access|Forbidden|Error)/is,
3496
+ /^\s*\{?\s*"error"\s*:\s*"(rate.?limit|quota|unauthorized)"/i,
3497
+ /^\s*error code:\s*\d{3,4}/i,
3498
+ ];
3499
+
3500
+ function _looksLikeLLMError(content) {
3501
+ if (typeof content !== 'string') return false;
3502
+ const head = content.slice(0, 500);
3503
+ return _LLM_ERROR_MARKERS.some(re => re.test(head));
3504
+ }
3505
+
3506
+ // Minimal package.json template — used when the LLM-generated one is corrupted.
3507
+ function _fallbackPackageJson(projectName) {
3508
+ return JSON.stringify({
3509
+ name: String(projectName || 'nha-project').toLowerCase().replace(/[^a-z0-9-]/g, '-'),
3510
+ version: '1.0.0',
3511
+ description: 'NHA-generated project (package.json auto-repaired)',
3512
+ main: 'index.js',
3513
+ type: 'commonjs',
3514
+ scripts: { start: 'node index.js' },
3515
+ dependencies: {},
3516
+ }, null, 2);
3517
+ }
3518
+
3519
+ // Sanitize content before writing to disk. Rejects LLM error responses,
3520
+ // repairs corrupt JSON manifests, and falls back to a minimal template when
3521
+ // content is unsalvageable. This is the LAST line of defense between the LLM
3522
+ // API and the user's filesystem.
3523
+ function _sanitizeGeneratedFile(name, content, projectName) {
3524
+ const isJson = /\.json$/i.test(name) || name.endsWith('package.json');
3525
+ const isPkg = name === 'package.json' || name.endsWith('/package.json');
3526
+
3527
+ if (_looksLikeLLMError(content)) {
3528
+ if (isPkg) return _fallbackPackageJson(projectName);
3529
+ return '/* nha-webcraft: file content from LLM looked like an HTTP error response and was discarded. Re-generate from chat. */';
3530
+ }
3531
+ if (isJson) {
3532
+ try { JSON.parse(content); }
3533
+ catch {
3534
+ if (isPkg) return _fallbackPackageJson(projectName);
3535
+ try {
3536
+ const repaired = content
3537
+ .replace(/,\s*([}\]])/g, '$1')
3538
+ .replace(/([{,]\s*)([A-Za-z_][A-Za-z0-9_]*)\s*:/g, '$1"$2":');
3539
+ JSON.parse(repaired);
3540
+ return repaired;
3541
+ } catch {
3542
+ return '{}';
3543
+ }
3544
+ }
3545
+ }
3546
+ return content;
3547
+ }
3548
+
3452
3549
  // Authoritative list of modules covered by our offline-safe shims.
3453
3550
  // Used by both the pre-scan (to skip them from npm install) and the Tier 1
3454
3551
  // retry (to detect "missing module" stderr that's already covered by a shim).