commune-ai 0.2.6 → 0.2.62

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
@@ -1,22 +1,22 @@
1
1
  # @commune/sdk
2
2
 
3
3
  Commune is the **communication infrastructure for agents**. It gives your agent a **unified inbox**
4
- for **email + Slack**, so your agent can talk to humans where they already work. Most teams
4
+ for **email**, so your agent can talk to humans where they already work. Most teams
5
5
  get a working integration in **~15 minutes**.
6
6
 
7
7
  ## Why Commune exists (what it enables)
8
- Agents are powerful, but users already live in **email and Slack**. Commune bridges that gap so:
8
+ Agents are powerful, but users already live in **email**. Commune bridges that gap so:
9
9
  - your agent is reachable where humans already work
10
- - you don’t have to build deliverability, threading, or Slack plumbing
10
+ - you don’t have to build deliverability, threading, or email plumbing
11
11
  - you can ship an agent‑first experience in minutes, not weeks
12
12
 
13
13
  In practice, Commune lets you:
14
14
  - give an agent a real inbox on your domain
15
- - respond in the correct email or Slack thread every time
15
+ - respond in the correct email thread every time
16
16
  - use **conversation state** to make smarter, context‑aware replies
17
17
 
18
18
  ## How it works (mental model)
19
- 1) Commune receives inbound email/Slack events.
19
+ 1) Commune receives inbound email events.
20
20
  2) Commune normalizes them into a **UnifiedMessage**.
21
21
  3) Commune sends the UnifiedMessage to your webhook.
22
22
  4) Your agent replies using one API call.
@@ -48,7 +48,7 @@ const handler = createWebhookHandler({
48
48
  });
49
49
  },
50
50
  onEvent: async (message, context) => {
51
- // Example inbound payload (unified across email + Slack):
51
+ // Example inbound payload:
52
52
  // message = {
53
53
  // channel: "email",
54
54
  // conversation_id: "thread_id",
@@ -61,32 +61,17 @@ const handler = createWebhookHandler({
61
61
  const agentReply = await llm.complete(prompt); // replace with your LLM client
62
62
 
63
63
  // Email reply (same thread)
64
- if (message.channel === "email") {
65
- const sender = message.participants.find(p => p.role === "sender")?.identity;
66
- if (!sender) return;
67
-
68
- await client.messages.send({
69
- channel: "email",
70
- to: sender,
71
- text: agentReply,
72
- conversation_id: message.conversation_id,
73
- domainId: context.payload.domainId,
74
- inboxId: context.payload.inboxId,
75
- });
76
- }
77
-
78
- // Slack reply (same thread)
79
- if (message.channel === "slack") {
80
- const channelId = message.metadata.slack_channel_id;
81
- if (!channelId) return;
82
-
83
- await client.messages.send({
84
- channel: "slack",
85
- to: channelId,
86
- text: agentReply,
87
- conversation_id: message.conversation_id, // thread_ts
88
- });
89
- }
64
+ const sender = message.participants.find(p => p.role === "sender")?.identity;
65
+ if (!sender) return;
66
+
67
+ await client.messages.send({
68
+ channel: "email",
69
+ to: sender,
70
+ text: agentReply,
71
+ conversation_id: message.conversation_id,
72
+ domainId: context.payload.domainId,
73
+ inboxId: context.payload.inboxId,
74
+ });
90
75
  },
91
76
  });
92
77
 
@@ -103,16 +88,16 @@ npm install @commune/sdk
103
88
  ---
104
89
 
105
90
  ## Unified inbox (what your webhook receives)
106
- Every inbound email or Slack message arrives in this shape:
91
+ Every inbound email arrives in this shape:
107
92
 
108
93
  ```ts
109
94
  export interface UnifiedMessage {
110
- channel: "email" | "slack";
95
+ channel: "email";
111
96
  message_id: string;
112
- conversation_id: string; // email thread or Slack thread_ts
97
+ conversation_id: string; // email thread
113
98
  participants: { role: string; identity: string }[];
114
99
  content: string;
115
- metadata: { slack_channel_id?: string; ... };
100
+ metadata: { ... };
116
101
  }
117
102
  ```
118
103
 
@@ -132,6 +117,81 @@ const client = new CommuneClient({ apiKey: process.env.COMMUNE_API_KEY! });
132
117
 
133
118
  ---
134
119
 
120
+ <!-- -->
121
+ ## Attachments
122
+ Send and receive email attachments with secure storage and temporary download URLs.
123
+
124
+ ### Sending attachments
125
+ Upload attachments first, then use the attachment ID when sending emails.
126
+
127
+ ```ts
128
+ import fs from 'fs';
129
+
130
+ // 1. Upload attachment (base64 encoded)
131
+ const fileBuffer = fs.readFileSync('invoice.pdf');
132
+ const base64Content = fileBuffer.toString('base64');
133
+
134
+ const { attachment_id } = await client.attachments.upload(
135
+ base64Content,
136
+ 'invoice.pdf',
137
+ 'application/pdf'
138
+ );
139
+
140
+ // 2. Send email with attachment
141
+ await client.messages.send({
142
+ to: 'customer@example.com',
143
+ subject: 'Your invoice',
144
+ text: 'Please find your invoice attached.',
145
+ attachments: [attachment_id],
146
+ domainId: 'your-domain-id',
147
+ });
148
+ ```
149
+
150
+ ### Receiving attachments
151
+ Attachments are available through:
152
+ - Email events in the incoming webhook
153
+ - Metadata of semantic search results
154
+
155
+ Use the `attachment_id` to access the attachment.
156
+
157
+ ```ts
158
+ // Get attachment metadata
159
+ const attachment = await client.attachments.get("att_123");
160
+ console.log(attachment.filename, attachment.size);
161
+
162
+ // Get download URL (expires in 1 hour)
163
+ const { url } = await client.attachments.get("att_123", { url: true });
164
+ // Use the URL to download or display the file
165
+
166
+ // Custom expiration (2 hours)
167
+ const { url } = await client.attachments.get("att_123", { url: true, expiresIn: 7200 });
168
+ ```
169
+
170
+ ### Handling attachments in webhook
171
+ ```ts
172
+ const handler = createWebhookHandler({
173
+ onEvent: async (message, context) => {
174
+ const { attachments } = context.payload;
175
+
176
+ if (attachments && attachments.length > 0) {
177
+ for (const att of attachments) {
178
+ console.log(`Attachment: ${att.filename} (${att.size} bytes)`);
179
+
180
+ // Get download URL
181
+ const { url } = await client.attachments.get(att.attachment_id, { url: true });
182
+
183
+ // Download the file
184
+ const response = await fetch(url);
185
+ const buffer = await response.arrayBuffer();
186
+ // Process the file...
187
+ }
188
+ }
189
+ },
190
+ });
191
+ ```
192
+
193
+ ---
194
+
135
195
  ## Semantic Search
136
196
  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
197
 
@@ -230,10 +290,33 @@ interface SearchResult {
230
290
  participants: string[];
231
291
  threadId: string;
232
292
  timestamp: Date;
293
+ attachmentIds?: string[]; // Attachment IDs in this conversation
294
+ hasAttachments?: boolean; // Whether conversation has attachments
295
+ attachmentCount?: number; // Number of attachments
233
296
  };
234
297
  }
235
298
  ```
236
299
 
300
+ ### Search with attachments
301
+ ```ts
302
+ // Search for conversations with attachments
303
+ const results = await client.searchConversations(
304
+ "invoice with receipt",
305
+ { organizationId: "org_123" }
306
+ );
307
+
308
+ // Filter results that have attachments
309
+ const withAttachments = results.filter(r => r.metadata.hasAttachments);
310
+
311
+ // Access attachments from search results
312
+ for (const result of withAttachments) {
313
+ for (const attachmentId of result.metadata.attachmentIds || []) {
314
+ const { url, filename } = await client.attachments.get(attachmentId, { url: true });
315
+ console.log(`Download: ${filename} - ${url}`);
316
+ }
317
+ }
318
+ ```
319
+
237
320
  ## Spam Protection
238
321
  Commune includes built-in spam detection and protection to keep your agent safe from malicious emails and prevent abuse.
239
322
 
@@ -283,7 +366,7 @@ Commune stores conversation state so your agent can respond with context.
283
366
  import { CommuneClient } from "@commune/sdk";
284
367
  const client = new CommuneClient({ apiKey: process.env.COMMUNE_API_KEY! });
285
368
 
286
- // Thread history (email thread or Slack thread)
369
+ // Thread history (email thread)
287
370
  const thread = await client.messages.listByConversation(message.conversation_id, {
288
371
  order: "asc",
289
372
  limit: 50,
@@ -305,8 +388,8 @@ const inboxMessages = await client.messages.list({
305
388
 
306
389
  ---
307
390
 
308
- ## Cross‑channel behavior (email + Slack in one handler)
309
- The same `UnifiedMessage` shape works for both channels. You only branch on `channel`.
391
+ ## Email handling
392
+ The `UnifiedMessage` shape works for email messages.
310
393
 
311
394
  ```ts
312
395
  import express from "express";
@@ -317,31 +400,17 @@ const client = new CommuneClient({ apiKey: process.env.COMMUNE_API_KEY! });
317
400
 
318
401
  const handler = createWebhookHandler({
319
402
  onEvent: async (message, context) => {
320
- if (message.channel === "email") {
321
- const sender = message.participants.find(p => p.role === "sender")?.identity;
322
- if (!sender) return;
323
-
324
- await client.messages.send({
325
- channel: "email",
326
- to: sender,
327
- text: "Got it — thanks for the message.",
328
- conversation_id: message.conversation_id,
329
- domainId: context.payload.domainId,
330
- inboxId: context.payload.inboxId,
331
- });
332
- }
333
-
334
- if (message.channel === "slack") {
335
- const channelId = message.metadata.slack_channel_id;
336
- if (!channelId) return;
337
-
338
- await client.messages.send({
339
- channel: "slack",
340
- to: channelId,
341
- text: "Replying in thread ✅",
342
- conversation_id: message.conversation_id,
343
- });
344
- }
403
+ const sender = message.participants.find(p => p.role === "sender")?.identity;
404
+ if (!sender) return;
405
+
406
+ await client.messages.send({
407
+ channel: "email",
408
+ to: sender,
409
+ text: "Got it — thanks for the message.",
410
+ conversation_id: message.conversation_id,
411
+ domainId: context.payload.domainId,
412
+ inboxId: context.payload.inboxId,
413
+ });
345
414
  },
346
415
  });
347
416
 
@@ -527,47 +596,58 @@ const handler = createWebhookHandler({
527
596
  ## Full example (single file)
528
597
  A complete copy‑paste example that:
529
598
  - receives webhook
599
+ - handles incoming attachments
600
+ - sends email with attachments
530
601
  - replies by email
531
- - replies in Slack thread
532
- - fetches conversation history
533
602
 
534
603
  ```ts
535
604
  import express from "express";
536
605
  import { CommuneClient, createWebhookHandler } from "@commune/sdk";
606
+ import fs from "fs";
537
607
 
538
608
  const client = new CommuneClient({ apiKey: process.env.COMMUNE_API_KEY! });
539
609
 
540
610
  const handler = createWebhookHandler({
541
611
  onEvent: async (message, context) => {
542
- // 1) Email reply (same thread)
543
- if (message.channel === "email") {
544
- const sender = message.participants.find(p => p.role === "sender")?.identity;
545
- if (!sender) return;
546
-
547
- await client.messages.send({
548
- channel: "email",
549
- to: sender,
550
- text: "Thanks! We received your email.",
551
- conversation_id: message.conversation_id,
552
- domainId: context.payload.domainId,
553
- inboxId: context.payload.inboxId,
554
- });
555
-
556
- return;
612
+ // 1) Handle incoming attachments
613
+ const { attachments } = context.payload;
614
+ if (attachments && attachments.length > 0) {
615
+ console.log(`Received ${attachments.length} attachments`);
616
+
617
+ for (const att of attachments) {
618
+ // Get download URL
619
+ const { url, filename } = await client.attachments.get(att.attachment_id, { url: true });
620
+ console.log(`Attachment: ${filename} - ${url}`);
621
+
622
+ // Download if needed
623
+ // const response = await fetch(url);
624
+ // const buffer = await response.arrayBuffer();
625
+ }
557
626
  }
558
627
 
559
- // 2) Slack reply (same thread)
560
- if (message.channel === "slack") {
561
- const channelId = message.metadata.slack_channel_id;
562
- if (!channelId) return;
563
-
564
- await client.messages.send({
565
- channel: "slack",
566
- to: channelId,
567
- text: "Thanks! Replying in thread.",
568
- conversation_id: message.conversation_id,
569
- });
570
- }
628
+ // 2) Email reply with attachment (same thread)
629
+ const sender = message.participants.find(p => p.role === "sender")?.identity;
630
+ if (!sender) return;
631
+
632
+ // Upload attachment for sending
633
+ const fileBuffer = fs.readFileSync('receipt.pdf');
634
+ const base64Content = fileBuffer.toString('base64');
635
+ const { attachment_id } = await client.attachments.upload(
636
+ base64Content,
637
+ 'receipt.pdf',
638
+ 'application/pdf'
639
+ );
640
+
641
+ await client.messages.send({
642
+ channel: "email",
643
+ to: sender,
644
+ subject: "Your receipt",
645
+ text: "Thanks! Here's your receipt.",
646
+ attachments: [attachment_id],
647
+ conversation_id: message.conversation_id,
648
+ domainId: context.payload.domainId,
649
+ inboxId: context.payload.inboxId,
650
+ });
571
651
  },
572
652
  });
573
653
 
package/dist/client.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { AttachmentRecord, AttachmentUrl, CreateDomainPayload, DomainEntry, InboxEntry, MessageListParams, SendMessagePayload, UnifiedMessage, SearchFilter, SearchOptions, SearchResult, IndexConversationPayload } from './types.js';
1
+ import type { AttachmentRecord, AttachmentUrl, AttachmentUploadResponse, 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;
@@ -72,6 +72,7 @@ export declare class CommuneClient {
72
72
  }>;
73
73
  };
74
74
  attachments: {
75
+ upload: (content: string, filename: string, mimeType: string) => Promise<AttachmentUploadResponse>;
75
76
  get: (attachmentId: string, options?: {
76
77
  url?: boolean;
77
78
  expiresIn?: number;
package/dist/client.js CHANGED
@@ -128,6 +128,12 @@ export class CommuneClient {
128
128
  },
129
129
  };
130
130
  this.attachments = {
131
+ upload: async (content, filename, mimeType) => {
132
+ return this.request('/api/attachments/upload', {
133
+ method: 'POST',
134
+ body: JSON.stringify({ content, filename, mimeType }),
135
+ });
136
+ },
131
137
  get: async (attachmentId, options) => {
132
138
  if (options?.url) {
133
139
  const expiresIn = options.expiresIn || 3600;
package/dist/types.d.ts CHANGED
@@ -59,6 +59,13 @@ export interface AttachmentUrl {
59
59
  mimeType: string;
60
60
  size: number;
61
61
  }
62
+ export interface AttachmentUploadResponse {
63
+ attachment_id: string;
64
+ filename: string;
65
+ mime_type: string;
66
+ size: number;
67
+ storage_type: 'cloudinary' | 'database';
68
+ }
62
69
  export interface DomainWebhook {
63
70
  id?: string;
64
71
  endpoint?: string;
@@ -104,10 +111,7 @@ export interface SendMessagePayload {
104
111
  to: string | string[];
105
112
  text?: string;
106
113
  html?: string;
107
- attachments?: {
108
- id?: string;
109
- attachment_id?: string;
110
- }[];
114
+ attachments?: string[];
111
115
  subject?: string;
112
116
  cc?: string[];
113
117
  bcc?: string[];
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "commune-ai",
3
- "version": "0.2.6",
4
- "description": "Our email infrastructure - webhooks, threads, history, and semantic search",
3
+ "version": "0.2.62",
4
+ "description": "SDK-first email infrastructure for agents - threads, history, semantic search, structured data output and attachments.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",