nothumanallowed 10.8.2 → 10.9.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/constants.mjs +1 -1
- package/src/services/tool-executor.mjs +122 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nothumanallowed",
|
|
3
|
-
"version": "10.
|
|
3
|
+
"version": "10.9.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/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.9.1';
|
|
9
9
|
export const BASE_URL = 'https://nothumanallowed.com/cli';
|
|
10
10
|
export const API_BASE = 'https://nothumanallowed.com/api/v1';
|
|
11
11
|
|
|
@@ -365,6 +365,18 @@ TOOLS:
|
|
|
365
365
|
65. canvas_clear()
|
|
366
366
|
Clear the canvas panel.
|
|
367
367
|
|
|
368
|
+
--- AGENT MESSENGER ---
|
|
369
|
+
|
|
370
|
+
66. collab_send(message: string, channel?: string)
|
|
371
|
+
Send an E2E encrypted message to the active Alexandria channel (AgentMessenger).
|
|
372
|
+
Use this when the user asks to notify collaborators, share progress, or send a message to other agents/team members.
|
|
373
|
+
If channel is not specified, sends to the active channel.
|
|
374
|
+
|
|
375
|
+
67. collab_read(channel?: string, limit?: number)
|
|
376
|
+
Read and decrypt messages from an Alexandria channel. Returns the latest messages (default 20).
|
|
377
|
+
Use this when the user asks to check messages, read a conversation, or see what others wrote.
|
|
378
|
+
If channel is not specified, reads from the active channel. Channel can be an ID or a name.
|
|
379
|
+
|
|
368
380
|
RULES:
|
|
369
381
|
- ABSOLUTE RULE: NEVER LIE. NEVER fabricate, invent, or guess information. If you do not know, say "I don't know." If a tool fails, say it failed. If you cannot see something, say so. Honesty is MORE important than being helpful.
|
|
370
382
|
- CRITICAL: For web searches, ALWAYS use web_search — NEVER open Google/Bing/DuckDuckGo in the browser.
|
|
@@ -1443,6 +1455,116 @@ export async function executeTool(action, params, config) {
|
|
|
1443
1455
|
return '[CANVAS_CLEAR]Canvas cleared.[/CANVAS_CLEAR]';
|
|
1444
1456
|
}
|
|
1445
1457
|
|
|
1458
|
+
// ── Agent Messenger ──────────────────────────────────────────────────
|
|
1459
|
+
case 'collab_send': {
|
|
1460
|
+
const { addCronJob, listCronJobs, loadChannels, getActiveChannel } = await import('./ops-daemon.mjs').catch(() => ({}));
|
|
1461
|
+
const collabMod = await import('../commands/collab.mjs').catch(() => null);
|
|
1462
|
+
if (!collabMod) return 'AgentMessenger not available.';
|
|
1463
|
+
|
|
1464
|
+
const msg = params.message;
|
|
1465
|
+
if (!msg) return 'Message is required.';
|
|
1466
|
+
|
|
1467
|
+
// Use collab CLI to send
|
|
1468
|
+
try {
|
|
1469
|
+
const fs = await import('fs');
|
|
1470
|
+
const path = await import('path');
|
|
1471
|
+
const crypto = await import('crypto');
|
|
1472
|
+
const os = await import('os');
|
|
1473
|
+
const NHA_DIR = path.default.join(os.default.homedir(), '.nha');
|
|
1474
|
+
const chFile = path.default.join(NHA_DIR, 'collab', 'channels.json');
|
|
1475
|
+
const idFile = path.default.join(NHA_DIR, 'collab', 'identity.json');
|
|
1476
|
+
|
|
1477
|
+
if (!fs.default.existsSync(chFile) || !fs.default.existsSync(idFile)) {
|
|
1478
|
+
return 'No Alexandria channels configured. Create one first: nha collab create "name"';
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
const channels = JSON.parse(fs.default.readFileSync(chFile, 'utf-8'));
|
|
1482
|
+
const identity = JSON.parse(fs.default.readFileSync(idFile, 'utf-8'));
|
|
1483
|
+
const channel = params.channel
|
|
1484
|
+
? channels.find(c => c.name?.toLowerCase().includes(params.channel.toLowerCase()) || c.id === params.channel)
|
|
1485
|
+
: channels.find(c => c.active) || channels[0];
|
|
1486
|
+
|
|
1487
|
+
if (!channel) return 'No active channel found.';
|
|
1488
|
+
|
|
1489
|
+
const API = 'https://nothumanallowed.com/api/v1/alexandria';
|
|
1490
|
+
const channelKey = crypto.default.createHash('sha256').update('alexandria-e2e-key-v2').update(channel.id).update(channel.secret || '').digest();
|
|
1491
|
+
const nonce = crypto.default.randomBytes(12);
|
|
1492
|
+
const cipher = crypto.default.createCipheriv('aes-256-gcm', channelKey, nonce);
|
|
1493
|
+
const encrypted = Buffer.concat([cipher.update(msg, 'utf-8'), cipher.final()]);
|
|
1494
|
+
const tag = cipher.getAuthTag();
|
|
1495
|
+
const ciphertext = Buffer.concat([encrypted, tag]).toString('base64');
|
|
1496
|
+
|
|
1497
|
+
const r = await fetch(API + '/channels/' + channel.id + '/messages', {
|
|
1498
|
+
method: 'POST',
|
|
1499
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1500
|
+
body: JSON.stringify({ senderFingerprint: identity.fingerprint, nonce: nonce.toString('base64'), ciphertext, type: 'text' }),
|
|
1501
|
+
});
|
|
1502
|
+
|
|
1503
|
+
if (!r.ok) return `Failed to send: ${r.status}`;
|
|
1504
|
+
return `Message sent to "${channel.name}" (encrypted). Other members will see it in real-time.`;
|
|
1505
|
+
} catch (e) {
|
|
1506
|
+
return `Failed to send: ${e.message}`;
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
case 'collab_read': {
|
|
1511
|
+
try {
|
|
1512
|
+
const fs2 = await import('fs');
|
|
1513
|
+
const path2 = await import('path');
|
|
1514
|
+
const crypto2 = await import('crypto');
|
|
1515
|
+
const os2 = await import('os');
|
|
1516
|
+
const NHA2 = path2.default.join(os2.default.homedir(), '.nha');
|
|
1517
|
+
const chFile2 = path2.default.join(NHA2, 'collab', 'channels.json');
|
|
1518
|
+
const idFile2 = path2.default.join(NHA2, 'collab', 'identity.json');
|
|
1519
|
+
|
|
1520
|
+
if (!fs2.default.existsSync(chFile2) || !fs2.default.existsSync(idFile2)) {
|
|
1521
|
+
return 'No Alexandria channels configured.';
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
const channels2 = JSON.parse(fs2.default.readFileSync(chFile2, 'utf-8'));
|
|
1525
|
+
const identity2 = JSON.parse(fs2.default.readFileSync(idFile2, 'utf-8'));
|
|
1526
|
+
const channel2 = params.channel
|
|
1527
|
+
? channels2.find(c => c.name?.toLowerCase().includes(params.channel.toLowerCase()) || c.id === params.channel || c.id.startsWith(params.channel))
|
|
1528
|
+
: channels2.find(c => c.active) || channels2[0];
|
|
1529
|
+
|
|
1530
|
+
if (!channel2) return 'No matching channel found.';
|
|
1531
|
+
|
|
1532
|
+
const AAPI = 'https://nothumanallowed.com/api/v1/alexandria';
|
|
1533
|
+
const r2 = await fetch(AAPI + '/channels/' + channel2.id + '/messages?fp=' + identity2.fingerprint);
|
|
1534
|
+
if (!r2.ok) return 'Channel not found or expired.';
|
|
1535
|
+
const data2 = await r2.json();
|
|
1536
|
+
if (!data2.messages || data2.messages.length === 0) return `No messages in "${channel2.name}".`;
|
|
1537
|
+
|
|
1538
|
+
const cKey = crypto2.default.createHash('sha256').update('alexandria-e2e-key-v2').update(channel2.id).update(channel2.secret || '').digest();
|
|
1539
|
+
const lim = params.limit || 20;
|
|
1540
|
+
const msgs2 = data2.messages.slice(-lim);
|
|
1541
|
+
const lines = [`Channel: ${channel2.name} (${data2.messages.length} total)\n`];
|
|
1542
|
+
|
|
1543
|
+
for (const msg of msgs2) {
|
|
1544
|
+
if (msg.type === 'system') { lines.push('[system] member joined'); continue; }
|
|
1545
|
+
let content = '[encrypted]';
|
|
1546
|
+
if (msg.ciphertext && msg.nonce) {
|
|
1547
|
+
try {
|
|
1548
|
+
const nonce = Buffer.from(msg.nonce, 'base64');
|
|
1549
|
+
const raw = Buffer.from(msg.ciphertext, 'base64');
|
|
1550
|
+
const tag = raw.subarray(raw.length - 16);
|
|
1551
|
+
const enc = raw.subarray(0, raw.length - 16);
|
|
1552
|
+
const dec = crypto2.default.createDecipheriv('aes-256-gcm', cKey, nonce);
|
|
1553
|
+
dec.setAuthTag(tag);
|
|
1554
|
+
content = dec.update(enc) + dec.final('utf-8');
|
|
1555
|
+
} catch {}
|
|
1556
|
+
}
|
|
1557
|
+
const sender = data2.members?.find(m => m.fingerprint === msg.senderFingerprint);
|
|
1558
|
+
const time = new Date(msg.timestamp).toLocaleTimeString();
|
|
1559
|
+
lines.push(`[${time}] ${sender?.displayName || '?'}: ${content}`);
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
return lines.join('\n');
|
|
1563
|
+
} catch (e) {
|
|
1564
|
+
return `Failed to read: ${e.message}`;
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1446
1568
|
default:
|
|
1447
1569
|
return `Unknown action: ${action}`;
|
|
1448
1570
|
}
|