nothumanallowed 6.3.5 → 6.4.1
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/commands/ui.mjs +23 -23
- package/src/constants.mjs +1 -1
- package/src/services/web-ui.mjs +44 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nothumanallowed",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.4.1",
|
|
4
4
|
"description": "NotHumanAllowed — 38 AI agents for security, code, DevOps, data & daily ops. Per-agent memory, Telegram + Discord auto-responder, proactive intelligence daemon, voice chat, plugin system.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/commands/ui.mjs
CHANGED
|
@@ -533,8 +533,8 @@ export async function cmdUI(args) {
|
|
|
533
533
|
return;
|
|
534
534
|
}
|
|
535
535
|
|
|
536
|
-
// ── Favicon (no-content)
|
|
537
|
-
if (pathname === '/favicon.ico') {
|
|
536
|
+
// ── Favicon + Apple touch icons (no-content, suppress 404) ────────
|
|
537
|
+
if (pathname === '/favicon.ico' || pathname.startsWith('/apple-touch-icon')) {
|
|
538
538
|
res.writeHead(204);
|
|
539
539
|
res.end();
|
|
540
540
|
return;
|
|
@@ -598,6 +598,24 @@ export async function cmdUI(args) {
|
|
|
598
598
|
return;
|
|
599
599
|
}
|
|
600
600
|
|
|
601
|
+
// POST /api/email/read — read full email by ID
|
|
602
|
+
if (method === 'POST' && pathname === '/api/email/read') {
|
|
603
|
+
const body = await parseBody(req);
|
|
604
|
+
if (!body.messageId) {
|
|
605
|
+
sendJSON(res, 400, { error: 'messageId required' });
|
|
606
|
+
logRequest(method, pathname, 400, Date.now() - start);
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
try {
|
|
610
|
+
const msg = await getMessage(config, body.messageId);
|
|
611
|
+
sendJSON(res, 200, { message: msg });
|
|
612
|
+
} catch (e) {
|
|
613
|
+
sendJSON(res, 200, { error: e.message });
|
|
614
|
+
}
|
|
615
|
+
logRequest(method, pathname, 200, Date.now() - start);
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
|
|
601
619
|
// GET /api/emails
|
|
602
620
|
if (method === 'GET' && pathname === '/api/emails') {
|
|
603
621
|
try {
|
|
@@ -869,20 +887,11 @@ export async function cmdUI(args) {
|
|
|
869
887
|
|
|
870
888
|
const server = http.createServer(handleRequest);
|
|
871
889
|
|
|
872
|
-
// ── WebSocket
|
|
873
|
-
const wsServer = http.createServer((req, res) => {
|
|
874
|
-
if (req.url === '/health') {
|
|
875
|
-
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
876
|
-
res.end(JSON.stringify({ ok: true }));
|
|
877
|
-
return;
|
|
878
|
-
}
|
|
879
|
-
res.writeHead(404);
|
|
880
|
-
res.end();
|
|
881
|
-
});
|
|
882
|
-
|
|
890
|
+
// ── WebSocket integrated in main server (same port, /ws path) ──────
|
|
883
891
|
const wsClients = new Set();
|
|
884
892
|
|
|
885
|
-
|
|
893
|
+
server.on('upgrade', (req, socket, head) => {
|
|
894
|
+
if (req.url !== '/ws') { socket.destroy(); return; }
|
|
886
895
|
const key = req.headers['sec-websocket-key'];
|
|
887
896
|
if (!key) { socket.destroy(); return; }
|
|
888
897
|
|
|
@@ -911,15 +920,6 @@ export async function cmdUI(args) {
|
|
|
911
920
|
socket.on('error', () => wsClients.delete(socket));
|
|
912
921
|
});
|
|
913
922
|
|
|
914
|
-
wsServer.on('error', (err) => {
|
|
915
|
-
if (err.code === 'EADDRINUSE') {
|
|
916
|
-
// Daemon already has it — that's fine, frontend will connect to daemon
|
|
917
|
-
return;
|
|
918
|
-
}
|
|
919
|
-
});
|
|
920
|
-
|
|
921
|
-
wsServer.listen(3848, HOST, () => {});
|
|
922
|
-
|
|
923
923
|
server.on('error', (err) => {
|
|
924
924
|
if (err.code === 'EADDRINUSE') {
|
|
925
925
|
fail(`Port ${port} is already in use. Try: nha ui --port=${port + 1}`);
|
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 = '6.
|
|
8
|
+
export const VERSION = '6.4.1';
|
|
9
9
|
export const BASE_URL = 'https://nothumanallowed.com/cli';
|
|
10
10
|
export const API_BASE = 'https://nothumanallowed.com/api/v1';
|
|
11
11
|
|
package/src/services/web-ui.mjs
CHANGED
|
@@ -373,10 +373,50 @@ function renderEmails(el){
|
|
|
373
373
|
var e=dash.emails;
|
|
374
374
|
if(e.length===0){el.innerHTML='<div class="card" style="text-align:center;color:var(--dim);padding:30px">No unread emails</div>';return}
|
|
375
375
|
var h='';e.forEach(function(x){
|
|
376
|
-
h+='<div class="card email"><div class="email__header"><span class="email__from">'+esc(x.from)+'</span><span class="email__date">'+esc(x.date)+'</span></div><div class="email__subject">'+esc(x.subject)+'</div><div class="email__snippet">'+esc((x.snippet||'').slice(0,150))+'</div></div>';
|
|
376
|
+
h+='<div class="card email" style="cursor:pointer" onclick="openEmail(\\x27'+esc(x.id)+'\\x27)"><div class="email__header"><span class="email__from">'+esc(x.from)+'</span><span class="email__date">'+esc(x.date)+'</span></div><div class="email__subject">'+esc(x.subject)+'</div><div class="email__snippet">'+esc((x.snippet||'').slice(0,150))+'</div><div style="font-size:10px;color:var(--green3);margin-top:6px">Click to read full email</div></div>';
|
|
377
377
|
});
|
|
378
378
|
el.innerHTML=h;
|
|
379
379
|
}
|
|
380
|
+
var openEmailId=null;
|
|
381
|
+
function openEmail(id){
|
|
382
|
+
openEmailId=id;
|
|
383
|
+
var el=document.getElementById('content');
|
|
384
|
+
el.innerHTML='<div style="text-align:center;padding:40px"><div class="spinner"></div><div style="color:var(--dim)">Loading email...</div></div>';
|
|
385
|
+
apiPost('/api/email/read',{messageId:id}).then(function(r){
|
|
386
|
+
if(!r||r.error){el.innerHTML='<div class="card" style="color:var(--red);padding:20px">Error: '+(r&&r.error||'Failed to load email')+'</div>';return}
|
|
387
|
+
var m=r.message||r;
|
|
388
|
+
var h='<div style="margin-bottom:12px"><button class="btn btn--secondary" onclick="switchView(\\x27emails\\x27)" style="font-size:11px">← Back to inbox</button></div>';
|
|
389
|
+
h+='<div class="card" style="padding:20px">';
|
|
390
|
+
h+='<div style="margin-bottom:12px;padding-bottom:12px;border-bottom:1px solid var(--border)">';
|
|
391
|
+
h+='<div style="font-size:16px;font-weight:700;color:var(--bright);margin-bottom:8px">'+esc(m.subject||'(no subject)')+'</div>';
|
|
392
|
+
h+='<div style="font-size:12px;color:var(--dim);margin-bottom:4px"><strong style="color:var(--text)">From:</strong> '+esc(m.from||'')+'</div>';
|
|
393
|
+
h+='<div style="font-size:12px;color:var(--dim);margin-bottom:4px"><strong style="color:var(--text)">To:</strong> '+esc(m.to||'')+'</div>';
|
|
394
|
+
h+='<div style="font-size:12px;color:var(--dim)"><strong style="color:var(--text)">Date:</strong> '+esc(m.date||'')+'</div>';
|
|
395
|
+
h+='</div>';
|
|
396
|
+
h+='<div style="font-size:13px;line-height:1.7;color:var(--text);white-space:pre-wrap;word-wrap:break-word">'+esc(m.body||m.snippet||'(no content)')+'</div>';
|
|
397
|
+
h+='</div>';
|
|
398
|
+
// Action buttons
|
|
399
|
+
h+='<div style="display:flex;gap:8px;margin-top:12px">';
|
|
400
|
+
h+='<button class="btn btn--primary" onclick="replyToEmail(\\x27'+esc(id)+'\\x27)" style="font-size:12px">Reply</button>';
|
|
401
|
+
h+='<button class="btn btn--secondary" onclick="askAgentAboutEmail(\\x27'+esc(id)+'\\x27)" style="font-size:12px">Ask SABER to scan</button>';
|
|
402
|
+
h+='</div>';
|
|
403
|
+
el.innerHTML=h;
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
function replyToEmail(id){
|
|
407
|
+
switchView('chat');
|
|
408
|
+
setTimeout(function(){
|
|
409
|
+
var inp=document.getElementById('chatInput');
|
|
410
|
+
if(inp){inp.value='Reply to email '+id+': ';inp.focus()}
|
|
411
|
+
},200);
|
|
412
|
+
}
|
|
413
|
+
function askAgentAboutEmail(id){
|
|
414
|
+
switchView('chat');
|
|
415
|
+
setTimeout(function(){
|
|
416
|
+
var inp=document.getElementById('chatInput');
|
|
417
|
+
if(inp){inp.value='Scan email '+id+' for phishing or security threats';inp.focus()}
|
|
418
|
+
},200);
|
|
419
|
+
}
|
|
380
420
|
|
|
381
421
|
// ---- CALENDAR (monthly grid + day detail modal) ----
|
|
382
422
|
var calYear, calMonth;
|
|
@@ -684,7 +724,7 @@ function renderSettings(el) {
|
|
|
684
724
|
}
|
|
685
725
|
|
|
686
726
|
function settingsSection(id, title, desc, fields) {
|
|
687
|
-
var h = '<
|
|
727
|
+
var h = '<form class="card" style="margin-bottom:16px" id="settings-' + id + '" onsubmit="event.preventDefault();saveSettingsSection(\\x27' + id + '\\x27)">' +
|
|
688
728
|
'<div class="card__title" style="color:var(--green);font-size:14px;margin-bottom:4px">' + esc(title) + '</div>' +
|
|
689
729
|
'<div style="font-size:11px;color:var(--dim);margin-bottom:12px">' + esc(desc) + '</div>';
|
|
690
730
|
|
|
@@ -713,7 +753,7 @@ function settingsSection(id, title, desc, fields) {
|
|
|
713
753
|
'<span id="settings-status-' + id + '" style="font-size:11px;color:var(--dim)"></span>' +
|
|
714
754
|
'</div>';
|
|
715
755
|
|
|
716
|
-
h += '</
|
|
756
|
+
h += '</form>';
|
|
717
757
|
return h;
|
|
718
758
|
}
|
|
719
759
|
|
|
@@ -840,7 +880,7 @@ var ws = null;
|
|
|
840
880
|
var wsReconnectTimer = null;
|
|
841
881
|
function connectWebSocket() {
|
|
842
882
|
try {
|
|
843
|
-
ws = new WebSocket('ws://' + window.location.
|
|
883
|
+
ws = new WebSocket('ws://' + window.location.host + '/ws');
|
|
844
884
|
} catch(e) { return; }
|
|
845
885
|
|
|
846
886
|
ws.onopen = function() {
|