commune-ai 0.1.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/README.md ADDED
@@ -0,0 +1,245 @@
1
+ # commune-ai
2
+
3
+ **Email infrastructure for AI agents**
4
+
5
+ Build agents that receive emails, reply in threads, access conversation history, and search across all organizational emails. Most teams get their first agent responding in **~15 minutes**.
6
+
7
+ ## What you get
8
+
9
+ 1. **Webhook Integration** - Receive emails instantly as structured data
10
+ 2. **Thread-aware Replies** - Reply in email conversations with full context
11
+ 3. **User History** - Access complete conversation history per user
12
+ 4. **Organization Search** - Semantic search across all emails (coming soon)
13
+
14
+ > By default, the SDK talks to the hosted Commune API. If you self‑host,
15
+ > pass `baseUrl` to the client.
16
+
17
+ ---
18
+
19
+ ## Install
20
+ ```bash
21
+ npm install commune-ai
22
+ # or
23
+ yarn add commune-ai
24
+ # or
25
+ pnpm add commune-ai
26
+ ```
27
+
28
+ ---
29
+
30
+ ## Receive emails instantly
31
+
32
+ Get emails delivered to your agent as structured webhook data.
33
+
34
+ ```ts
35
+ import express from "express";
36
+ import { CommuneClient, createWebhookHandler } from "commune-ai";
37
+
38
+ const client = new CommuneClient({
39
+ apiKey: process.env.COMMUNE_API_KEY,
40
+ });
41
+
42
+ const handler = createWebhookHandler({
43
+ onEvent: async (message, context) => {
44
+ // Every email arrives as structured data:
45
+ // message = {
46
+ // channel: "email",
47
+ // conversation_id: "thread_id",
48
+ // participants: [{ role: "sender", identity: "user@example.com" }],
49
+ // content: "Can you help with pricing?",
50
+ // metadata: { subject: "Pricing Question", ... }
51
+ // }
52
+
53
+ console.log(`New email: ${message.content}`);
54
+
55
+ // Your agent logic here
56
+ const reply = `Thanks for your email! I'll help with that.`;
57
+
58
+ // Reply in the same email thread
59
+ const sender = message.participants.find(p => p.role === "sender")?.identity;
60
+ if (sender) {
61
+ await client.messages.send({
62
+ to: sender,
63
+ text: reply,
64
+ conversation_id: message.conversation_id,
65
+ domainId: context.payload.domainId,
66
+ inboxId: context.payload.inboxId,
67
+ });
68
+ }
69
+ },
70
+ });
71
+
72
+ const app = express();
73
+ app.post("/webhook", express.raw({ type: "*/*" }), handler);
74
+ app.listen(3000, () => console.log("Agent listening on :3000"));
75
+ ```
76
+
77
+ **Email structure:**
78
+ ```ts
79
+ interface UnifiedMessage {
80
+ channel: "email";
81
+ message_id: string;
82
+ conversation_id: string; // email thread ID
83
+ participants: Array<{
84
+ role: "sender" | "to" | "cc" | "bcc";
85
+ identity: string; // email address
86
+ }>;
87
+ content: string;
88
+ content_html?: string;
89
+ metadata: {
90
+ subject?: string;
91
+ created_at: string;
92
+ // ... more email metadata
93
+ };
94
+ }
95
+ ```
96
+
97
+ ---
98
+
99
+ ## Reply in email threads
100
+
101
+ Keep conversations organized by replying in the same email thread.
102
+
103
+ ```ts
104
+ import express from "express";
105
+ import { CommuneClient, createWebhookHandler } from "commune-ai";
106
+
107
+ const client = new CommuneClient({
108
+ apiKey: process.env.COMMUNE_API_KEY,
109
+ });
110
+
111
+ const handler = createWebhookHandler({
112
+ onEvent: async (message, context) => {
113
+ const sender = message.participants.find(p => p.role === "sender")?.identity;
114
+ if (!sender) return;
115
+
116
+ // Reply maintains the email thread
117
+ await client.messages.send({
118
+ to: sender,
119
+ text: "Thanks for your question! Here's what I can help with...",
120
+ conversation_id: message.conversation_id, // keeps it in thread
121
+ subject: `Re: ${message.metadata.subject}`, // thread subject
122
+ domainId: context.payload.domainId,
123
+ inboxId: context.payload.inboxId,
124
+ });
125
+ },
126
+ });
127
+
128
+ const app = express();
129
+ app.post("/webhook", express.raw({ type: "*/*" }), handler);
130
+ app.listen(3000);
131
+ ```
132
+
133
+ ---
134
+
135
+ ---
136
+
137
+ ## Access conversation history
138
+
139
+ Give your agent full context by accessing different types of email history.
140
+
141
+ ```ts
142
+ import { CommuneClient } from "commune-ai";
143
+ const client = new CommuneClient({
144
+ apiKey: process.env.COMMUNE_API_KEY,
145
+ });
146
+
147
+ // 1) Thread history - all messages in current conversation
148
+ const threadMessages = await client.messages.listByConversation(
149
+ message.conversation_id,
150
+ { order: "asc", limit: 20 }
151
+ );
152
+
153
+ // 2) User history - all emails from/to a specific person
154
+ const userEmails = await client.messages.list({
155
+ sender: "customer@example.com",
156
+ limit: 50,
157
+ order: "desc", // newest first
158
+ });
159
+
160
+ // 3) Inbox history - all emails in your domain
161
+ const inboxEmails = await client.messages.list({
162
+ inbox_id: "inbox_123",
163
+ limit: 100,
164
+ });
165
+
166
+ // 4) Organization search - semantic search across all emails (coming soon)
167
+ const searchResults = await client.search({
168
+ query: "pricing questions from last week",
169
+ limit: 10,
170
+ threshold: 0.8,
171
+ });
172
+
173
+ console.log(`Found ${searchResults.length} relevant emails`);
174
+ for (const result of searchResults) {
175
+ console.log(`Match (${result.similarity.toFixed(2)}): ${result.message.content}`);
176
+ }
177
+ ```
178
+
179
+ ---
180
+
181
+ ## Complete agent example
182
+
183
+ A production-ready agent that receives emails, accesses conversation history, and provides contextual responses.
184
+
185
+ ```ts
186
+ import "dotenv/config";
187
+ import express from "express";
188
+ import { CommuneClient, createWebhookHandler } from "commune-ai";
189
+
190
+ const client = new CommuneClient({
191
+ apiKey: process.env.COMMUNE_API_KEY,
192
+ });
193
+
194
+ const handler = createWebhookHandler({
195
+ onEvent: async (message, context) => {
196
+ const sender = message.participants.find(p => p.role === "sender")?.identity;
197
+ if (!sender) return;
198
+
199
+ // Get conversation history for context
200
+ const threadHistory = await client.messages.listByConversation(
201
+ message.conversation_id,
202
+ { order: "asc", limit: 10 }
203
+ );
204
+
205
+ // Get user's full email history
206
+ const userHistory = await client.messages.list({
207
+ sender,
208
+ limit: 20,
209
+ order: "desc",
210
+ });
211
+
212
+ // Analyze the conversation
213
+ const isFollowUp = threadHistory.length > 1;
214
+ const userEmailCount = userHistory.length;
215
+
216
+ // Generate contextual response
217
+ let response = "";
218
+ if (isFollowUp) {
219
+ response = `Welcome back! I see this is a follow-up to our conversation. `;
220
+ } else if (userEmailCount > 1) {
221
+ response = `I see you've emailed us before. `;
222
+ }
223
+
224
+ response += `Regarding "${message.content.substring(0, 50)}..." - I'll help you with that.`;
225
+
226
+ // Send contextual reply
227
+ await client.messages.send({
228
+ to: sender,
229
+ text: response,
230
+ conversation_id: message.conversation_id,
231
+ subject: `Re: ${message.metadata.subject}`,
232
+ domainId: context.payload.domainId,
233
+ inboxId: context.payload.inboxId,
234
+ });
235
+ },
236
+ });
237
+
238
+ const app = express();
239
+ app.post("/webhook", express.raw({ type: "*/*" }), handler);
240
+
241
+ // Health check endpoint
242
+ app.get("/health", (req, res) => res.json({ status: "ok" }));
243
+
244
+ app.listen(3000, () => console.log("Agent running on port 3000"));
245
+ ```
@@ -0,0 +1,59 @@
1
+ import type { AttachmentRecord, ConversationListParams, CreateDomainPayload, DomainEntry, InboxEntry, MessageListParams, SemanticSearchParams, SemanticSearchResult, SendMessagePayload, UnifiedMessage } from './types.js';
2
+ export type ClientOptions = {
3
+ baseUrl?: string;
4
+ apiKey: string;
5
+ headers?: Record<string, string>;
6
+ fetcher?: typeof fetch;
7
+ };
8
+ export declare class CommuneClient {
9
+ private baseUrl;
10
+ private apiKey;
11
+ private headers?;
12
+ private fetcher;
13
+ constructor(options: ClientOptions);
14
+ private request;
15
+ domains: {
16
+ list: () => Promise<DomainEntry[]>;
17
+ create: (payload: CreateDomainPayload) => Promise<Record<string, unknown>>;
18
+ get: (domainId: string) => Promise<Record<string, unknown>>;
19
+ verify: (domainId: string) => Promise<Record<string, unknown>>;
20
+ records: (domainId: string) => Promise<unknown[]>;
21
+ status: (domainId: string) => Promise<Record<string, unknown>>;
22
+ createWebhook: (domainId: string, payload?: {
23
+ endpoint?: string;
24
+ events?: string[];
25
+ }) => Promise<Record<string, unknown>>;
26
+ saveWebhookSecret: (domainId: string, secret: string) => Promise<Record<string, unknown>>;
27
+ };
28
+ inboxes: {
29
+ list: (domainId: string) => Promise<InboxEntry[]>;
30
+ create: (domainId: string, payload: {
31
+ localPart: string;
32
+ agent?: InboxEntry["agent"];
33
+ webhook?: InboxEntry["webhook"];
34
+ status?: string;
35
+ }) => Promise<InboxEntry>;
36
+ update: (domainId: string, inboxId: string, payload: {
37
+ localPart?: string;
38
+ agent?: InboxEntry["agent"];
39
+ webhook?: InboxEntry["webhook"];
40
+ status?: string;
41
+ }) => Promise<InboxEntry>;
42
+ remove: (domainId: string, inboxId: string) => Promise<{
43
+ ok: boolean;
44
+ }>;
45
+ setWebhook: (domainId: string, inboxId: string, payload: {
46
+ endpoint: string;
47
+ events?: string[];
48
+ }) => Promise<InboxEntry>;
49
+ };
50
+ messages: {
51
+ send: (payload: SendMessagePayload) => Promise<Record<string, unknown>>;
52
+ list: (params: MessageListParams) => Promise<UnifiedMessage[]>;
53
+ listByConversation: (conversationId: string, params?: ConversationListParams) => Promise<UnifiedMessage[]>;
54
+ };
55
+ search: (params: SemanticSearchParams) => Promise<SemanticSearchResult[]>;
56
+ attachments: {
57
+ get: (attachmentId: string) => Promise<AttachmentRecord>;
58
+ };
59
+ }
package/dist/client.js ADDED
@@ -0,0 +1,153 @@
1
+ const DEFAULT_BASE_URL = 'https://web-production-3f46f.up.railway.app';
2
+ const buildQuery = (params) => {
3
+ const query = new URLSearchParams();
4
+ Object.entries(params).forEach(([key, value]) => {
5
+ if (value === undefined || value === null || value === '') {
6
+ return;
7
+ }
8
+ query.set(key, String(value));
9
+ });
10
+ const serialized = query.toString();
11
+ return serialized ? `?${serialized}` : '';
12
+ };
13
+ export class CommuneClient {
14
+ constructor(options) {
15
+ this.domains = {
16
+ list: async () => {
17
+ const response = await this.request(`/api/domains`);
18
+ if (Array.isArray(response)) {
19
+ return response;
20
+ }
21
+ return Array.isArray(response.data) ? response.data : [];
22
+ },
23
+ create: async (payload) => {
24
+ return this.request(`/api/domains`, {
25
+ method: 'POST',
26
+ json: payload,
27
+ });
28
+ },
29
+ get: async (domainId) => {
30
+ return this.request(`/api/domains/${encodeURIComponent(domainId)}`);
31
+ },
32
+ verify: async (domainId) => {
33
+ return this.request(`/api/domains/${encodeURIComponent(domainId)}/verify`, { method: 'POST' });
34
+ },
35
+ records: async (domainId) => {
36
+ return this.request(`/api/domains/${encodeURIComponent(domainId)}/records`);
37
+ },
38
+ status: async (domainId) => {
39
+ return this.request(`/api/domains/${encodeURIComponent(domainId)}/status`);
40
+ },
41
+ createWebhook: async (domainId, payload) => {
42
+ return this.request(`/api/domains/${encodeURIComponent(domainId)}/webhook`, {
43
+ method: 'POST',
44
+ json: payload,
45
+ });
46
+ },
47
+ saveWebhookSecret: async (domainId, secret) => {
48
+ return this.request(`/api/domains/${encodeURIComponent(domainId)}/webhook/secret`, { method: 'POST', json: { secret } });
49
+ },
50
+ };
51
+ this.inboxes = {
52
+ list: async (domainId) => {
53
+ return this.request(`/api/domains/${encodeURIComponent(domainId)}/inboxes`);
54
+ },
55
+ create: async (domainId, payload) => {
56
+ return this.request(`/api/domains/${encodeURIComponent(domainId)}/inboxes`, {
57
+ method: 'POST',
58
+ json: payload,
59
+ });
60
+ },
61
+ update: async (domainId, inboxId, payload) => {
62
+ return this.request(`/api/domains/${encodeURIComponent(domainId)}/inboxes/${encodeURIComponent(inboxId)}`, {
63
+ method: 'PUT',
64
+ json: payload,
65
+ });
66
+ },
67
+ remove: async (domainId, inboxId) => {
68
+ return this.request(`/api/domains/${encodeURIComponent(domainId)}/inboxes/${encodeURIComponent(inboxId)}`, { method: 'DELETE' });
69
+ },
70
+ setWebhook: async (domainId, inboxId, payload) => {
71
+ return this.request(`/api/domains/${encodeURIComponent(domainId)}/inboxes/${encodeURIComponent(inboxId)}/webhook`, { method: 'POST', json: payload });
72
+ },
73
+ };
74
+ this.messages = {
75
+ send: async (payload) => {
76
+ // Always send as email (Slack support coming soon)
77
+ const emailPayload = { ...payload, channel: 'email' };
78
+ return this.request('/api/messages/send', {
79
+ method: 'POST',
80
+ json: emailPayload,
81
+ });
82
+ },
83
+ list: async (params) => {
84
+ return this.request(`/api/messages${buildQuery({
85
+ sender: params.sender,
86
+ channel: params.channel,
87
+ before: params.before,
88
+ after: params.after,
89
+ limit: params.limit,
90
+ order: params.order,
91
+ domain_id: params.domain_id,
92
+ inbox_id: params.inbox_id,
93
+ })}`);
94
+ },
95
+ listByConversation: async (conversationId, params = {}) => {
96
+ return this.request(`/api/conversations/${encodeURIComponent(conversationId)}/messages${buildQuery({
97
+ limit: params.limit,
98
+ order: params.order,
99
+ })}`);
100
+ },
101
+ };
102
+ // Semantic search across all organizational emails (coming soon)
103
+ this.search = async (params) => {
104
+ // Placeholder for future semantic search implementation
105
+ // This will search across all emails in the organization
106
+ return this.request(`/api/search${buildQuery({
107
+ q: params.query,
108
+ limit: params.limit || 10,
109
+ threshold: params.threshold || 0.7,
110
+ before: params.before,
111
+ after: params.after,
112
+ sender: params.sender,
113
+ })}`);
114
+ };
115
+ this.attachments = {
116
+ get: async (attachmentId) => {
117
+ return this.request(`/api/attachments/${encodeURIComponent(attachmentId)}`);
118
+ },
119
+ };
120
+ if (!options.apiKey) {
121
+ throw new Error('API key is required. Get one from your Commune dashboard at https://commune.ai');
122
+ }
123
+ this.baseUrl = (options.baseUrl || DEFAULT_BASE_URL).replace(/\/$/, '');
124
+ this.apiKey = options.apiKey;
125
+ this.headers = options.headers;
126
+ this.fetcher = options.fetcher || fetch;
127
+ }
128
+ async request(path, options = {}) {
129
+ const { json, headers, ...rest } = options;
130
+ const response = await this.fetcher(`${this.baseUrl}${path}`, {
131
+ ...rest,
132
+ headers: {
133
+ 'Content-Type': 'application/json',
134
+ ...(this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {}),
135
+ ...(this.headers || {}),
136
+ ...(headers || {}),
137
+ },
138
+ body: json ? JSON.stringify(json) : rest.body,
139
+ });
140
+ const data = (await response.json().catch(() => ({})));
141
+ if (!response.ok) {
142
+ const errorValue = data?.error;
143
+ const message = errorValue?.message ||
144
+ (typeof data?.error === 'string' ? data.error : undefined) ||
145
+ response.statusText;
146
+ throw new Error(message);
147
+ }
148
+ if (data.data !== undefined) {
149
+ return data.data;
150
+ }
151
+ return data;
152
+ }
153
+ }
@@ -0,0 +1,5 @@
1
+ export { CommuneClient } from './client.js';
2
+ export type { ApiError, ApiResponse, AttachmentRecord, Channel, ConversationListParams, CreateDomainPayload, Direction, DomainEntry, DomainWebhook, InboxEntry, InboxWebhook, InboundEmailWebhookPayload, MessageListParams, MessageMetadata, Participant, ParticipantRole, SemanticSearchParams, SemanticSearchResult, SendMessagePayload, SvixHeaders, UnifiedMessage, } from './types.js';
3
+ export { verifyResendWebhook } from './webhooks.js';
4
+ export { createWebhookHandler } from './listener.js';
5
+ export type { CommuneWebhookEvent, CommuneWebhookHandlerContext, CreateWebhookHandlerOptions, } from './listener.js';
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export { CommuneClient } from './client.js';
2
+ export { verifyResendWebhook } from './webhooks.js';
3
+ // export { verifySlackWebhook } from './webhooks.js'; // Slack support coming soon
4
+ export { createWebhookHandler } from './listener.js';
@@ -0,0 +1,15 @@
1
+ import type { InboundEmailWebhookPayload, UnifiedMessage } from './types.js';
2
+ export type CommuneWebhookEvent = InboundEmailWebhookPayload;
3
+ export type CommuneWebhookHandlerContext = {
4
+ payload: CommuneWebhookEvent;
5
+ rawBody: string;
6
+ headers: Record<string, string | undefined>;
7
+ };
8
+ export type CreateWebhookHandlerOptions = {
9
+ onEvent: (message: UnifiedMessage, context: CommuneWebhookHandlerContext) => Promise<void> | void;
10
+ verify?: (input: {
11
+ rawBody: string;
12
+ headers: Record<string, string | undefined>;
13
+ }) => boolean;
14
+ };
15
+ export declare const createWebhookHandler: ({ onEvent, verify }: CreateWebhookHandlerOptions) => (req: any, res?: any) => Promise<Response | undefined>;
@@ -0,0 +1,85 @@
1
+ const collectNodeBody = async (req) => {
2
+ if (req?.body) {
3
+ if (Buffer.isBuffer(req.body)) {
4
+ return req.body.toString('utf8');
5
+ }
6
+ if (typeof req.body === 'string') {
7
+ return req.body;
8
+ }
9
+ }
10
+ return await new Promise((resolve, reject) => {
11
+ const chunks = [];
12
+ req.on('data', (chunk) => chunks.push(chunk));
13
+ req.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
14
+ req.on('error', reject);
15
+ });
16
+ };
17
+ const getHeadersFromNode = (req) => {
18
+ const headers = {};
19
+ if (!req?.headers) {
20
+ return headers;
21
+ }
22
+ for (const [key, value] of Object.entries(req.headers)) {
23
+ headers[key.toLowerCase()] = Array.isArray(value) ? value[0] : value;
24
+ }
25
+ return headers;
26
+ };
27
+ const parsePayload = (rawBody) => {
28
+ try {
29
+ return JSON.parse(rawBody);
30
+ }
31
+ catch (error) {
32
+ return null;
33
+ }
34
+ };
35
+ export const createWebhookHandler = ({ onEvent, verify }) => {
36
+ return async (req, res) => {
37
+ if (req?.method && req.method !== 'POST') {
38
+ if (res) {
39
+ res.status(405).json({ error: 'Method Not Allowed' });
40
+ return;
41
+ }
42
+ return new Response(JSON.stringify({ error: 'Method Not Allowed' }), {
43
+ status: 405,
44
+ headers: { 'Content-Type': 'application/json' },
45
+ });
46
+ }
47
+ const isWebRequest = typeof req?.text === 'function';
48
+ const rawBody = isWebRequest ? await req.text() : await collectNodeBody(req);
49
+ const headers = isWebRequest
50
+ ? {
51
+ 'x-commune-signature': req.headers.get('x-commune-signature') || undefined,
52
+ }
53
+ : getHeadersFromNode(req);
54
+ if (verify && !verify({ rawBody, headers })) {
55
+ if (res) {
56
+ res.status(401).json({ error: 'Invalid signature' });
57
+ return;
58
+ }
59
+ return new Response(JSON.stringify({ error: 'Invalid signature' }), {
60
+ status: 401,
61
+ headers: { 'Content-Type': 'application/json' },
62
+ });
63
+ }
64
+ const payload = parsePayload(rawBody);
65
+ if (!payload) {
66
+ if (res) {
67
+ res.status(400).json({ error: 'Invalid JSON payload' });
68
+ return;
69
+ }
70
+ return new Response(JSON.stringify({ error: 'Invalid JSON payload' }), {
71
+ status: 400,
72
+ headers: { 'Content-Type': 'application/json' },
73
+ });
74
+ }
75
+ await onEvent(payload.message, { payload, rawBody, headers });
76
+ if (res) {
77
+ res.json({ ok: true });
78
+ return;
79
+ }
80
+ return new Response(JSON.stringify({ ok: true }), {
81
+ status: 200,
82
+ headers: { 'Content-Type': 'application/json' },
83
+ });
84
+ };
85
+ };
@@ -0,0 +1,155 @@
1
+ export type Channel = 'email';
2
+ export type Direction = 'inbound' | 'outbound';
3
+ export type ParticipantRole = 'sender' | 'to' | 'cc' | 'bcc' | 'mentioned' | 'participant';
4
+ export interface Participant {
5
+ role: ParticipantRole;
6
+ identity: string;
7
+ }
8
+ export interface MessageMetadata {
9
+ created_at: string;
10
+ subject?: string;
11
+ in_reply_to?: string | null;
12
+ references?: string[];
13
+ is_private?: boolean;
14
+ domain_id?: string | null;
15
+ inbox_id?: string | null;
16
+ inbox_address?: string | null;
17
+ message_id?: string | null;
18
+ provider?: 'resend' | 'email' | string;
19
+ raw?: unknown;
20
+ }
21
+ export interface UnifiedMessage {
22
+ _id?: string;
23
+ channel: Channel;
24
+ message_id: string;
25
+ conversation_id: string;
26
+ direction: Direction;
27
+ participants: Participant[];
28
+ content: string;
29
+ content_html?: string | null;
30
+ attachments: string[];
31
+ created_at: string;
32
+ metadata: MessageMetadata;
33
+ }
34
+ export interface AttachmentRecord {
35
+ attachment_id: string;
36
+ message_id: string;
37
+ filename: string;
38
+ mime_type: string;
39
+ size: number;
40
+ content_base64: string | null;
41
+ source: Channel;
42
+ source_url?: string | null;
43
+ download_error?: boolean;
44
+ }
45
+ export interface DomainWebhook {
46
+ id?: string;
47
+ endpoint?: string;
48
+ events?: string[];
49
+ secret?: string | null;
50
+ }
51
+ export interface InboxWebhook {
52
+ endpoint?: string;
53
+ events?: string[];
54
+ }
55
+ export interface InboxEntry {
56
+ id: string;
57
+ localPart: string;
58
+ address?: string;
59
+ agent?: {
60
+ id?: string;
61
+ name?: string;
62
+ metadata?: Record<string, unknown>;
63
+ };
64
+ webhook?: InboxWebhook;
65
+ createdAt?: string;
66
+ status?: string;
67
+ }
68
+ export interface DomainEntry {
69
+ id: string;
70
+ name?: string;
71
+ status?: string;
72
+ region?: string;
73
+ records?: unknown[];
74
+ createdAt?: string;
75
+ webhook?: DomainWebhook;
76
+ inboxes?: InboxEntry[];
77
+ }
78
+ export interface SendMessagePayload {
79
+ conversation_id?: string;
80
+ to: string | string[];
81
+ text?: string;
82
+ html?: string;
83
+ attachments?: {
84
+ id?: string;
85
+ attachment_id?: string;
86
+ }[];
87
+ subject?: string;
88
+ cc?: string[];
89
+ bcc?: string[];
90
+ headers?: Record<string, string>;
91
+ replyTo?: string | string[];
92
+ reply_to?: string | string[];
93
+ domainId?: string;
94
+ inboxId?: string;
95
+ domain?: string;
96
+ from?: string;
97
+ localPart?: string;
98
+ }
99
+ export interface CreateDomainPayload {
100
+ name: string;
101
+ region?: string;
102
+ capabilities?: {
103
+ sending?: string;
104
+ receiving?: string;
105
+ };
106
+ createWebhook?: boolean;
107
+ }
108
+ export interface MessageListParams {
109
+ sender?: string;
110
+ channel?: Channel;
111
+ before?: string;
112
+ after?: string;
113
+ limit?: number;
114
+ order?: 'asc' | 'desc';
115
+ domain_id?: string;
116
+ inbox_id?: string;
117
+ }
118
+ export interface ConversationListParams {
119
+ limit?: number;
120
+ order?: 'asc' | 'desc';
121
+ }
122
+ export interface SvixHeaders {
123
+ id: string;
124
+ timestamp: string;
125
+ signature: string;
126
+ }
127
+ export interface InboundEmailWebhookPayload {
128
+ domainId: string;
129
+ inboxId?: string;
130
+ inboxAddress?: string;
131
+ event: unknown;
132
+ email: unknown;
133
+ message: UnifiedMessage;
134
+ }
135
+ export interface ApiError {
136
+ message?: string;
137
+ [key: string]: unknown;
138
+ }
139
+ export interface ApiResponse<T> {
140
+ data: T;
141
+ error?: ApiError;
142
+ }
143
+ export interface SemanticSearchParams {
144
+ query: string;
145
+ limit?: number;
146
+ threshold?: number;
147
+ before?: string;
148
+ after?: string;
149
+ sender?: string;
150
+ }
151
+ export interface SemanticSearchResult {
152
+ message: UnifiedMessage;
153
+ similarity: number;
154
+ highlights: string[];
155
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ import type { SvixHeaders } from './types.js';
2
+ export declare const verifyResendWebhook: (payload: string, headers: SvixHeaders, secret: string) => unknown;
@@ -0,0 +1,37 @@
1
+ import { Webhook } from 'svix';
2
+ export const verifyResendWebhook = (payload, headers, secret) => {
3
+ const webhook = new Webhook(secret);
4
+ const svixHeaders = {
5
+ 'svix-id': headers.id,
6
+ 'svix-timestamp': headers.timestamp,
7
+ 'svix-signature': headers.signature,
8
+ };
9
+ return webhook.verify(payload, svixHeaders);
10
+ };
11
+ // Slack webhook verification (coming soon)
12
+ // export const verifySlackWebhook = ({
13
+ // rawBody,
14
+ // timestamp,
15
+ // signature,
16
+ // signingSecret,
17
+ // }: {
18
+ // rawBody: string;
19
+ // timestamp: string;
20
+ // signature: string;
21
+ // signingSecret: string;
22
+ // }) => {
23
+ // const baseString = `v0:${timestamp}:${rawBody}`;
24
+ // const hmac = crypto
25
+ // .createHmac('sha256', signingSecret)
26
+ // .update(baseString, 'utf8')
27
+ // .digest('hex');
28
+ //
29
+ // const expected = `v0=${hmac}`;
30
+ // const expectedBuffer = Buffer.from(expected, 'utf8');
31
+ // const signatureBuffer = Buffer.from(signature, 'utf8');
32
+ // if (expectedBuffer.length !== signatureBuffer.length) {
33
+ // return false;
34
+ // }
35
+ //
36
+ // return crypto.timingSafeEqual(expectedBuffer, signatureBuffer);
37
+ // };
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "commune-ai",
3
+ "version": "0.1.0",
4
+ "description": "Unified communication SDK for Commune (email + Slack)",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "README.md"
17
+ ],
18
+ "scripts": {
19
+ "build": "tsc -p tsconfig.json",
20
+ "clean": "rm -rf dist"
21
+ },
22
+ "dependencies": {
23
+ "svix": "^1.44.0"
24
+ },
25
+ "devDependencies": {
26
+ "@types/node": "^20.12.12",
27
+ "typescript": "^5.5.4"
28
+ }
29
+ }