commune-ai 0.2.4 → 0.2.6

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 CHANGED
@@ -132,6 +132,150 @@ const client = new CommuneClient({ apiKey: process.env.COMMUNE_API_KEY! });
132
132
 
133
133
  ---
134
134
 
135
+ ## Semantic Search
136
+ Commune provides powerful semantic search capabilities to help your agent find relevant conversations and context. The search is powered by embeddings and vector similarity, allowing natural language queries.
137
+
138
+ ### Basic Search
139
+ ```ts
140
+ import { CommuneClient } from "@commune/sdk";
141
+ const client = new CommuneClient({ apiKey: process.env.COMMUNE_API_KEY! });
142
+
143
+ // Search across all conversations in an organization
144
+ const results = await client.searchConversations(
145
+ "customer asking about refund policy",
146
+ { organizationId: "org_123" }
147
+ );
148
+
149
+ // Search with inbox filter
150
+ const inboxResults = await client.searchConversations(
151
+ "shipping delays",
152
+ {
153
+ organizationId: "org_123",
154
+ inboxIds: ["inbox_1", "inbox_2"]
155
+ }
156
+ );
157
+
158
+ // Search by participant
159
+ const userResults = await client.searchConversations(
160
+ "account upgrade request",
161
+ {
162
+ organizationId: "org_123",
163
+ participants: ["user@example.com"]
164
+ }
165
+ );
166
+
167
+ // Search with date range
168
+ const dateResults = await client.searchConversations(
169
+ "feature request",
170
+ {
171
+ organizationId: "org_123",
172
+ startDate: "2026-01-01",
173
+ endDate: "2026-02-01"
174
+ }
175
+ );
176
+ ```
177
+
178
+ ### Manual Indexing
179
+ By default, all conversations are automatically indexed. You can also manually index conversations:
180
+
181
+ ```ts
182
+ // Index a single conversation
183
+ await client.indexConversation("org_123", {
184
+ id: "conv_123",
185
+ subject: "Product Inquiry",
186
+ content: "Customer asking about product features",
187
+ metadata: {
188
+ subject: "Product Inquiry",
189
+ organizationId: "org_123",
190
+ inboxId: "inbox_1",
191
+ domainId: "domain_1",
192
+ participants: ["customer@example.com"],
193
+ threadId: "thread_1",
194
+ timestamp: new Date()
195
+ }
196
+ });
197
+
198
+ // Batch index multiple conversations
199
+ await client.indexConversationBatch("org_123", [
200
+ {
201
+ id: "conv_1",
202
+ subject: "Support Request",
203
+ content: "Customer needs help with login",
204
+ metadata: {
205
+ subject: "Support Request",
206
+ organizationId: "org_123",
207
+ inboxId: "support_inbox",
208
+ domainId: "domain_1",
209
+ participants: ["customer@example.com"],
210
+ threadId: "thread_1",
211
+ timestamp: new Date()
212
+ }
213
+ },
214
+ // ... more conversations
215
+ ]);
216
+ ```
217
+
218
+ ### Search Results
219
+ Search results include relevance scores and metadata:
220
+
221
+ ```ts
222
+ interface SearchResult {
223
+ id: string; // Conversation ID
224
+ score: number; // Similarity score (0-1)
225
+ metadata: {
226
+ subject: string; // Email subject
227
+ organizationId: string;
228
+ inboxId: string;
229
+ domainId: string;
230
+ participants: string[];
231
+ threadId: string;
232
+ timestamp: Date;
233
+ };
234
+ }
235
+ ```
236
+
237
+ ## Spam Protection
238
+ Commune includes built-in spam detection and protection to keep your agent safe from malicious emails and prevent abuse.
239
+
240
+ ### Automatic Spam Detection
241
+ All incoming emails are automatically analyzed for spam patterns:
242
+ - Content analysis (spam keywords, suspicious patterns)
243
+ - URL validation (broken links, phishing detection)
244
+ - Sender reputation tracking
245
+ - Domain blacklist checking
246
+
247
+ Spam emails are automatically rejected before reaching your webhook, protecting your agent from:
248
+ - Phishing attempts
249
+ - Malicious links
250
+ - Mass spam campaigns
251
+ - Fraudulent content
252
+
253
+ ### Outbound Protection
254
+ Commune also protects your sending reputation:
255
+ - Rate limiting per organization tier
256
+ - Content validation for outbound emails
257
+ - Burst detection for mass mailing
258
+ - Automatic blocking of spam-like content
259
+
260
+ This ensures your domain maintains high deliverability and isn't flagged by email providers.
261
+
262
+ ### Spam Reporting
263
+ If a spam email gets through, you can report it:
264
+
265
+ ```ts
266
+ import { CommuneClient } from "@commune/sdk";
267
+ const client = new CommuneClient({ apiKey: process.env.COMMUNE_API_KEY! });
268
+
269
+ // Report a message as spam
270
+ await client.reportSpam({
271
+ message_id: "msg_123",
272
+ reason: "Unsolicited marketing email",
273
+ classification: "spam" // or "phishing", "malware", "other"
274
+ });
275
+ ```
276
+
277
+ The system learns from reports and automatically blocks repeat offenders.
278
+
135
279
  ## Context (conversation state)
136
280
  Commune stores conversation state so your agent can respond with context.
137
281
 
@@ -0,0 +1,91 @@
1
+ import { SearchFilter, SearchOptions, SearchResult, IndexConversationPayload } from '../types.js';
2
+ export declare class SearchClient {
3
+ private baseUrl;
4
+ private headers;
5
+ constructor(baseUrl: string, headers: Record<string, string>);
6
+ /**
7
+ * Search conversations using semantic search
8
+ * @param query - The search query in natural language
9
+ * @param filter - Filter criteria for the search
10
+ * @param options - Additional search options
11
+ * @returns Promise<SearchResult[]>
12
+ * @example
13
+ * ```typescript
14
+ * // Search across organization
15
+ * const results = await client.conversations.search(
16
+ * "customer asking about refund policy",
17
+ * { organizationId: "org_123" }
18
+ * );
19
+ *
20
+ * // Search in specific inboxes
21
+ * const results = await client.conversations.search(
22
+ * "shipping delays",
23
+ * {
24
+ * organizationId: "org_123",
25
+ * inboxIds: ["inbox_1", "inbox_2"]
26
+ * }
27
+ * );
28
+ *
29
+ * // Search with participant filter
30
+ * const results = await client.conversations.search(
31
+ * "account upgrade request",
32
+ * {
33
+ * organizationId: "org_123",
34
+ * participants: ["user@example.com"]
35
+ * }
36
+ * );
37
+ * ```
38
+ */
39
+ search(query: string, filter: SearchFilter, options?: SearchOptions): Promise<SearchResult[]>;
40
+ /**
41
+ * Index a conversation for semantic search
42
+ * @param organizationId - The organization ID
43
+ * @param conversation - The conversation to index
44
+ * @returns Promise<void>
45
+ * @example
46
+ * ```typescript
47
+ * await client.conversations.index("org_123", {
48
+ * id: "conv_123",
49
+ * subject: "Product Inquiry",
50
+ * content: "Customer asking about product features",
51
+ * metadata: {
52
+ * subject: "Product Inquiry",
53
+ * organizationId: "org_123",
54
+ * inboxId: "inbox_1",
55
+ * domainId: "domain_1",
56
+ * participants: ["customer@example.com"],
57
+ * threadId: "thread_1",
58
+ * timestamp: new Date()
59
+ * }
60
+ * });
61
+ * ```
62
+ */
63
+ index(organizationId: string, conversation: IndexConversationPayload): Promise<void>;
64
+ /**
65
+ * Index multiple conversations in batch
66
+ * @param organizationId - The organization ID
67
+ * @param conversations - Array of conversations to index
68
+ * @returns Promise<void>
69
+ * @example
70
+ * ```typescript
71
+ * await client.conversations.indexBatch("org_123", [
72
+ * {
73
+ * id: "conv_1",
74
+ * subject: "Support Request",
75
+ * content: "Customer needs help with login",
76
+ * metadata: {
77
+ * subject: "Support Request",
78
+ * organizationId: "org_123",
79
+ * inboxId: "support_inbox",
80
+ * domainId: "domain_1",
81
+ * participants: ["customer@example.com"],
82
+ * threadId: "thread_1",
83
+ * timestamp: new Date()
84
+ * }
85
+ * },
86
+ * // ... more conversations
87
+ * ]);
88
+ * ```
89
+ */
90
+ indexBatch(organizationId: string, conversations: IndexConversationPayload[]): Promise<void>;
91
+ }
@@ -0,0 +1,131 @@
1
+ export class SearchClient {
2
+ constructor(baseUrl, headers) {
3
+ this.baseUrl = baseUrl;
4
+ this.headers = headers;
5
+ }
6
+ /**
7
+ * Search conversations using semantic search
8
+ * @param query - The search query in natural language
9
+ * @param filter - Filter criteria for the search
10
+ * @param options - Additional search options
11
+ * @returns Promise<SearchResult[]>
12
+ * @example
13
+ * ```typescript
14
+ * // Search across organization
15
+ * const results = await client.conversations.search(
16
+ * "customer asking about refund policy",
17
+ * { organizationId: "org_123" }
18
+ * );
19
+ *
20
+ * // Search in specific inboxes
21
+ * const results = await client.conversations.search(
22
+ * "shipping delays",
23
+ * {
24
+ * organizationId: "org_123",
25
+ * inboxIds: ["inbox_1", "inbox_2"]
26
+ * }
27
+ * );
28
+ *
29
+ * // Search with participant filter
30
+ * const results = await client.conversations.search(
31
+ * "account upgrade request",
32
+ * {
33
+ * organizationId: "org_123",
34
+ * participants: ["user@example.com"]
35
+ * }
36
+ * );
37
+ * ```
38
+ */
39
+ async search(query, filter, options) {
40
+ const response = await fetch(`${this.baseUrl}/search`, {
41
+ method: 'POST',
42
+ headers: {
43
+ ...this.headers,
44
+ 'Content-Type': 'application/json',
45
+ },
46
+ body: JSON.stringify({ query, filter, options }),
47
+ });
48
+ const data = await response.json();
49
+ if (data.error) {
50
+ throw new Error(data.error.message || 'Search failed');
51
+ }
52
+ return data.data.results;
53
+ }
54
+ /**
55
+ * Index a conversation for semantic search
56
+ * @param organizationId - The organization ID
57
+ * @param conversation - The conversation to index
58
+ * @returns Promise<void>
59
+ * @example
60
+ * ```typescript
61
+ * await client.conversations.index("org_123", {
62
+ * id: "conv_123",
63
+ * subject: "Product Inquiry",
64
+ * content: "Customer asking about product features",
65
+ * metadata: {
66
+ * subject: "Product Inquiry",
67
+ * organizationId: "org_123",
68
+ * inboxId: "inbox_1",
69
+ * domainId: "domain_1",
70
+ * participants: ["customer@example.com"],
71
+ * threadId: "thread_1",
72
+ * timestamp: new Date()
73
+ * }
74
+ * });
75
+ * ```
76
+ */
77
+ async index(organizationId, conversation) {
78
+ const response = await fetch(`${this.baseUrl}/search/index`, {
79
+ method: 'POST',
80
+ headers: {
81
+ ...this.headers,
82
+ 'Content-Type': 'application/json',
83
+ },
84
+ body: JSON.stringify({ organizationId, conversation }),
85
+ });
86
+ const data = await response.json();
87
+ if (data.error || !data.data.success) {
88
+ throw new Error(data.error?.message || 'Failed to index conversation');
89
+ }
90
+ }
91
+ /**
92
+ * Index multiple conversations in batch
93
+ * @param organizationId - The organization ID
94
+ * @param conversations - Array of conversations to index
95
+ * @returns Promise<void>
96
+ * @example
97
+ * ```typescript
98
+ * await client.conversations.indexBatch("org_123", [
99
+ * {
100
+ * id: "conv_1",
101
+ * subject: "Support Request",
102
+ * content: "Customer needs help with login",
103
+ * metadata: {
104
+ * subject: "Support Request",
105
+ * organizationId: "org_123",
106
+ * inboxId: "support_inbox",
107
+ * domainId: "domain_1",
108
+ * participants: ["customer@example.com"],
109
+ * threadId: "thread_1",
110
+ * timestamp: new Date()
111
+ * }
112
+ * },
113
+ * // ... more conversations
114
+ * ]);
115
+ * ```
116
+ */
117
+ async indexBatch(organizationId, conversations) {
118
+ const response = await fetch(`${this.baseUrl}/search/index/batch`, {
119
+ method: 'POST',
120
+ headers: {
121
+ ...this.headers,
122
+ 'Content-Type': 'application/json',
123
+ },
124
+ body: JSON.stringify({ organizationId, conversations }),
125
+ });
126
+ const data = await response.json();
127
+ if (data.error || !data.data.success) {
128
+ throw new Error(data.error?.message || 'Failed to index conversations');
129
+ }
130
+ }
131
+ }
package/dist/client.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { AttachmentRecord, ConversationListParams, CreateDomainPayload, DomainEntry, InboxEntry, MessageListParams, SemanticSearchParams, SemanticSearchResult, SendMessagePayload, UnifiedMessage } from './types.js';
1
+ import type { AttachmentRecord, AttachmentUrl, CreateDomainPayload, DomainEntry, InboxEntry, MessageListParams, SendMessagePayload, UnifiedMessage, SearchFilter, SearchOptions, SearchResult, IndexConversationPayload } from './types.js';
2
2
  export type ClientOptions = {
3
3
  baseUrl?: string;
4
4
  apiKey: string;
@@ -6,6 +6,7 @@ export type ClientOptions = {
6
6
  fetcher?: typeof fetch;
7
7
  };
8
8
  export declare class CommuneClient {
9
+ private searchClient;
9
10
  private baseUrl;
10
11
  private apiKey;
11
12
  private headers?;
@@ -59,10 +60,21 @@ export declare class CommuneClient {
59
60
  messages: {
60
61
  send: (payload: SendMessagePayload) => Promise<Record<string, unknown>>;
61
62
  list: (params: MessageListParams) => Promise<UnifiedMessage[]>;
62
- listByConversation: (conversationId: string, params?: ConversationListParams) => Promise<UnifiedMessage[]>;
63
+ listByConversation: (conversationId: string, params?: MessageListParams) => Promise<UnifiedMessage[]>;
64
+ };
65
+ conversations: {
66
+ search: (query: string, filter: SearchFilter, options?: SearchOptions) => Promise<SearchResult[]>;
67
+ index: (organizationId: string, conversation: IndexConversationPayload) => Promise<{
68
+ success: boolean;
69
+ }>;
70
+ indexBatch: (organizationId: string, conversations: IndexConversationPayload[]) => Promise<{
71
+ success: boolean;
72
+ }>;
63
73
  };
64
- search: (params: SemanticSearchParams) => Promise<SemanticSearchResult[]>;
65
74
  attachments: {
66
- get: (attachmentId: string) => Promise<AttachmentRecord>;
75
+ get: (attachmentId: string, options?: {
76
+ url?: boolean;
77
+ expiresIn?: number;
78
+ }) => Promise<AttachmentRecord | AttachmentUrl>;
67
79
  };
68
80
  }
package/dist/client.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { SearchClient } from './client/search.js';
1
2
  const DEFAULT_BASE_URL = 'https://web-production-3f46f.up.railway.app';
2
3
  const buildQuery = (params) => {
3
4
  const query = new URLSearchParams();
@@ -89,26 +90,49 @@ export class CommuneClient {
89
90
  inbox_id: params.inbox_id,
90
91
  })}`);
91
92
  },
92
- listByConversation: async (conversationId, params = {}) => {
93
+ listByConversation: async (conversationId, params) => {
93
94
  return this.request(`/api/conversations/${encodeURIComponent(conversationId)}/messages${buildQuery({
94
- limit: params.limit,
95
- order: params.order,
95
+ limit: params?.limit,
96
+ order: params?.order,
96
97
  })}`);
97
98
  },
98
99
  };
99
- // Semantic search across all organizational emails
100
- this.search = async (params) => {
101
- return this.request(`/api/search${buildQuery({
102
- q: params.query,
103
- limit: params.limit || 10,
104
- threshold: params.threshold || 0.7,
105
- before: params.before,
106
- after: params.after,
107
- sender: params.sender,
108
- })}`);
100
+ this.conversations = {
101
+ search: async (query, filter, options) => {
102
+ return this.request('/api/search', {
103
+ method: 'POST',
104
+ json: {
105
+ query,
106
+ filter,
107
+ options,
108
+ },
109
+ });
110
+ },
111
+ index: async (organizationId, conversation) => {
112
+ return this.request('/api/search/index', {
113
+ method: 'POST',
114
+ json: {
115
+ organizationId,
116
+ conversation,
117
+ },
118
+ });
119
+ },
120
+ indexBatch: async (organizationId, conversations) => {
121
+ return this.request('/api/search/index/batch', {
122
+ method: 'POST',
123
+ json: {
124
+ organizationId,
125
+ conversations,
126
+ },
127
+ });
128
+ },
109
129
  };
110
130
  this.attachments = {
111
- get: async (attachmentId) => {
131
+ get: async (attachmentId, options) => {
132
+ if (options?.url) {
133
+ const expiresIn = options.expiresIn || 3600;
134
+ return this.request(`/api/attachments/${encodeURIComponent(attachmentId)}/url?expiresIn=${expiresIn}`);
135
+ }
112
136
  return this.request(`/api/attachments/${encodeURIComponent(attachmentId)}`);
113
137
  },
114
138
  };
@@ -116,6 +140,7 @@ export class CommuneClient {
116
140
  this.apiKey = options.apiKey;
117
141
  this.headers = options.headers;
118
142
  this.fetcher = options.fetcher || fetch;
143
+ this.searchClient = new SearchClient(this.baseUrl, { Authorization: `Bearer ${this.apiKey}` });
119
144
  }
120
145
  async request(path, options = {}) {
121
146
  const { json, headers, ...rest } = options;
@@ -0,0 +1,35 @@
1
+ export interface SearchFilter {
2
+ organizationId: string;
3
+ inboxIds?: string[];
4
+ participants?: string[];
5
+ domainId?: string;
6
+ startDate?: string;
7
+ endDate?: string;
8
+ }
9
+ export interface SearchOptions {
10
+ limit?: number;
11
+ offset?: number;
12
+ minScore?: number;
13
+ }
14
+ export interface SearchResult {
15
+ id: string;
16
+ score: number;
17
+ metadata: {
18
+ subject: string;
19
+ organizationId: string;
20
+ inboxId: string;
21
+ domainId: string;
22
+ participants: string[];
23
+ threadId: string;
24
+ timestamp: Date;
25
+ };
26
+ }
27
+ export interface ConversationMetadata {
28
+ subject: string;
29
+ organizationId: string;
30
+ inboxId: string;
31
+ domainId: string;
32
+ participants: string[];
33
+ threadId: string;
34
+ timestamp: Date;
35
+ }
@@ -0,0 +1 @@
1
+ export {};
package/dist/types.d.ts CHANGED
@@ -42,6 +42,22 @@ export interface AttachmentRecord {
42
42
  source: Channel;
43
43
  source_url?: string | null;
44
44
  download_error?: boolean;
45
+ storage_type?: 'cloudinary' | 'database';
46
+ cloudinary_url?: string | null;
47
+ cloudinary_public_id?: string | null;
48
+ }
49
+ export interface AttachmentMetadata {
50
+ attachment_id: string;
51
+ filename: string;
52
+ mime_type: string;
53
+ size: number;
54
+ }
55
+ export interface AttachmentUrl {
56
+ url: string;
57
+ expiresIn: number;
58
+ filename: string;
59
+ mimeType: string;
60
+ size: number;
45
61
  }
46
62
  export interface DomainWebhook {
47
63
  id?: string;
@@ -126,6 +142,8 @@ export interface MessageListParams {
126
142
  export interface ConversationListParams {
127
143
  limit?: number;
128
144
  order?: 'asc' | 'desc';
145
+ before?: string;
146
+ after?: string;
129
147
  }
130
148
  export interface SvixHeaders {
131
149
  id: string;
@@ -140,6 +158,7 @@ export interface InboundEmailWebhookPayload {
140
158
  email: unknown;
141
159
  message: UnifiedMessage;
142
160
  extractedData?: Record<string, any>;
161
+ attachments?: AttachmentMetadata[];
143
162
  }
144
163
  export interface ApiError {
145
164
  message?: string;
@@ -149,16 +168,51 @@ export interface ApiResponse<T> {
149
168
  data: T;
150
169
  error?: ApiError;
151
170
  }
152
- export interface SemanticSearchParams {
153
- query: string;
171
+ export interface SearchFilter {
172
+ organizationId: string;
173
+ inboxIds?: string[];
174
+ participants?: string[];
175
+ domainId?: string;
176
+ startDate?: string;
177
+ endDate?: string;
178
+ }
179
+ export interface SearchOptions {
154
180
  limit?: number;
155
- threshold?: number;
156
- before?: string;
157
- after?: string;
158
- sender?: string;
181
+ offset?: number;
182
+ minScore?: number;
159
183
  }
160
- export interface SemanticSearchResult {
161
- message: UnifiedMessage;
162
- similarity: number;
163
- highlights: string[];
184
+ export interface SearchResult {
185
+ id: string;
186
+ score: number;
187
+ metadata: {
188
+ subject: string;
189
+ organizationId: string;
190
+ inboxId: string;
191
+ domainId: string;
192
+ participants: string[];
193
+ threadId: string;
194
+ timestamp: Date;
195
+ attachmentIds?: string[];
196
+ hasAttachments?: boolean;
197
+ attachmentCount?: number;
198
+ };
199
+ }
200
+ export interface ConversationMetadata {
201
+ subject: string;
202
+ organizationId: string;
203
+ inboxId: string;
204
+ domainId: string;
205
+ participants: string[];
206
+ threadId: string;
207
+ timestamp: Date;
208
+ attachmentIds?: string[];
209
+ hasAttachments?: boolean;
210
+ attachmentCount?: number;
211
+ }
212
+ export interface IndexConversationPayload {
213
+ id: string;
214
+ subject: string;
215
+ content: string;
216
+ metadata: ConversationMetadata;
164
217
  }
218
+ export type SearchType = 'vector' | 'agent';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "commune-ai",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "description": "Our email infrastructure - webhooks, threads, history, and semantic search",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",