nothumanallowed 16.0.29 → 16.0.30
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 +1 -1
- package/src/constants.mjs +1 -1
- package/src/server/routes/webcraft.mjs +72 -17
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nothumanallowed",
|
|
3
|
-
"version": "16.0.
|
|
3
|
+
"version": "16.0.30",
|
|
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.
|
|
8
|
+
export const VERSION = '16.0.30';
|
|
9
9
|
export const BASE_URL = 'https://nothumanallowed.com/cli';
|
|
10
10
|
export const API_BASE = 'https://nothumanallowed.com/api/v1';
|
|
11
11
|
|
|
@@ -183,21 +183,39 @@ class SandboxManager {
|
|
|
183
183
|
}
|
|
184
184
|
emit({ type: 'status', msg: `Entry point: ${entryFile}` });
|
|
185
185
|
|
|
186
|
-
// Pre-flight: validate
|
|
187
|
-
//
|
|
188
|
-
//
|
|
189
|
-
//
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
186
|
+
// Pre-flight: validate ALL .js/.mjs/.cjs/.jsx files in the project with
|
|
187
|
+
// acorn. If any are corrupted (partial stream / HTTP error leaked as code),
|
|
188
|
+
// quarantine them with a minimal stub. This catches LLM stream interruptions
|
|
189
|
+
// in route handlers (routes/auth.js, routes/billing.js, etc.) — not just
|
|
190
|
+
// the entry file.
|
|
191
|
+
const repaired = [];
|
|
192
|
+
const codeExts = new Set(['.js', '.mjs', '.cjs', '.jsx']);
|
|
193
|
+
const scanSkip = new Set(['node_modules', '.git', '.nha-shims', 'dist', 'build', '.next', 'coverage']);
|
|
194
|
+
const projectBase = path.basename(projectDir);
|
|
195
|
+
const stack = [projectDir];
|
|
196
|
+
while (stack.length) {
|
|
197
|
+
const cur = stack.pop();
|
|
198
|
+
let entries;
|
|
199
|
+
try { entries = fs.readdirSync(cur, { withFileTypes: true }); } catch { continue; }
|
|
200
|
+
for (const ent of entries) {
|
|
201
|
+
if (scanSkip.has(ent.name) || ent.name.startsWith('.')) continue;
|
|
202
|
+
const abs = path.join(cur, ent.name);
|
|
203
|
+
if (ent.isDirectory()) { stack.push(abs); continue; }
|
|
204
|
+
if (!codeExts.has(path.extname(ent.name))) continue;
|
|
205
|
+
try {
|
|
206
|
+
const content = fs.readFileSync(abs, 'utf-8');
|
|
207
|
+
const relName = path.relative(projectDir, abs);
|
|
208
|
+
const sanitized = _sanitizeGeneratedFile(relName, content, projectBase);
|
|
209
|
+
if (sanitized !== content) {
|
|
210
|
+
fs.writeFileSync(abs + '.corrupt-' + Date.now(), content, 'utf-8');
|
|
211
|
+
fs.writeFileSync(abs, sanitized, 'utf-8');
|
|
212
|
+
repaired.push(relName);
|
|
213
|
+
}
|
|
214
|
+
} catch {}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
if (repaired.length > 0) {
|
|
218
|
+
emit({ type: 'warn', msg: `Pre-flight repair: ${repaired.length} file${repaired.length === 1 ? '' : 's'} quarantined (corrupted content) → ${repaired.join(', ')}. Re-generate from chat to restore them.` });
|
|
201
219
|
}
|
|
202
220
|
|
|
203
221
|
// ── Phase 2: Dependencies (pre-scan + batch install) ──────────────────
|
|
@@ -330,6 +348,43 @@ class SandboxManager {
|
|
|
330
348
|
emit({ type: 'phase', phase: 'ready', msg: `Server running on port ${port}` });
|
|
331
349
|
emit({ type: 'ready', port });
|
|
332
350
|
}
|
|
351
|
+
// ── HTTP probe: actually fetch GET / and report what came back ─────
|
|
352
|
+
// Tells the user IMMEDIATELY if the sandbox bound the port but serves
|
|
353
|
+
// a 404 / empty body / wrong content-type. Otherwise they see "ready"
|
|
354
|
+
// but the iframe is black/blank and can't tell why.
|
|
355
|
+
try {
|
|
356
|
+
const probeRes = await fetch(`http://127.0.0.1:${port}/`, {
|
|
357
|
+
signal: AbortSignal.timeout(5000),
|
|
358
|
+
redirect: 'manual',
|
|
359
|
+
}).catch(e => ({ _err: e.message }));
|
|
360
|
+
if (probeRes._err) {
|
|
361
|
+
emit({ type: 'warn', msg: `Probe GET /: ${probeRes._err}. The port is bound but the app doesn't respond on / — check your route handlers.` });
|
|
362
|
+
} else {
|
|
363
|
+
const ct = probeRes.headers.get('content-type') || '(none)';
|
|
364
|
+
const status = probeRes.status;
|
|
365
|
+
const body = await probeRes.text().catch(() => '');
|
|
366
|
+
const len = body.length;
|
|
367
|
+
const isHtml = /text\/html/i.test(ct);
|
|
368
|
+
const isJson = /application\/json/i.test(ct);
|
|
369
|
+
const preview = body.slice(0, 200).replace(/\s+/g, ' ').trim();
|
|
370
|
+
emit({ type: 'log', msg: `[probe] GET / → ${status} (${ct}, ${len} bytes)` });
|
|
371
|
+
if (status >= 400) {
|
|
372
|
+
emit({ type: 'warn', msg: `Probe: GET / returned ${status}. The sandbox is running but has no route for "/". Add app.get('/', ...) in your code or generate an index.html.` });
|
|
373
|
+
} else if (len === 0) {
|
|
374
|
+
emit({ type: 'warn', msg: `Probe: GET / returned empty body (${status}). The route exists but sends no response — check that you call res.send() / res.json() / res.end().` });
|
|
375
|
+
} else if (isHtml && !/<\w+/.test(body)) {
|
|
376
|
+
emit({ type: 'warn', msg: `Probe: GET / claims text/html but has no HTML tags. Preview: "${preview}". Likely a plain text leaked into the response.` });
|
|
377
|
+
} else if (isJson) {
|
|
378
|
+
emit({ type: 'status', msg: `Probe: GET / returns JSON. Browser preview will show raw JSON, not a rendered page. Consider serving an index.html with app.use(express.static('public')) or add a / route returning HTML.` });
|
|
379
|
+
} else if (isHtml) {
|
|
380
|
+
emit({ type: 'status', msg: `Probe: GET / returns HTML (${len} bytes). Iframe preview should render fine.` });
|
|
381
|
+
} else {
|
|
382
|
+
emit({ type: 'log', msg: `[probe] body preview: ${preview}` });
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
} catch (e) {
|
|
386
|
+
emit({ type: 'log', msg: `[probe] failed: ${e.message}` });
|
|
387
|
+
}
|
|
333
388
|
return;
|
|
334
389
|
}
|
|
335
390
|
|
|
@@ -3674,7 +3729,7 @@ function _installedDeps(projectDir) {
|
|
|
3674
3729
|
// `require('fs')` always works without installation. The pre-scan MUST exclude
|
|
3675
3730
|
// these or it tries to `npm install fs` which is meaningless (or worse, picks
|
|
3676
3731
|
// up a malicious typosquat). Authoritative list from Node 22 LTS docs.
|
|
3677
|
-
const _NODE_BUILTINS = new Set([
|
|
3732
|
+
export const _NODE_BUILTINS = new Set([
|
|
3678
3733
|
'assert', 'async_hooks', 'buffer', 'child_process', 'cluster', 'console',
|
|
3679
3734
|
'constants', 'crypto', 'dgram', 'diagnostics_channel', 'dns', 'domain',
|
|
3680
3735
|
'events', 'fs', 'fs/promises', 'http', 'http2', 'https', 'inspector',
|
|
@@ -3689,7 +3744,7 @@ const _NODE_BUILTINS = new Set([
|
|
|
3689
3744
|
// Common LLM hallucinations → real package name. The LLM sometimes invents
|
|
3690
3745
|
// shorter aliases for popular packages. We map them transparently so the
|
|
3691
3746
|
// generated code "just works" without npm install of fake packages.
|
|
3692
|
-
const _PACKAGE_ALIASES = new Map([
|
|
3747
|
+
export const _PACKAGE_ALIASES = new Map([
|
|
3693
3748
|
['jwt', 'jsonwebtoken'],
|
|
3694
3749
|
['bcrypt', 'bcryptjs'],
|
|
3695
3750
|
['mongo', 'mongoose'],
|