nothumanallowed 10.2.0 → 10.3.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 +60 -4
- package/src/constants.mjs +1 -1
- package/src/services/web-ui.mjs +36 -25
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nothumanallowed",
|
|
3
|
-
"version": "10.
|
|
3
|
+
"version": "10.3.1",
|
|
4
4
|
"description": "NotHumanAllowed — 38 AI agents, 53 tools. Email, calendar, browser automation, screen capture, canvas, cron/heartbeat, GitHub, Notion, Slack, voice chat, 28 languages. Zero-dependency CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/commands/ui.mjs
CHANGED
|
@@ -295,6 +295,34 @@ export async function cmdUI(args) {
|
|
|
295
295
|
fs.writeFileSync(idFile, JSON.stringify(identity, null, 2), { mode: 0o600 });
|
|
296
296
|
}
|
|
297
297
|
|
|
298
|
+
// GET /api/collab/channels — list local channels (from CLI + web UI)
|
|
299
|
+
if (collabAction === 'channels' && method === 'GET') {
|
|
300
|
+
const chFile = path.join(collabDir, 'channels.json');
|
|
301
|
+
let localChannels = [];
|
|
302
|
+
if (fs.existsSync(chFile)) {
|
|
303
|
+
try { localChannels = JSON.parse(fs.readFileSync(chFile, 'utf-8')); } catch {}
|
|
304
|
+
}
|
|
305
|
+
sendJSON(res, 200, { channels: localChannels, identity: { fingerprint: identity.fingerprint, displayName: identity.displayName } });
|
|
306
|
+
logRequest(method, pathname, 200, Date.now() - start);
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// POST /api/collab/channels — save channel to local file (sync web UI → CLI)
|
|
311
|
+
if (collabAction === 'channels' && method === 'POST') {
|
|
312
|
+
const body = await parseBody(req);
|
|
313
|
+
const chFile = path.join(collabDir, 'channels.json');
|
|
314
|
+
let localChannels = [];
|
|
315
|
+
if (fs.existsSync(chFile)) { try { localChannels = JSON.parse(fs.readFileSync(chFile, 'utf-8')); } catch {} }
|
|
316
|
+
if (!localChannels.find((c) => c.id === body.id)) {
|
|
317
|
+
localChannels.push({ id: body.id, name: body.name, active: true, role: body.role || 'member', createdAt: new Date().toISOString() });
|
|
318
|
+
localChannels.forEach((c) => { if (c.id !== body.id) c.active = false; });
|
|
319
|
+
fs.writeFileSync(chFile, JSON.stringify(localChannels, null, 2), { mode: 0o600 });
|
|
320
|
+
}
|
|
321
|
+
sendJSON(res, 200, { ok: true });
|
|
322
|
+
logRequest(method, pathname, 200, Date.now() - start);
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
|
|
298
326
|
if (collabAction === 'create' && method === 'POST') {
|
|
299
327
|
const body = await parseBody(req);
|
|
300
328
|
const r = await fetch(ALEX_API + '/channels', {
|
|
@@ -326,18 +354,46 @@ export async function cmdUI(args) {
|
|
|
326
354
|
const chId = url.searchParams.get('channelId');
|
|
327
355
|
if (!chId) { sendJSON(res, 400, { error: 'channelId required' }); return; }
|
|
328
356
|
const r = await fetch(ALEX_API + '/channels/' + chId + '/messages?fp=' + identity.fingerprint);
|
|
329
|
-
|
|
357
|
+
const data = await r.json();
|
|
358
|
+
// Decrypt messages client-side using channel key
|
|
359
|
+
const channelKey = crypto.createHash('sha256').update('alexandria-channel-key-v1').update(chId).digest();
|
|
360
|
+
if (data.messages) {
|
|
361
|
+
for (const msg of data.messages) {
|
|
362
|
+
if (msg.type === 'system' || !msg.ciphertext || !msg.nonce) continue;
|
|
363
|
+
try {
|
|
364
|
+
const nonce = Buffer.from(msg.nonce, 'base64');
|
|
365
|
+
const raw = Buffer.from(msg.ciphertext, 'base64');
|
|
366
|
+
const tag = raw.subarray(raw.length - 16);
|
|
367
|
+
const encrypted = raw.subarray(0, raw.length - 16);
|
|
368
|
+
const decipher = crypto.createDecipheriv('aes-256-gcm', channelKey, nonce);
|
|
369
|
+
decipher.setAuthTag(tag);
|
|
370
|
+
msg.content = decipher.update(encrypted) + decipher.final('utf-8');
|
|
371
|
+
} catch {
|
|
372
|
+
msg.content = '[cannot decrypt]';
|
|
373
|
+
}
|
|
374
|
+
// Add sender name from members list
|
|
375
|
+
const sender = data.members?.find((m) => m.fingerprint === msg.senderFingerprint);
|
|
376
|
+
msg.senderName = sender?.displayName || msg.senderFingerprint?.slice(0, 8) || 'Unknown';
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
sendJSON(res, 200, data);
|
|
330
380
|
logRequest(method, pathname, 200, Date.now() - start);
|
|
331
381
|
return;
|
|
332
382
|
}
|
|
333
383
|
|
|
334
384
|
if (collabAction === 'send' && method === 'POST') {
|
|
335
385
|
const body = await parseBody(req);
|
|
336
|
-
//
|
|
337
|
-
|
|
386
|
+
// Encrypt with the same channel key used by CLI
|
|
387
|
+
const channelKey = crypto.createHash('sha256').update('alexandria-channel-key-v1').update(body.channelId).digest();
|
|
388
|
+
const nonce = crypto.randomBytes(12);
|
|
389
|
+
const cipher = crypto.createCipheriv('aes-256-gcm', channelKey, nonce);
|
|
390
|
+
const encrypted = Buffer.concat([cipher.update(body.message, 'utf-8'), cipher.final()]);
|
|
391
|
+
const tag = cipher.getAuthTag();
|
|
392
|
+
const ciphertext = Buffer.concat([encrypted, tag]).toString('base64');
|
|
393
|
+
|
|
338
394
|
const r = await fetch(ALEX_API + '/channels/' + body.channelId + '/messages', {
|
|
339
395
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
340
|
-
body: JSON.stringify({ senderFingerprint: identity.fingerprint, nonce:
|
|
396
|
+
body: JSON.stringify({ senderFingerprint: identity.fingerprint, nonce: nonce.toString('base64'), ciphertext, type: 'text' }),
|
|
341
397
|
});
|
|
342
398
|
sendJSON(res, 200, await r.json());
|
|
343
399
|
logRequest(method, pathname, 200, Date.now() - start);
|
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 = '10.
|
|
8
|
+
export const VERSION = '10.3.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
|
@@ -1608,22 +1608,12 @@ function renderCollab(el){
|
|
|
1608
1608
|
h+='<button onclick="publishConversation()" style="padding:6px 12px;background:var(--greendim);border:1px solid var(--green3);border-radius:6px;color:var(--green);font-family:var(--mono);font-size:11px;cursor:pointer">Publish Current Chat</button>';
|
|
1609
1609
|
h+='</div>';
|
|
1610
1610
|
|
|
1611
|
-
// Load channels from
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
for(var i=0;i<collabChannels.length;i++){
|
|
1618
|
-
var ch=collabChannels[i];
|
|
1619
|
-
var active=collabActiveChannel===ch.id;
|
|
1620
|
-
h+='<div onclick="collabSelect(\\x27'+ch.id+'\\x27)" style="padding:8px 12px;cursor:pointer;border-left:3px solid '+(active?'var(--amber)':'transparent')+';background:'+(active?'var(--bg2)':'transparent')+';margin-bottom:2px;border-radius:0 6px 6px 0">';
|
|
1621
|
-
h+='<span style="font-size:12px;color:var(--fg)">'+esc(ch.name)+'</span>';
|
|
1622
|
-
h+='<span style="font-size:9px;color:var(--dim);margin-left:8px">'+ch.id.slice(0,8)+'...</span>';
|
|
1623
|
-
h+='</div>';
|
|
1624
|
-
}
|
|
1625
|
-
h+='</div>';
|
|
1626
|
-
}
|
|
1611
|
+
// Load channels from server (synced with CLI)
|
|
1612
|
+
apiGet('/api/collab/channels').then(function(r){
|
|
1613
|
+
if(r&&r.channels){collabChannels=r.channels;renderCollabChannelList();}
|
|
1614
|
+
}).catch(function(){});
|
|
1615
|
+
|
|
1616
|
+
h+='<div id="collabChannelList" style="margin-bottom:16px"><div style="color:var(--dim);font-size:11px;padding:8px">Loading channels...</div></div>';
|
|
1627
1617
|
|
|
1628
1618
|
// Messages area
|
|
1629
1619
|
h+='<div id="collabMessages" style="background:var(--bg2);border:1px solid var(--border);border-radius:8px;min-height:300px;max-height:500px;overflow-y:auto;padding:12px;margin-bottom:12px">';
|
|
@@ -1644,15 +1634,32 @@ function renderCollab(el){
|
|
|
1644
1634
|
if(collabActiveChannel)collabLoadMessages();
|
|
1645
1635
|
}
|
|
1646
1636
|
|
|
1637
|
+
function renderCollabChannelList(){
|
|
1638
|
+
var el=document.getElementById('collabChannelList');if(!el)return;
|
|
1639
|
+
if(collabChannels.length===0){el.innerHTML='<div style="color:var(--dim);font-size:11px;padding:8px">No channels yet</div>';return;}
|
|
1640
|
+
var h='';
|
|
1641
|
+
for(var i=0;i<collabChannels.length;i++){
|
|
1642
|
+
var ch=collabChannels[i];
|
|
1643
|
+
var active=collabActiveChannel===ch.id;
|
|
1644
|
+
h+='<div onclick="collabSelect(\\x27'+ch.id+'\\x27)" style="padding:8px 12px;cursor:pointer;border-left:3px solid '+(active?'var(--amber)':'transparent')+';background:'+(active?'var(--bg2)':'transparent')+';margin-bottom:2px;border-radius:0 6px 6px 0">';
|
|
1645
|
+
h+='<span style="font-size:12px;color:var(--fg)">'+esc(ch.name)+'</span>';
|
|
1646
|
+
h+='<span style="font-size:9px;color:var(--dim);margin-left:8px">'+ch.id.slice(0,8)+'...</span>';
|
|
1647
|
+
h+='</div>';
|
|
1648
|
+
}
|
|
1649
|
+
el.innerHTML=h;
|
|
1650
|
+
if(collabActiveChannel)collabLoadMessages();
|
|
1651
|
+
}
|
|
1647
1652
|
function collabCreateChannel(){
|
|
1648
1653
|
var name=prompt('Channel name:');if(!name)return;
|
|
1649
1654
|
apiPost('/api/collab/create',{name:name}).then(function(r){
|
|
1650
1655
|
if(r.error){alert(r.error);return;}
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
+
// Save to local file via server
|
|
1657
|
+
apiPost('/api/collab/channels',{id:r.id,name:name,role:'creator'}).then(function(){
|
|
1658
|
+
collabChannels.push({id:r.id,name:name,role:'creator'});
|
|
1659
|
+
collabActiveChannel=r.id;
|
|
1660
|
+
prompt('Share this invite code with collaborators:',r.id);
|
|
1661
|
+
renderCollabChannelList();
|
|
1662
|
+
});
|
|
1656
1663
|
});
|
|
1657
1664
|
}
|
|
1658
1665
|
|
|
@@ -1660,16 +1667,20 @@ function collabJoinChannel(){
|
|
|
1660
1667
|
var code=prompt('Invite code:');if(!code)return;
|
|
1661
1668
|
apiPost('/api/collab/join',{channelId:code}).then(function(r){
|
|
1662
1669
|
if(r.error){alert(r.error);return;}
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1670
|
+
apiPost('/api/collab/channels',{id:code,name:r.name||code.slice(0,8),role:'member'}).then(function(){
|
|
1671
|
+
collabChannels.push({id:code,name:r.name||code.slice(0,8),role:'member'});
|
|
1672
|
+
collabActiveChannel=code;
|
|
1673
|
+
renderCollabChannelList();
|
|
1674
|
+
});
|
|
1667
1675
|
});
|
|
1668
1676
|
}
|
|
1669
1677
|
|
|
1670
1678
|
function collabSelect(id){
|
|
1671
1679
|
collabActiveChannel=id;
|
|
1672
1680
|
collabLoadMessages();
|
|
1681
|
+
// Auto-refresh every 5s while channel is selected
|
|
1682
|
+
if(collabPolling)clearInterval(collabPolling);
|
|
1683
|
+
collabPolling=setInterval(function(){if(currentView==='collab'&&collabActiveChannel===id)collabLoadMessages();},5000);
|
|
1673
1684
|
}
|
|
1674
1685
|
|
|
1675
1686
|
function collabLoadMessages(){
|