myceliumail 1.0.9 → 1.0.13
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/.mappersan/outbox.json +15 -0
- package/.spidersan/registry.json +39 -0
- package/CHANGELOG.md +29 -0
- package/CLAUDE.md +29 -0
- package/README.md +23 -2
- package/dist/bin/myceliumail.js +17 -1
- package/dist/bin/myceliumail.js.map +1 -1
- package/dist/commands/close.d.ts +9 -0
- package/dist/commands/close.d.ts.map +1 -0
- package/dist/commands/close.js +153 -0
- package/dist/commands/close.js.map +1 -0
- package/dist/commands/collab.d.ts +8 -0
- package/dist/commands/collab.d.ts.map +1 -0
- package/dist/commands/collab.js +112 -0
- package/dist/commands/collab.js.map +1 -0
- package/dist/commands/inbox.d.ts.map +1 -1
- package/dist/commands/inbox.js +105 -26
- package/dist/commands/inbox.js.map +1 -1
- package/dist/commands/tags.d.ts +6 -0
- package/dist/commands/tags.d.ts.map +1 -0
- package/dist/commands/tags.js +90 -0
- package/dist/commands/tags.js.map +1 -0
- package/dist/commands/wake.d.ts +9 -0
- package/dist/commands/wake.d.ts.map +1 -0
- package/dist/commands/wake.js +198 -0
- package/dist/commands/wake.js.map +1 -0
- package/dist/dashboard/public/app.js +117 -0
- package/dist/dashboard/public/index.html +63 -5
- package/dist/dashboard/routes.d.ts.map +1 -1
- package/dist/dashboard/routes.js +31 -2
- package/dist/dashboard/routes.js.map +1 -1
- package/dist/lib/update-check.d.ts.map +1 -1
- package/dist/lib/update-check.js +6 -4
- package/dist/lib/update-check.js.map +1 -1
- package/dist/lib/watson-digest.d.ts +40 -0
- package/dist/lib/watson-digest.d.ts.map +1 -0
- package/dist/lib/watson-digest.js +164 -0
- package/dist/lib/watson-digest.js.map +1 -0
- package/dist/storage/supabase.d.ts +4 -0
- package/dist/storage/supabase.d.ts.map +1 -1
- package/dist/storage/supabase.js +57 -0
- package/dist/storage/supabase.js.map +1 -1
- package/docs/COLLAB_mappersan_mycmail_setup.md +115 -0
- package/docs/COLLAB_wake_close_commands.md +518 -0
- package/docs/CROSS_TOOL_INTEGRATION_PLAN.md +246 -0
- package/docs/JSON_SCHEMA_WAKE_CLOSE.md +246 -0
- package/docs/MYCMAIL_QUICKSTART.md +103 -0
- package/docs/WAKE_AGENTS_SHARED_DOC.md +1215 -0
- package/mcp-server/README.md +75 -69
- package/mcp-server/package-lock.json +2 -2
- package/mcp-server/package.json +5 -1
- package/mcp-server/postinstall.js +14 -0
- package/mcp-server/src/server.ts +39 -0
- package/mobile-app/README.md +36 -0
- package/mobile-app/app/compose/page.tsx +140 -0
- package/mobile-app/app/favicon.ico +0 -0
- package/mobile-app/app/globals.css +26 -0
- package/mobile-app/app/layout.tsx +42 -0
- package/mobile-app/app/message/[id]/page.tsx +126 -0
- package/mobile-app/app/page.tsx +131 -0
- package/mobile-app/components/MessageCard.tsx +60 -0
- package/mobile-app/eslint.config.mjs +18 -0
- package/mobile-app/lib/supabase.ts +87 -0
- package/mobile-app/next.config.ts +7 -0
- package/mobile-app/package-lock.json +6674 -0
- package/mobile-app/package.json +27 -0
- package/mobile-app/postcss.config.mjs +7 -0
- package/mobile-app/public/file.svg +1 -0
- package/mobile-app/public/globe.svg +1 -0
- package/mobile-app/public/next.svg +1 -0
- package/mobile-app/public/vercel.svg +1 -0
- package/mobile-app/public/window.svg +1 -0
- package/mobile-app/tsconfig.json +34 -0
- package/package.json +2 -1
- package/postinstall.js +14 -0
- package/src/bin/myceliumail.ts +19 -1
- package/src/commands/close.ts +172 -0
- package/src/commands/collab.ts +125 -0
- package/src/commands/inbox.ts +120 -29
- package/src/commands/tags.ts +102 -0
- package/src/commands/wake.ts +228 -0
- package/src/dashboard/public/app.js +117 -0
- package/src/dashboard/public/index.html +63 -5
- package/src/dashboard/routes.ts +31 -2
- package/src/lib/update-check.ts +7 -4
- package/src/lib/watson-digest.ts +217 -0
- package/src/storage/supabase.ts +71 -0
- package/vscode-extension/README.md +107 -0
- package/vscode-extension/package-lock.json +1941 -0
- package/vscode-extension/package.json +117 -0
- package/vscode-extension/src/chatParticipant.ts +179 -0
- package/vscode-extension/src/extension.ts +262 -0
- package/vscode-extension/src/handlers.ts +265 -0
- package/vscode-extension/src/realtime.ts +302 -0
- package/vscode-extension/src/types.ts +41 -0
- package/vscode-extension/tsconfig.json +26 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect } from 'react';
|
|
4
|
+
import { useParams, useRouter } from 'next/navigation';
|
|
5
|
+
import { supabase, Message, markAsRead } from '@/lib/supabase';
|
|
6
|
+
import Link from 'next/link';
|
|
7
|
+
|
|
8
|
+
export default function MessageDetailPage() {
|
|
9
|
+
const params = useParams();
|
|
10
|
+
const router = useRouter();
|
|
11
|
+
const messageId = params.id as string;
|
|
12
|
+
|
|
13
|
+
const [message, setMessage] = useState<Message | null>(null);
|
|
14
|
+
const [loading, setLoading] = useState(true);
|
|
15
|
+
const [error, setError] = useState<string | null>(null);
|
|
16
|
+
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
if (!messageId) return;
|
|
19
|
+
|
|
20
|
+
const loadMessage = async () => {
|
|
21
|
+
const { data, error: fetchError } = await supabase
|
|
22
|
+
.from('agent_messages')
|
|
23
|
+
.select('*')
|
|
24
|
+
.eq('id', messageId)
|
|
25
|
+
.single();
|
|
26
|
+
|
|
27
|
+
if (fetchError) {
|
|
28
|
+
setError('Failed to load message');
|
|
29
|
+
setLoading(false);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
setMessage(data);
|
|
34
|
+
setLoading(false);
|
|
35
|
+
|
|
36
|
+
// Mark as read
|
|
37
|
+
if (!data.read) {
|
|
38
|
+
markAsRead(messageId);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
loadMessage();
|
|
43
|
+
}, [messageId]);
|
|
44
|
+
|
|
45
|
+
const handleReply = () => {
|
|
46
|
+
if (!message) return;
|
|
47
|
+
router.push(`/compose?to=${message.from_agent}&subject=Re: ${encodeURIComponent(message.subject || '')}`);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
if (loading) {
|
|
51
|
+
return (
|
|
52
|
+
<div className="min-h-screen bg-gray-950 text-white flex items-center justify-center">
|
|
53
|
+
<div className="text-center">
|
|
54
|
+
<div className="text-4xl mb-4 animate-pulse">🍄</div>
|
|
55
|
+
<p className="text-gray-500">Loading message...</p>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (error || !message) {
|
|
62
|
+
return (
|
|
63
|
+
<div className="min-h-screen bg-gray-950 text-white flex items-center justify-center">
|
|
64
|
+
<div className="text-center">
|
|
65
|
+
<div className="text-4xl mb-4">❌</div>
|
|
66
|
+
<p className="text-red-400">{error || 'Message not found'}</p>
|
|
67
|
+
<Link href="/" className="mt-4 inline-block px-4 py-2 bg-gray-800 rounded-lg">
|
|
68
|
+
Back to Inbox
|
|
69
|
+
</Link>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<div className="min-h-screen bg-gray-950 text-white">
|
|
77
|
+
{/* Header */}
|
|
78
|
+
<header className="sticky top-0 z-10 bg-gray-950/90 backdrop-blur-sm border-b border-gray-800 px-4 py-4">
|
|
79
|
+
<div className="flex items-center justify-between">
|
|
80
|
+
<div className="flex items-center gap-3">
|
|
81
|
+
<Link href="/" className="text-2xl">←</Link>
|
|
82
|
+
<span className="text-xl">{message.encrypted ? '🔐' : '📨'}</span>
|
|
83
|
+
</div>
|
|
84
|
+
<button
|
|
85
|
+
onClick={handleReply}
|
|
86
|
+
className="px-4 py-2 bg-purple-600 text-white text-sm rounded-lg font-medium hover:bg-purple-500 transition-colors"
|
|
87
|
+
>
|
|
88
|
+
↩️ Reply
|
|
89
|
+
</button>
|
|
90
|
+
</div>
|
|
91
|
+
</header>
|
|
92
|
+
|
|
93
|
+
{/* Message Content */}
|
|
94
|
+
<main className="px-4 py-6">
|
|
95
|
+
{/* Meta info */}
|
|
96
|
+
<div className="mb-6 p-4 bg-gray-900 rounded-xl border border-gray-800">
|
|
97
|
+
<div className="grid grid-cols-2 gap-4 text-sm">
|
|
98
|
+
<div>
|
|
99
|
+
<span className="text-gray-400">From</span>
|
|
100
|
+
<p className="text-blue-400 font-mono text-lg">{message.from_agent}</p>
|
|
101
|
+
</div>
|
|
102
|
+
<div>
|
|
103
|
+
<span className="text-gray-400">To</span>
|
|
104
|
+
<p className="text-green-400 font-mono text-lg">{message.to_agent}</p>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
<div className="mt-3 text-gray-500 text-sm">
|
|
108
|
+
{new Date(message.created_at).toLocaleString()}
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
|
|
112
|
+
{/* Subject */}
|
|
113
|
+
<h1 className="text-2xl font-bold mb-6">
|
|
114
|
+
{message.subject || '(no subject)'}
|
|
115
|
+
</h1>
|
|
116
|
+
|
|
117
|
+
{/* Body */}
|
|
118
|
+
<div className="p-4 bg-gray-900 rounded-xl border border-gray-800">
|
|
119
|
+
<p className="whitespace-pre-wrap text-gray-300 leading-relaxed">
|
|
120
|
+
{message.message}
|
|
121
|
+
</p>
|
|
122
|
+
</div>
|
|
123
|
+
</main>
|
|
124
|
+
</div>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
4
|
+
import { supabase, getInbox, getAgentKeys, Message } from '@/lib/supabase';
|
|
5
|
+
import MessageCard from '@/components/MessageCard';
|
|
6
|
+
import Link from 'next/link';
|
|
7
|
+
|
|
8
|
+
// Default agents if we can't fetch from agent_keys
|
|
9
|
+
const DEFAULT_AGENTS = ['treebird', 'wsan', 'ssan', 'mycm', 'antigravity'];
|
|
10
|
+
|
|
11
|
+
export default function InboxPage() {
|
|
12
|
+
const [messages, setMessages] = useState<Message[]>([]);
|
|
13
|
+
const [loading, setLoading] = useState(true);
|
|
14
|
+
const [agents, setAgents] = useState<string[]>(DEFAULT_AGENTS);
|
|
15
|
+
const [error, setError] = useState<string | null>(null);
|
|
16
|
+
|
|
17
|
+
const loadInbox = useCallback(async () => {
|
|
18
|
+
try {
|
|
19
|
+
// Try to get agents from agent_keys table
|
|
20
|
+
const agentKeys = await getAgentKeys();
|
|
21
|
+
const agentIds = agentKeys.length > 0 ? agentKeys : DEFAULT_AGENTS;
|
|
22
|
+
setAgents(agentIds);
|
|
23
|
+
|
|
24
|
+
const inbox = await getInbox(agentIds, 50);
|
|
25
|
+
setMessages(inbox);
|
|
26
|
+
setError(null);
|
|
27
|
+
} catch (err) {
|
|
28
|
+
console.error('Failed to load inbox:', err);
|
|
29
|
+
setError('Failed to load messages');
|
|
30
|
+
} finally {
|
|
31
|
+
setLoading(false);
|
|
32
|
+
}
|
|
33
|
+
}, []);
|
|
34
|
+
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
loadInbox();
|
|
37
|
+
|
|
38
|
+
// Set up real-time subscription
|
|
39
|
+
const channel = supabase
|
|
40
|
+
.channel('mobile-inbox')
|
|
41
|
+
.on(
|
|
42
|
+
'postgres_changes',
|
|
43
|
+
{
|
|
44
|
+
event: 'INSERT',
|
|
45
|
+
schema: 'public',
|
|
46
|
+
table: 'agent_messages',
|
|
47
|
+
},
|
|
48
|
+
(payload) => {
|
|
49
|
+
const newMsg = payload.new as Message;
|
|
50
|
+
// Check if this message is for one of our agents
|
|
51
|
+
if (agents.includes(newMsg.to_agent)) {
|
|
52
|
+
setMessages(prev => [newMsg, ...prev]);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
)
|
|
56
|
+
.subscribe();
|
|
57
|
+
|
|
58
|
+
return () => {
|
|
59
|
+
supabase.removeChannel(channel);
|
|
60
|
+
};
|
|
61
|
+
}, [loadInbox, agents]);
|
|
62
|
+
|
|
63
|
+
const unreadCount = messages.filter(m => !m.read).length;
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<div className="min-h-screen bg-gray-950 text-white">
|
|
67
|
+
{/* Header */}
|
|
68
|
+
<header className="sticky top-0 z-10 bg-gray-950/90 backdrop-blur-sm border-b border-gray-800 px-4 py-4">
|
|
69
|
+
<div className="flex items-center justify-between">
|
|
70
|
+
<div className="flex items-center gap-3">
|
|
71
|
+
<span className="text-3xl">🍄</span>
|
|
72
|
+
<h1 className="text-xl font-bold bg-gradient-to-r from-purple-400 to-pink-500 bg-clip-text text-transparent">
|
|
73
|
+
Myceliumail
|
|
74
|
+
</h1>
|
|
75
|
+
</div>
|
|
76
|
+
<div className="flex items-center gap-2">
|
|
77
|
+
<button
|
|
78
|
+
onClick={() => { setLoading(true); loadInbox(); }}
|
|
79
|
+
className="p-2 rounded-lg hover:bg-gray-800 transition-colors"
|
|
80
|
+
>
|
|
81
|
+
🔄
|
|
82
|
+
</button>
|
|
83
|
+
<Link
|
|
84
|
+
href="/compose"
|
|
85
|
+
className="px-4 py-2 bg-purple-600 text-white text-sm rounded-lg font-medium hover:bg-purple-500 transition-colors"
|
|
86
|
+
>
|
|
87
|
+
✉️ Compose
|
|
88
|
+
</Link>
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
<div className="mt-2 text-sm text-gray-400">
|
|
92
|
+
{unreadCount > 0 ? (
|
|
93
|
+
<span className="text-blue-400">{unreadCount} unread</span>
|
|
94
|
+
) : (
|
|
95
|
+
<span>No unread messages</span>
|
|
96
|
+
)} · {messages.length} total
|
|
97
|
+
</div>
|
|
98
|
+
</header>
|
|
99
|
+
|
|
100
|
+
{/* Message List */}
|
|
101
|
+
<main className="px-4 py-4 space-y-3">
|
|
102
|
+
{loading ? (
|
|
103
|
+
<div className="text-center py-12 text-gray-500">
|
|
104
|
+
<div className="text-4xl mb-4 animate-pulse">🍄</div>
|
|
105
|
+
<p>Loading messages...</p>
|
|
106
|
+
</div>
|
|
107
|
+
) : error ? (
|
|
108
|
+
<div className="text-center py-12 text-red-400">
|
|
109
|
+
<div className="text-4xl mb-4">❌</div>
|
|
110
|
+
<p>{error}</p>
|
|
111
|
+
<button
|
|
112
|
+
onClick={loadInbox}
|
|
113
|
+
className="mt-4 px-4 py-2 bg-gray-800 rounded-lg"
|
|
114
|
+
>
|
|
115
|
+
Retry
|
|
116
|
+
</button>
|
|
117
|
+
</div>
|
|
118
|
+
) : messages.length === 0 ? (
|
|
119
|
+
<div className="text-center py-12 text-gray-500">
|
|
120
|
+
<div className="text-4xl mb-4">📭</div>
|
|
121
|
+
<p>No messages yet</p>
|
|
122
|
+
</div>
|
|
123
|
+
) : (
|
|
124
|
+
messages.map(message => (
|
|
125
|
+
<MessageCard key={message.id} message={message} />
|
|
126
|
+
))
|
|
127
|
+
)}
|
|
128
|
+
</main>
|
|
129
|
+
</div>
|
|
130
|
+
);
|
|
131
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Message } from '@/lib/supabase';
|
|
4
|
+
import Link from 'next/link';
|
|
5
|
+
|
|
6
|
+
interface MessageCardProps {
|
|
7
|
+
message: Message;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export default function MessageCard({ message }: MessageCardProps) {
|
|
11
|
+
const isUnread = !message.read;
|
|
12
|
+
const timeAgo = formatTimeAgo(new Date(message.created_at));
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<Link href={`/message/${message.id}`}>
|
|
16
|
+
<div className={`
|
|
17
|
+
p-4 rounded-xl border transition-all active:scale-[0.98]
|
|
18
|
+
${isUnread
|
|
19
|
+
? 'bg-gray-800/50 border-gray-700'
|
|
20
|
+
: 'bg-gray-900 border-gray-800'
|
|
21
|
+
}
|
|
22
|
+
`}>
|
|
23
|
+
<div className="flex items-center justify-between mb-2">
|
|
24
|
+
<div className="flex items-center gap-2">
|
|
25
|
+
<span className="text-lg">
|
|
26
|
+
{message.encrypted ? '🔐' : '📨'}
|
|
27
|
+
</span>
|
|
28
|
+
<span className={`font-medium ${isUnread ? 'text-white' : 'text-gray-300'}`}>
|
|
29
|
+
{message.from_agent}
|
|
30
|
+
</span>
|
|
31
|
+
<span className="text-gray-500 text-sm">→ {message.to_agent}</span>
|
|
32
|
+
</div>
|
|
33
|
+
{isUnread && (
|
|
34
|
+
<span className="w-2 h-2 rounded-full bg-blue-500" />
|
|
35
|
+
)}
|
|
36
|
+
</div>
|
|
37
|
+
<div className={`text-base truncate mb-1 ${isUnread ? 'text-gray-100 font-medium' : 'text-gray-400'}`}>
|
|
38
|
+
{message.subject || '(no subject)'}
|
|
39
|
+
</div>
|
|
40
|
+
<div className="text-sm text-gray-500">
|
|
41
|
+
{timeAgo}
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
</Link>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function formatTimeAgo(date: Date): string {
|
|
49
|
+
const now = new Date();
|
|
50
|
+
const diffMs = now.getTime() - date.getTime();
|
|
51
|
+
const diffMins = Math.floor(diffMs / 60000);
|
|
52
|
+
const diffHours = Math.floor(diffMs / 3600000);
|
|
53
|
+
const diffDays = Math.floor(diffMs / 86400000);
|
|
54
|
+
|
|
55
|
+
if (diffMins < 1) return 'just now';
|
|
56
|
+
if (diffMins < 60) return `${diffMins}m ago`;
|
|
57
|
+
if (diffHours < 24) return `${diffHours}h ago`;
|
|
58
|
+
if (diffDays < 7) return `${diffDays}d ago`;
|
|
59
|
+
return date.toLocaleDateString();
|
|
60
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { defineConfig, globalIgnores } from "eslint/config";
|
|
2
|
+
import nextVitals from "eslint-config-next/core-web-vitals";
|
|
3
|
+
import nextTs from "eslint-config-next/typescript";
|
|
4
|
+
|
|
5
|
+
const eslintConfig = defineConfig([
|
|
6
|
+
...nextVitals,
|
|
7
|
+
...nextTs,
|
|
8
|
+
// Override default ignores of eslint-config-next.
|
|
9
|
+
globalIgnores([
|
|
10
|
+
// Default ignores of eslint-config-next:
|
|
11
|
+
".next/**",
|
|
12
|
+
"out/**",
|
|
13
|
+
"build/**",
|
|
14
|
+
"next-env.d.ts",
|
|
15
|
+
]),
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
export default eslintConfig;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { createClient } from '@supabase/supabase-js';
|
|
2
|
+
|
|
3
|
+
// Supabase configuration - using the same credentials as mycmail
|
|
4
|
+
const SUPABASE_URL = process.env.NEXT_PUBLIC_SUPABASE_URL || 'https://ruvwundetxnzesrbkdzr.supabase.co';
|
|
5
|
+
const SUPABASE_ANON_KEY = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || '';
|
|
6
|
+
|
|
7
|
+
export const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
|
|
8
|
+
realtime: {
|
|
9
|
+
params: { eventsPerSecond: 10 }
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
export interface Message {
|
|
14
|
+
id: string;
|
|
15
|
+
from_agent: string;
|
|
16
|
+
to_agent: string;
|
|
17
|
+
subject: string;
|
|
18
|
+
message: string;
|
|
19
|
+
encrypted: boolean;
|
|
20
|
+
read: boolean;
|
|
21
|
+
created_at: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function getInbox(agentIds: string[], limit = 50): Promise<Message[]> {
|
|
25
|
+
const { data, error } = await supabase
|
|
26
|
+
.from('agent_messages')
|
|
27
|
+
.select('*')
|
|
28
|
+
.in('to_agent', agentIds)
|
|
29
|
+
.order('created_at', { ascending: false })
|
|
30
|
+
.limit(limit);
|
|
31
|
+
|
|
32
|
+
if (error) {
|
|
33
|
+
console.error('Failed to fetch inbox:', error);
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return data || [];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function sendMessage(
|
|
41
|
+
from: string,
|
|
42
|
+
to: string,
|
|
43
|
+
subject: string,
|
|
44
|
+
body: string
|
|
45
|
+
): Promise<Message | null> {
|
|
46
|
+
const { data, error } = await supabase
|
|
47
|
+
.from('agent_messages')
|
|
48
|
+
.insert({
|
|
49
|
+
from_agent: from,
|
|
50
|
+
to_agent: to,
|
|
51
|
+
subject,
|
|
52
|
+
message: body,
|
|
53
|
+
encrypted: false,
|
|
54
|
+
read: false,
|
|
55
|
+
})
|
|
56
|
+
.select()
|
|
57
|
+
.single();
|
|
58
|
+
|
|
59
|
+
if (error) {
|
|
60
|
+
console.error('Failed to send message:', error);
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return data;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export async function markAsRead(messageId: string): Promise<boolean> {
|
|
68
|
+
const { error } = await supabase
|
|
69
|
+
.from('agent_messages')
|
|
70
|
+
.update({ read: true })
|
|
71
|
+
.eq('id', messageId);
|
|
72
|
+
|
|
73
|
+
return !error;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export async function getAgentKeys(): Promise<string[]> {
|
|
77
|
+
const { data, error } = await supabase
|
|
78
|
+
.from('agent_keys')
|
|
79
|
+
.select('agent_id');
|
|
80
|
+
|
|
81
|
+
if (error || !data) {
|
|
82
|
+
console.error('Failed to fetch agent keys:', error);
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return data.map(row => row.agent_id);
|
|
87
|
+
}
|