nothumanallowed 10.1.0 → 10.2.0

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": "10.1.0",
3
+ "version": "10.2.0",
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": {
@@ -255,21 +255,24 @@ async function cmdSend(args) {
255
255
  const chInfo = await apiGet(`/channels/${channel.id}`);
256
256
  if (chInfo.error) { fail(chInfo.error); return; }
257
257
 
258
- // For simplicity, use a channel-wide shared key derived from creator's public key + our private key
259
- // In production, you'd do pairwise key exchange for each member
260
- const creatorMember = chInfo.members[0]; // First member is creator
261
- if (!creatorMember) { fail('Channel has no members'); return; }
262
-
263
- let sharedKey;
264
- if (creatorMember.fingerprint === identity.fingerprint) {
265
- // We're the creator — use a key derived from our own keypair (self-encryption)
266
- // Other members will derive the same key using our public key
267
- sharedKey = crypto.createHash('sha256').update(Buffer.from(identity.publicKey, 'base64')).update('alexandria-channel-key').digest();
268
- } else {
269
- sharedKey = deriveSharedSecret(identity.privateKey, creatorMember.publicKey);
270
- }
271
-
272
- const { nonce, ciphertext } = encrypt(message, sharedKey);
258
+ // Channel-wide shared key: derived from channel ID + member's private key salt
259
+ // All members who know the channel ID can derive the same key
260
+ // This is the "invite code IS the key" model — simple, secure for the use case
261
+ const sharedKey = crypto.createHash('sha256')
262
+ .update(channel.id)
263
+ .update('alexandria-e2e-v1')
264
+ .update(Buffer.from(identity.privateKey, 'base64').subarray(0, 16)) // salt with private key fragment for uniqueness
265
+ .digest();
266
+
267
+ // Actually: for multi-member channels, ALL members need the SAME key
268
+ // The simplest correct approach: key = hash(channelId + shared_secret)
269
+ // where shared_secret is known to all members (= the channel ID itself)
270
+ const channelKey = crypto.createHash('sha256')
271
+ .update('alexandria-channel-key-v1')
272
+ .update(channel.id)
273
+ .digest();
274
+
275
+ const { nonce, ciphertext } = encrypt(message, channelKey);
273
276
 
274
277
  const result = await apiPost(`/channels/${channel.id}/messages`, {
275
278
  senderFingerprint: identity.fingerprint,
@@ -297,16 +300,11 @@ async function cmdRead(args) {
297
300
  return;
298
301
  }
299
302
 
300
- // Get channel info for key exchange
301
- const chInfo = await apiGet(`/channels/${channel.id}`);
302
- const creatorMember = chInfo.members[0];
303
-
304
- let sharedKey;
305
- if (creatorMember.fingerprint === identity.fingerprint) {
306
- sharedKey = crypto.createHash('sha256').update(Buffer.from(identity.publicKey, 'base64')).update('alexandria-channel-key').digest();
307
- } else {
308
- sharedKey = deriveSharedSecret(identity.privateKey, creatorMember.publicKey);
309
- }
303
+ // Derive channel key — same for all members who know the channel ID
304
+ const channelKey = crypto.createHash('sha256')
305
+ .update('alexandria-channel-key-v1')
306
+ .update(channel.id)
307
+ .digest();
310
308
 
311
309
  console.log(`\n ${BOLD}${channel.name}${NC} ${D}(${result.messages.length} messages)${NC}\n`);
312
310
 
@@ -321,7 +319,7 @@ async function cmdRead(args) {
321
319
  }
322
320
 
323
321
  try {
324
- const plaintext = decrypt(msg.ciphertext, msg.nonce, sharedKey);
322
+ const plaintext = decrypt(msg.ciphertext, msg.nonce, channelKey);
325
323
  const isMe = msg.senderFingerprint === identity.fingerprint;
326
324
  const color = isMe ? G : C;
327
325
  console.log(` ${D}${time}${NC} ${color}${senderName}${NC}: ${plaintext}`);
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.1.0';
8
+ export const VERSION = '10.2.0';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -1599,7 +1599,7 @@ var collabPolling=null;
1599
1599
 
1600
1600
  function renderCollab(el){
1601
1601
  var h='<div style="max-width:800px;margin:0 auto;padding:20px">';
1602
- h+='<h2 style="font-family:var(--term);color:var(--amber);font-size:18px;margin-bottom:16px">Alexandria — Encrypted Communication</h2>';
1602
+ h+='<h2 style="font-family:var(--term);color:var(--amber);font-size:18px;margin-bottom:16px">AgentMessenger — Encrypted Communication</h2>';
1603
1603
 
1604
1604
  // Channel list
1605
1605
  h+='<div style="display:flex;gap:8px;margin-bottom:16px;flex-wrap:wrap">';
@@ -2254,7 +2254,7 @@ init();
2254
2254
  <div class="sidebar__section">
2255
2255
  <div class="sidebar__label">AI</div>
2256
2256
  <div class="nav-item" data-view="agents" onclick="switchView('agents')"><span class="nav-item__icon">&#129302;</span> Agents</div>
2257
- <div class="nav-item" data-view="collab" onclick="switchView('collab')"><span class="nav-item__icon">&#128274;</span> Collab</div>
2257
+ <div class="nav-item" data-view="collab" onclick="switchView('collab')"><span class="nav-item__icon">&#128274;</span> AgentMessenger</div>
2258
2258
  </div>
2259
2259
  <div class="sidebar__section">
2260
2260
  <div class="sidebar__label">Config</div>