myceliumail 1.0.2 → 1.0.3
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/.context7 +87 -0
- package/.eslintrc.json +29 -0
- package/COMPLETE.md +51 -0
- package/MYCELIUMAIL_STARTER_KIT.md +603 -0
- package/NEXT_STEPS.md +96 -0
- package/desktop/README.md +102 -0
- package/desktop/assets/icon.icns +0 -0
- package/desktop/assets/icon.iconset/icon_128x128.png +0 -0
- package/desktop/assets/icon.iconset/icon_128x128@2x.png +0 -0
- package/desktop/assets/icon.iconset/icon_16x16.png +0 -0
- package/desktop/assets/icon.iconset/icon_16x16@2x.png +0 -0
- package/desktop/assets/icon.iconset/icon_256x256.png +0 -0
- package/desktop/assets/icon.iconset/icon_256x256@2x.png +0 -0
- package/desktop/assets/icon.iconset/icon_32x32.png +0 -0
- package/desktop/assets/icon.iconset/icon_32x32@2x.png +0 -0
- package/desktop/assets/icon.iconset/icon_512x512.png +0 -0
- package/desktop/assets/icon.iconset/icon_512x512@2x.png +0 -0
- package/desktop/assets/icon.png +0 -0
- package/desktop/assets/tray-icon.png +0 -0
- package/desktop/main.js +257 -0
- package/desktop/package-lock.json +4198 -0
- package/desktop/package.json +48 -0
- package/desktop/preload.js +11 -0
- package/dist/bin/myceliumail.js +2 -0
- package/dist/bin/myceliumail.js.map +1 -1
- package/dist/commands/key-announce.d.ts +6 -0
- package/dist/commands/key-announce.d.ts.map +1 -0
- package/dist/commands/key-announce.js +63 -0
- package/dist/commands/key-announce.js.map +1 -0
- package/docs/20251215_Treebird-Ecosystem_Knowledge-Base_v2.md +292 -0
- package/docs/20251215_Treebird-Ecosystem_Project-Instructions_v2.md +176 -0
- package/docs/AGENT_DELEGATION_WORKFLOW.md +453 -0
- package/docs/AGENT_STARTER_KIT.md +145 -0
- package/docs/ANNOUNCEMENT_DRAFTS.md +55 -0
- package/docs/DASHBOARD_AGENT_HANDOFF.md +429 -0
- package/docs/DASHBOARD_AGENT_PROMPT.md +32 -0
- package/docs/DASHBOARD_BUILD_ROADMAP.md +61 -0
- package/docs/DEPLOYMENT.md +59 -0
- package/docs/LESSONS_LEARNED.md +127 -0
- package/docs/MCP_PUBLISHING_ROADMAP.md +113 -0
- package/docs/MCP_STARTER_KIT.md +117 -0
- package/docs/SSAN_MESSAGES_SUMMARY.md +92 -0
- package/docs/STORAGE_ARCHITECTURE.md +114 -0
- package/mcp-server/README.md +143 -0
- package/mcp-server/assets/icon.png +0 -0
- package/mcp-server/myceliumail-mcp-1.0.0.tgz +0 -0
- package/mcp-server/package-lock.json +1142 -0
- package/mcp-server/package.json +49 -0
- package/mcp-server/src/lib/config.ts +55 -0
- package/mcp-server/src/lib/crypto.ts +150 -0
- package/mcp-server/src/lib/storage.ts +267 -0
- package/mcp-server/src/server.ts +387 -0
- package/mcp-server/tsconfig.json +26 -0
- package/package.json +3 -3
- package/src/bin/myceliumail.ts +54 -0
- package/src/commands/broadcast.ts +70 -0
- package/src/commands/dashboard.ts +19 -0
- package/src/commands/inbox.ts +75 -0
- package/src/commands/key-announce.ts +70 -0
- package/src/commands/key-import.ts +35 -0
- package/src/commands/keygen.ts +44 -0
- package/src/commands/keys.ts +55 -0
- package/src/commands/read.ts +97 -0
- package/src/commands/send.ts +89 -0
- package/src/commands/watch.ts +101 -0
- package/src/dashboard/public/app.js +523 -0
- package/src/dashboard/public/index.html +75 -0
- package/src/dashboard/public/styles.css +68 -0
- package/src/dashboard/routes.ts +128 -0
- package/src/dashboard/server.ts +33 -0
- package/src/lib/config.ts +104 -0
- package/src/lib/crypto.ts +210 -0
- package/src/lib/realtime.ts +109 -0
- package/src/storage/local.ts +209 -0
- package/src/storage/supabase.ts +336 -0
- package/src/types/index.ts +53 -0
- package/supabase/migrations/000_myceliumail_setup.sql +93 -0
- package/supabase/migrations/001_enable_realtime.sql +10 -0
- package/tsconfig.json +28 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* key-announce command - Publish your public key to Supabase
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Command } from 'commander';
|
|
6
|
+
import { loadConfig, hasSupabaseConfig } from '../lib/config.js';
|
|
7
|
+
import { hasKeyPair, loadKeyPair, getPublicKeyBase64 } from '../lib/crypto.js';
|
|
8
|
+
|
|
9
|
+
export function createKeyAnnounceCommand(): Command {
|
|
10
|
+
return new Command('key-announce')
|
|
11
|
+
.description('Publish your public key to the network')
|
|
12
|
+
.action(async () => {
|
|
13
|
+
const config = loadConfig();
|
|
14
|
+
const agentId = config.agentId;
|
|
15
|
+
|
|
16
|
+
// Check for keypair
|
|
17
|
+
if (!hasKeyPair(agentId)) {
|
|
18
|
+
console.error(`❌ No keypair found for ${agentId}`);
|
|
19
|
+
console.log(' Run: mycmail keygen');
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Check for Supabase
|
|
24
|
+
if (!hasSupabaseConfig(config)) {
|
|
25
|
+
console.error('❌ Supabase not configured');
|
|
26
|
+
console.log(' Set supabase_url and supabase_key in ~/.myceliumail/config.json');
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const keyPair = loadKeyPair(agentId);
|
|
31
|
+
if (!keyPair) {
|
|
32
|
+
console.error('❌ Failed to load keypair');
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const publicKey = getPublicKeyBase64(keyPair);
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
// Make direct Supabase request
|
|
40
|
+
const url = `${config.supabaseUrl}/rest/v1/agent_keys`;
|
|
41
|
+
const response = await fetch(url, {
|
|
42
|
+
method: 'POST',
|
|
43
|
+
headers: {
|
|
44
|
+
'Content-Type': 'application/json',
|
|
45
|
+
'apikey': config.supabaseKey!,
|
|
46
|
+
'Authorization': `Bearer ${config.supabaseKey}`,
|
|
47
|
+
'Prefer': 'resolution=merge-duplicates',
|
|
48
|
+
},
|
|
49
|
+
body: JSON.stringify({
|
|
50
|
+
agent_id: agentId,
|
|
51
|
+
public_key: publicKey,
|
|
52
|
+
updated_at: new Date().toISOString(),
|
|
53
|
+
}),
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
if (!response.ok) {
|
|
57
|
+
const error = await response.text();
|
|
58
|
+
throw new Error(error);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
console.log('📢 Public key announced successfully!\n');
|
|
62
|
+
console.log(`Agent ID: ${agentId}`);
|
|
63
|
+
console.log(`Public Key: ${publicKey}`);
|
|
64
|
+
console.log('\n✅ Other agents can now send you encrypted messages');
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.error('❌ Failed to announce key:', error);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* key-import command - Import a peer's public key
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Command } from 'commander';
|
|
6
|
+
import { saveKnownKey, getKnownKey } from '../lib/crypto.js';
|
|
7
|
+
|
|
8
|
+
export function createKeyImportCommand(): Command {
|
|
9
|
+
return new Command('key-import')
|
|
10
|
+
.description('Import a peer agent\'s public key')
|
|
11
|
+
.argument('<agent>', 'Agent ID to import key for')
|
|
12
|
+
.argument('<key>', 'Base64 encoded public key')
|
|
13
|
+
.option('-f, --force', 'Overwrite existing key')
|
|
14
|
+
.action(async (agentId: string, publicKey: string, options) => {
|
|
15
|
+
// Validate key format (basic check)
|
|
16
|
+
if (publicKey.length < 40) {
|
|
17
|
+
console.error('❌ Invalid key format. Expected base64 encoded NaCl public key.');
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const existing = getKnownKey(agentId);
|
|
22
|
+
if (existing && !options.force) {
|
|
23
|
+
console.log(`⚠️ Key already exists for ${agentId}`);
|
|
24
|
+
console.log(`Existing key: ${existing.slice(0, 20)}...`);
|
|
25
|
+
console.log('\nUse --force to overwrite');
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
saveKnownKey(agentId, publicKey);
|
|
30
|
+
|
|
31
|
+
console.log(`✅ Imported public key for ${agentId}`);
|
|
32
|
+
console.log(`Key: ${publicKey.slice(0, 20)}...`);
|
|
33
|
+
console.log('\n🔐 You can now send encrypted messages to this agent');
|
|
34
|
+
});
|
|
35
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* keygen command - Generate encryption keypair
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Command } from 'commander';
|
|
6
|
+
import { loadConfig } from '../lib/config.js';
|
|
7
|
+
import {
|
|
8
|
+
generateKeyPair,
|
|
9
|
+
saveKeyPair,
|
|
10
|
+
hasKeyPair,
|
|
11
|
+
getPublicKeyBase64,
|
|
12
|
+
loadKeyPair
|
|
13
|
+
} from '../lib/crypto.js';
|
|
14
|
+
|
|
15
|
+
export function createKeygenCommand(): Command {
|
|
16
|
+
return new Command('keygen')
|
|
17
|
+
.description('Generate a new encryption keypair')
|
|
18
|
+
.option('-f, --force', 'Overwrite existing keypair')
|
|
19
|
+
.action(async (options) => {
|
|
20
|
+
const config = loadConfig();
|
|
21
|
+
const agentId = config.agentId;
|
|
22
|
+
|
|
23
|
+
if (hasKeyPair(agentId) && !options.force) {
|
|
24
|
+
console.log(`⚠️ Keypair already exists for ${agentId}`);
|
|
25
|
+
const existingPair = loadKeyPair(agentId);
|
|
26
|
+
if (existingPair) {
|
|
27
|
+
console.log(`📧 Your public key: ${getPublicKeyBase64(existingPair)}`);
|
|
28
|
+
}
|
|
29
|
+
console.log('\nUse --force to regenerate (⚠️ this will invalidate existing encrypted messages)');
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const keyPair = generateKeyPair();
|
|
34
|
+
saveKeyPair(agentId, keyPair);
|
|
35
|
+
|
|
36
|
+
const publicKey = getPublicKeyBase64(keyPair);
|
|
37
|
+
|
|
38
|
+
console.log('🔐 Keypair generated successfully!\n');
|
|
39
|
+
console.log(`Agent ID: ${agentId}`);
|
|
40
|
+
console.log(`📧 Your public key (share with other agents):\n`);
|
|
41
|
+
console.log(publicKey);
|
|
42
|
+
console.log('\n✅ Keys stored in ~/.myceliumail/keys/');
|
|
43
|
+
});
|
|
44
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* keys command - List known public keys
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Command } from 'commander';
|
|
6
|
+
import { loadConfig } from '../lib/config.js';
|
|
7
|
+
import {
|
|
8
|
+
loadKnownKeys,
|
|
9
|
+
listOwnKeys,
|
|
10
|
+
loadKeyPair,
|
|
11
|
+
getPublicKeyBase64
|
|
12
|
+
} from '../lib/crypto.js';
|
|
13
|
+
|
|
14
|
+
export function createKeysCommand(): Command {
|
|
15
|
+
return new Command('keys')
|
|
16
|
+
.description('List known public keys')
|
|
17
|
+
.option('-a, --all', 'Show full keys (not truncated)')
|
|
18
|
+
.action(async (options) => {
|
|
19
|
+
const config = loadConfig();
|
|
20
|
+
const ownKeys = listOwnKeys();
|
|
21
|
+
const knownKeys = loadKnownKeys();
|
|
22
|
+
|
|
23
|
+
console.log('🔐 Encryption Keys\n');
|
|
24
|
+
|
|
25
|
+
// Own keys
|
|
26
|
+
console.log('── Your Keys ──');
|
|
27
|
+
if (ownKeys.length === 0) {
|
|
28
|
+
console.log(' No keypairs generated. Run: mycmail keygen');
|
|
29
|
+
} else {
|
|
30
|
+
for (const agentId of ownKeys) {
|
|
31
|
+
const keyPair = loadKeyPair(agentId);
|
|
32
|
+
if (keyPair) {
|
|
33
|
+
const pubKey = getPublicKeyBase64(keyPair);
|
|
34
|
+
const display = options.all ? pubKey : `${pubKey.slice(0, 20)}...`;
|
|
35
|
+
const marker = agentId === config.agentId ? ' (active)' : '';
|
|
36
|
+
console.log(` ${agentId}${marker}: ${display}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Known peer keys
|
|
42
|
+
console.log('\n── Peer Keys ──');
|
|
43
|
+
const peerEntries = Object.entries(knownKeys);
|
|
44
|
+
if (peerEntries.length === 0) {
|
|
45
|
+
console.log(' No peer keys imported. Use: mycmail key-import <agent> <key>');
|
|
46
|
+
} else {
|
|
47
|
+
for (const [agentId, pubKey] of peerEntries) {
|
|
48
|
+
const display = options.all ? pubKey : `${pubKey.slice(0, 20)}...`;
|
|
49
|
+
console.log(` ${agentId}: ${display}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
console.log('\n💡 Tip: Use --all to show full keys');
|
|
54
|
+
});
|
|
55
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* read command - Read a specific message
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Command } from 'commander';
|
|
6
|
+
import { loadConfig } from '../lib/config.js';
|
|
7
|
+
import { loadKeyPair, decryptMessage } from '../lib/crypto.js';
|
|
8
|
+
import * as storage from '../storage/supabase.js';
|
|
9
|
+
|
|
10
|
+
export function createReadCommand(): Command {
|
|
11
|
+
return new Command('read')
|
|
12
|
+
.description('Read a specific message')
|
|
13
|
+
.argument('<id>', 'Message ID (can be partial)')
|
|
14
|
+
.action(async (messageId: string) => {
|
|
15
|
+
const config = loadConfig();
|
|
16
|
+
const agentId = config.agentId;
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
// Get message - handle partial ID
|
|
20
|
+
const message = await storage.getMessage(messageId);
|
|
21
|
+
|
|
22
|
+
if (!message) {
|
|
23
|
+
// Try to find by partial ID
|
|
24
|
+
const inbox = await storage.getInbox(agentId, { limit: 100 });
|
|
25
|
+
const found = inbox.find(m => m.id.startsWith(messageId));
|
|
26
|
+
|
|
27
|
+
if (!found) {
|
|
28
|
+
console.error(`❌ Message not found: ${messageId}`);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return readAndDisplay(found, agentId);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
await readAndDisplay(message, agentId);
|
|
36
|
+
} catch (error) {
|
|
37
|
+
console.error('❌ Failed to read message:', error);
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function readAndDisplay(message: Awaited<ReturnType<typeof storage.getMessage>>, agentId: string): Promise<void> {
|
|
44
|
+
if (!message) return;
|
|
45
|
+
|
|
46
|
+
// Mark as read
|
|
47
|
+
await storage.markAsRead(message.id);
|
|
48
|
+
|
|
49
|
+
let subject = message.subject;
|
|
50
|
+
let body = message.body;
|
|
51
|
+
|
|
52
|
+
// Decrypt if encrypted
|
|
53
|
+
if (message.encrypted && message.ciphertext && message.nonce && message.senderPublicKey) {
|
|
54
|
+
const keyPair = loadKeyPair(agentId);
|
|
55
|
+
|
|
56
|
+
if (!keyPair) {
|
|
57
|
+
console.log('⚠️ Cannot decrypt: no keypair found');
|
|
58
|
+
console.log(' Generate keypair with: mycmail keygen');
|
|
59
|
+
} else {
|
|
60
|
+
try {
|
|
61
|
+
const decrypted = decryptMessage({
|
|
62
|
+
ciphertext: message.ciphertext,
|
|
63
|
+
nonce: message.nonce,
|
|
64
|
+
senderPublicKey: message.senderPublicKey,
|
|
65
|
+
}, keyPair);
|
|
66
|
+
|
|
67
|
+
if (decrypted) {
|
|
68
|
+
const parsed = JSON.parse(decrypted);
|
|
69
|
+
subject = parsed.subject || subject;
|
|
70
|
+
body = parsed.body || body;
|
|
71
|
+
console.log('🔓 Message decrypted\n');
|
|
72
|
+
} else {
|
|
73
|
+
console.log('⚠️ Decryption failed\n');
|
|
74
|
+
}
|
|
75
|
+
} catch (e) {
|
|
76
|
+
console.log('⚠️ Failed to parse decrypted message\n');
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Display message
|
|
82
|
+
console.log('━'.repeat(60));
|
|
83
|
+
console.log(`From: ${message.sender}`);
|
|
84
|
+
console.log(`To: ${message.recipient}`);
|
|
85
|
+
console.log(`Date: ${message.createdAt.toLocaleString()}`);
|
|
86
|
+
console.log(`Subject: ${subject}`);
|
|
87
|
+
if (message.encrypted) {
|
|
88
|
+
console.log(`🔐 Encrypted message`);
|
|
89
|
+
}
|
|
90
|
+
console.log('━'.repeat(60));
|
|
91
|
+
console.log('');
|
|
92
|
+
console.log(body);
|
|
93
|
+
console.log('');
|
|
94
|
+
console.log('━'.repeat(60));
|
|
95
|
+
console.log(`ID: ${message.id}`);
|
|
96
|
+
console.log('\n💡 Reply with: mycmail reply <id> "<message>"');
|
|
97
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* send command - Send a message to another agent
|
|
3
|
+
*
|
|
4
|
+
* Messages are encrypted by default. Use --plaintext to send unencrypted.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Command } from 'commander';
|
|
8
|
+
import { loadConfig } from '../lib/config.js';
|
|
9
|
+
import {
|
|
10
|
+
loadKeyPair,
|
|
11
|
+
getKnownKey,
|
|
12
|
+
encryptMessage,
|
|
13
|
+
decodePublicKey
|
|
14
|
+
} from '../lib/crypto.js';
|
|
15
|
+
import * as storage from '../storage/supabase.js';
|
|
16
|
+
|
|
17
|
+
export function createSendCommand(): Command {
|
|
18
|
+
return new Command('send')
|
|
19
|
+
.description('Send a message to another agent (encrypted by default)')
|
|
20
|
+
.argument('<recipient>', 'Recipient agent ID')
|
|
21
|
+
.argument('<subject>', 'Message subject')
|
|
22
|
+
.option('-m, --message <body>', 'Message body (or provide via stdin)')
|
|
23
|
+
.option('-p, --plaintext', 'Send unencrypted (not recommended)')
|
|
24
|
+
.action(async (recipient: string, subject: string, options) => {
|
|
25
|
+
const config = loadConfig();
|
|
26
|
+
const sender = config.agentId;
|
|
27
|
+
|
|
28
|
+
if (sender === 'anonymous') {
|
|
29
|
+
console.error('❌ Agent ID not configured.');
|
|
30
|
+
console.error('Set MYCELIUMAIL_AGENT_ID or configure ~/.myceliumail/config.json');
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const body = options.message || subject;
|
|
35
|
+
let messageOptions;
|
|
36
|
+
let encrypted = false;
|
|
37
|
+
|
|
38
|
+
// Encrypt by default unless --plaintext is specified
|
|
39
|
+
if (!options.plaintext) {
|
|
40
|
+
const senderKeyPair = loadKeyPair(sender);
|
|
41
|
+
const recipientPubKeyB64 = getKnownKey(recipient);
|
|
42
|
+
|
|
43
|
+
if (senderKeyPair && recipientPubKeyB64) {
|
|
44
|
+
try {
|
|
45
|
+
const recipientPubKey = decodePublicKey(recipientPubKeyB64);
|
|
46
|
+
const payload = JSON.stringify({ subject, body });
|
|
47
|
+
const encryptedData = encryptMessage(payload, recipientPubKey, senderKeyPair);
|
|
48
|
+
|
|
49
|
+
messageOptions = {
|
|
50
|
+
encrypted: true,
|
|
51
|
+
ciphertext: encryptedData.ciphertext,
|
|
52
|
+
nonce: encryptedData.nonce,
|
|
53
|
+
senderPublicKey: encryptedData.senderPublicKey,
|
|
54
|
+
};
|
|
55
|
+
encrypted = true;
|
|
56
|
+
} catch (err) {
|
|
57
|
+
console.warn('⚠️ Encryption failed, sending plaintext:', (err as Error).message);
|
|
58
|
+
}
|
|
59
|
+
} else {
|
|
60
|
+
// Warn user but don't block
|
|
61
|
+
if (!senderKeyPair) {
|
|
62
|
+
console.warn('⚠️ No keypair found. Run: mycmail keygen');
|
|
63
|
+
}
|
|
64
|
+
if (!recipientPubKeyB64) {
|
|
65
|
+
console.warn(`⚠️ No public key for ${recipient}. Run: mycmail key-import ${recipient} <key>`);
|
|
66
|
+
}
|
|
67
|
+
console.warn(' Sending as plaintext (use -p to suppress this warning)\n');
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const message = await storage.sendMessage(
|
|
73
|
+
sender,
|
|
74
|
+
recipient,
|
|
75
|
+
subject,
|
|
76
|
+
body,
|
|
77
|
+
messageOptions
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
console.log(`\n✅ Message sent to ${recipient}`);
|
|
81
|
+
console.log(` ID: ${message.id}`);
|
|
82
|
+
console.log(` Subject: ${subject}`);
|
|
83
|
+
console.log(` ${encrypted ? '🔐 Encrypted' : '📨 Plaintext'}`);
|
|
84
|
+
} catch (error) {
|
|
85
|
+
console.error('❌ Failed to send message:', error);
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Watch Command
|
|
3
|
+
*
|
|
4
|
+
* Listen for new messages in real-time and show desktop notifications.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Command } from 'commander';
|
|
8
|
+
import notifier from 'node-notifier';
|
|
9
|
+
import { loadConfig } from '../lib/config.js';
|
|
10
|
+
import { subscribeToMessages, closeConnection } from '../lib/realtime.js';
|
|
11
|
+
|
|
12
|
+
export function createWatchCommand(): Command {
|
|
13
|
+
const command = new Command('watch')
|
|
14
|
+
.description('Watch for new messages in real-time')
|
|
15
|
+
.option('-a, --agent <id>', 'Agent ID to watch (default: current agent)')
|
|
16
|
+
.option('-q, --quiet', 'Suppress console output, only show notifications')
|
|
17
|
+
.action(async (options) => {
|
|
18
|
+
const config = loadConfig();
|
|
19
|
+
const agentId = options.agent || config.agentId;
|
|
20
|
+
|
|
21
|
+
if (!options.quiet) {
|
|
22
|
+
console.log(`\n🍄 Watching inbox for ${agentId}...`);
|
|
23
|
+
console.log('Press Ctrl+C to stop\n');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const channel = subscribeToMessages(
|
|
27
|
+
agentId,
|
|
28
|
+
(message) => {
|
|
29
|
+
// Show console output
|
|
30
|
+
if (!options.quiet) {
|
|
31
|
+
const time = new Date(message.created_at).toLocaleTimeString();
|
|
32
|
+
console.log(`📬 [${time}] New message from ${message.from_agent}`);
|
|
33
|
+
console.log(` Subject: ${message.subject}`);
|
|
34
|
+
if (!message.encrypted) {
|
|
35
|
+
const preview = message.message.length > 80
|
|
36
|
+
? message.message.substring(0, 80) + '...'
|
|
37
|
+
: message.message;
|
|
38
|
+
console.log(` Preview: ${preview}`);
|
|
39
|
+
} else {
|
|
40
|
+
console.log(` 🔒 [Encrypted]`);
|
|
41
|
+
}
|
|
42
|
+
console.log();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Show desktop notification
|
|
46
|
+
const preview = message.encrypted
|
|
47
|
+
? '🔒 Encrypted message'
|
|
48
|
+
: message.message.length > 100
|
|
49
|
+
? message.message.substring(0, 100) + '...'
|
|
50
|
+
: message.message;
|
|
51
|
+
|
|
52
|
+
notifier.notify({
|
|
53
|
+
title: `📬 ${message.from_agent}: ${message.subject}`,
|
|
54
|
+
message: preview,
|
|
55
|
+
sound: true,
|
|
56
|
+
wait: false,
|
|
57
|
+
});
|
|
58
|
+
},
|
|
59
|
+
(status, error) => {
|
|
60
|
+
if (!options.quiet) {
|
|
61
|
+
switch (status) {
|
|
62
|
+
case 'SUBSCRIBED':
|
|
63
|
+
console.log('✅ Connected to Supabase Realtime\n');
|
|
64
|
+
break;
|
|
65
|
+
case 'CLOSED':
|
|
66
|
+
console.log('🔌 Connection closed');
|
|
67
|
+
break;
|
|
68
|
+
case 'CHANNEL_ERROR':
|
|
69
|
+
console.error('❌ Channel error:', error?.message);
|
|
70
|
+
break;
|
|
71
|
+
case 'TIMED_OUT':
|
|
72
|
+
console.error('⏱️ Connection timed out');
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
if (!channel) {
|
|
80
|
+
console.error('❌ Failed to start watching. Check your Supabase configuration.');
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Handle graceful shutdown
|
|
85
|
+
const cleanup = async () => {
|
|
86
|
+
if (!options.quiet) {
|
|
87
|
+
console.log('\n👋 Stopping watch...');
|
|
88
|
+
}
|
|
89
|
+
await closeConnection();
|
|
90
|
+
process.exit(0);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
process.on('SIGINT', cleanup);
|
|
94
|
+
process.on('SIGTERM', cleanup);
|
|
95
|
+
|
|
96
|
+
// Keep the process running
|
|
97
|
+
await new Promise(() => { });
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
return command;
|
|
101
|
+
}
|