nothumanallowed 14.5.3 → 14.5.5
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": "14.5.
|
|
3
|
+
"version": "14.5.5",
|
|
4
4
|
"description": "NotHumanAllowed — 38 AI agents, 80 tools, Studio (visual agentic workflows). Email, calendar, browser automation, screen capture, canvas, cron/heartbeat, Alexandria E2E messaging, GitHub, Notion, Slack, voice chat, free AI (Liara), 28 languages. Zero-dependency CLI.",
|
|
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 = '14.5.
|
|
8
|
+
export const VERSION = '14.5.5';
|
|
9
9
|
export const BASE_URL = 'https://nothumanallowed.com/cli';
|
|
10
10
|
export const API_BASE = 'https://nothumanallowed.com/api/v1';
|
|
11
11
|
|
|
@@ -42,7 +42,10 @@ class SandboxManager {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
isRunning() {
|
|
45
|
-
|
|
45
|
+
if (!this._sandbox || !this._sandbox.proc) return false;
|
|
46
|
+
if (this._sandbox.proc.killed) { this._sandbox = null; return false; }
|
|
47
|
+
// Verify the process is actually alive (not zombie)
|
|
48
|
+
try { process.kill(this._sandbox.proc.pid, 0); return true; } catch { this._sandbox = null; return false; }
|
|
46
49
|
}
|
|
47
50
|
|
|
48
51
|
status() {
|
|
@@ -55,17 +58,43 @@ class SandboxManager {
|
|
|
55
58
|
get port() { return this.isRunning() ? this._sandbox.port : null; }
|
|
56
59
|
|
|
57
60
|
async stop() {
|
|
58
|
-
if (!this.
|
|
59
|
-
const { proc } = this._sandbox;
|
|
61
|
+
if (!this._sandbox) return;
|
|
62
|
+
const { proc, port } = this._sandbox;
|
|
60
63
|
this._sandbox = null;
|
|
64
|
+
|
|
65
|
+
// 1. Kill the entire process group (parent + all children)
|
|
61
66
|
try {
|
|
62
|
-
proc.
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
67
|
+
if (proc.pid) {
|
|
68
|
+
// Kill the process group — negative PID kills all processes in the group
|
|
69
|
+
try { process.kill(-proc.pid, 'SIGTERM'); } catch {}
|
|
70
|
+
// Grace period, then SIGKILL the group
|
|
71
|
+
await new Promise((resolve) => {
|
|
72
|
+
const t = setTimeout(() => {
|
|
73
|
+
try { process.kill(-proc.pid, 'SIGKILL'); } catch {}
|
|
74
|
+
try { proc.kill('SIGKILL'); } catch {}
|
|
75
|
+
resolve();
|
|
76
|
+
}, 1500);
|
|
77
|
+
proc.once('exit', () => { clearTimeout(t); resolve(); });
|
|
78
|
+
});
|
|
79
|
+
} else {
|
|
80
|
+
try { proc.kill('SIGKILL'); } catch {}
|
|
81
|
+
}
|
|
68
82
|
} catch {}
|
|
83
|
+
|
|
84
|
+
// 2. Force-kill any orphan processes still holding the port
|
|
85
|
+
if (port) {
|
|
86
|
+
try {
|
|
87
|
+
if (process.platform === 'win32') {
|
|
88
|
+
await execAsync(`for /f "tokens=5" %a in ('netstat -ano ^| findstr :${port} ^| findstr LISTENING') do taskkill /F /PID %a`, { timeout: 3000 });
|
|
89
|
+
} else {
|
|
90
|
+
const { stdout } = await execAsync(`lsof -ti:${port} 2>/dev/null || true`, { timeout: 2000 });
|
|
91
|
+
const pids = stdout.trim().split(/\s+/).filter(Boolean);
|
|
92
|
+
for (const pid of pids) { try { process.kill(parseInt(pid), 'SIGKILL'); } catch {} }
|
|
93
|
+
}
|
|
94
|
+
} catch {}
|
|
95
|
+
// Wait for OS to release the port
|
|
96
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
97
|
+
}
|
|
69
98
|
}
|
|
70
99
|
|
|
71
100
|
/**
|
|
@@ -155,7 +184,7 @@ class SandboxManager {
|
|
|
155
184
|
NODE_ENV: 'development',
|
|
156
185
|
NHA_SANDBOX: '1',
|
|
157
186
|
},
|
|
158
|
-
detached:
|
|
187
|
+
detached: true,
|
|
159
188
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
160
189
|
});
|
|
161
190
|
|
|
@@ -947,7 +976,7 @@ RULES:
|
|
|
947
976
|
const proc = spawn('node', [patchedEntry], {
|
|
948
977
|
cwd: projectDir,
|
|
949
978
|
env: { ...process.env, PORT: String(port), NODE_ENV: 'development', NHA_SANDBOX: '1' },
|
|
950
|
-
detached:
|
|
979
|
+
detached: true, stdio: ['ignore', 'pipe', 'pipe'],
|
|
951
980
|
});
|
|
952
981
|
sandbox._sandbox = { proc, port, projectName, startedAt: new Date(), healthy: false };
|
|
953
982
|
let sandboxStderr = '';
|
|
@@ -1089,7 +1118,7 @@ RULES:
|
|
|
1089
1118
|
const proc = spawn('node', [patchedEntry], {
|
|
1090
1119
|
cwd: projectDir,
|
|
1091
1120
|
env: { ...process.env, PORT: String(port), NODE_ENV: 'development', NHA_SANDBOX: '1' },
|
|
1092
|
-
detached:
|
|
1121
|
+
detached: true,
|
|
1093
1122
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
1094
1123
|
});
|
|
1095
1124
|
sandbox._sandbox = { proc, port, projectName, startedAt: new Date(), healthy: false };
|
|
@@ -2006,6 +2035,14 @@ export function register(router) {
|
|
|
2006
2035
|
} catch (e) { sendError(res, 500, e.message); }
|
|
2007
2036
|
});
|
|
2008
2037
|
|
|
2038
|
+
// ── Sandbox stop (beacon — for beforeunload) ───────────────────────────────
|
|
2039
|
+
router.post('/api/studio/webcraft/sandbox/stop-beacon', async (_req, res) => {
|
|
2040
|
+
try {
|
|
2041
|
+
await sandbox.stop();
|
|
2042
|
+
sendJSON(res, 200, { ok: true });
|
|
2043
|
+
} catch (e) { sendError(res, 500, e.message); }
|
|
2044
|
+
});
|
|
2045
|
+
|
|
2009
2046
|
// ── Sandbox status ────────────────────────────────────────────────────────
|
|
2010
2047
|
router.get('/api/studio/webcraft/sandbox/status', (_req, res) => {
|
|
2011
2048
|
sendJSON(res, 200, sandbox.status());
|
|
@@ -2100,6 +2137,73 @@ export function register(router) {
|
|
|
2100
2137
|
} catch (e) { sendError(res, 500, e.message); }
|
|
2101
2138
|
});
|
|
2102
2139
|
|
|
2140
|
+
// ── Full project scan — lint ALL files at once ───────────────────────────
|
|
2141
|
+
router.post('/api/studio/webcraft/scan', async (req, res) => {
|
|
2142
|
+
try {
|
|
2143
|
+
const { projectName } = await parseBody(req);
|
|
2144
|
+
if (!projectName) return sendError(res, 400, 'projectName required');
|
|
2145
|
+
const dir = ProjectStore.dir(projectName);
|
|
2146
|
+
if (!fs.existsSync(dir)) return sendJSON(res, 200, { issues: [] });
|
|
2147
|
+
|
|
2148
|
+
const files = _listProjectFiles(dir);
|
|
2149
|
+
const issues = [];
|
|
2150
|
+
const allFileNames = new Set(files);
|
|
2151
|
+
|
|
2152
|
+
for (const relPath of files) {
|
|
2153
|
+
const content = ProjectStore.readFile(projectName, relPath);
|
|
2154
|
+
if (!content) continue;
|
|
2155
|
+
const ext = relPath.split('.').pop()?.toLowerCase();
|
|
2156
|
+
|
|
2157
|
+
// JS syntax check
|
|
2158
|
+
if (ext === 'js' || ext === 'mjs') {
|
|
2159
|
+
try { new Function(content); } catch (e) {
|
|
2160
|
+
issues.push({ file: relPath, severity: 'error', message: `Syntax: ${e.message.replace(/\n.*/s, '')}` });
|
|
2161
|
+
}
|
|
2162
|
+
}
|
|
2163
|
+
|
|
2164
|
+
// JSON parse check
|
|
2165
|
+
if (ext === 'json') {
|
|
2166
|
+
try { JSON.parse(content); } catch (e) {
|
|
2167
|
+
issues.push({ file: relPath, severity: 'error', message: `JSON: ${e.message}` });
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
|
|
2171
|
+
// CSS brace balance
|
|
2172
|
+
if (ext === 'css') {
|
|
2173
|
+
const o = (content.match(/\{/g) || []).length, c = (content.match(/\}/g) || []).length;
|
|
2174
|
+
if (o !== c) issues.push({ file: relPath, severity: 'warning', message: `Unbalanced braces: ${o} open, ${c} close` });
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2177
|
+
// HTML closing tag + reference check
|
|
2178
|
+
if (ext === 'html' || ext === 'htm') {
|
|
2179
|
+
if (!content.includes('</html>')) {
|
|
2180
|
+
issues.push({ file: relPath, severity: 'warning', message: 'Missing </html>' });
|
|
2181
|
+
}
|
|
2182
|
+
// Check file references
|
|
2183
|
+
const refRegex = /(?:src|href)=["']([^"']*?\.(?:js|css|mjs))["']/gi;
|
|
2184
|
+
let m;
|
|
2185
|
+
while ((m = refRegex.exec(content)) !== null) {
|
|
2186
|
+
const ref = m[1];
|
|
2187
|
+
if (ref.startsWith('http') || ref.startsWith('//') || ref.startsWith('data:')) continue;
|
|
2188
|
+
const htmlDir = path.dirname(relPath);
|
|
2189
|
+
const refPath = ref.startsWith('/') ? ref.slice(1) : path.join(htmlDir, ref).replace(/\\/g, '/');
|
|
2190
|
+
const publicRef = refPath.startsWith('public/') ? refPath : `public/${refPath}`;
|
|
2191
|
+
if (!allFileNames.has(refPath) && !allFileNames.has(publicRef) && !allFileNames.has(ref)) {
|
|
2192
|
+
issues.push({ file: relPath, severity: 'error', message: `Missing file: ${ref}` });
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2196
|
+
|
|
2197
|
+
// Truncation check
|
|
2198
|
+
if (isFileTruncated(content, relPath)) {
|
|
2199
|
+
issues.push({ file: relPath, severity: 'warning', message: 'File appears truncated' });
|
|
2200
|
+
}
|
|
2201
|
+
}
|
|
2202
|
+
|
|
2203
|
+
sendJSON(res, 200, { issues, scanned: files.length });
|
|
2204
|
+
} catch (e) { sendError(res, 500, e.message); }
|
|
2205
|
+
});
|
|
2206
|
+
|
|
2103
2207
|
// ── Syntax check ──────────────────────────────────────────────────────────
|
|
2104
2208
|
router.post('/api/studio/webcraft/syntax-check', async (req, res) => {
|
|
2105
2209
|
try {
|