commune-ai 0.2.61 → 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,14 +117,42 @@ const client = new CommuneClient({ apiKey: process.env.COMMUNE_API_KEY! });
132
117
 
133
118
  ---
134
119
 
120
+ <!-- -->
135
121
  ## Attachments
136
- Access email attachments with secure, temporary download URLs.
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');
137
133
 
138
- Attachments are stored and available as `attachment_id`. These IDs are:
139
- - Available through email events in the incoming webhook
140
- - Available in metadata of semantic search results
134
+ const { attachment_id } = await client.attachments.upload(
135
+ base64Content,
136
+ 'invoice.pdf',
137
+ 'application/pdf'
138
+ );
141
139
 
142
- Use the `attachment_id` to directly access the attachment, download it or read it.
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.
143
156
 
144
157
  ```ts
145
158
  // Get attachment metadata
@@ -353,7 +366,7 @@ Commune stores conversation state so your agent can respond with context.
353
366
  import { CommuneClient } from "@commune/sdk";
354
367
  const client = new CommuneClient({ apiKey: process.env.COMMUNE_API_KEY! });
355
368
 
356
- // Thread history (email thread or Slack thread)
369
+ // Thread history (email thread)
357
370
  const thread = await client.messages.listByConversation(message.conversation_id, {
358
371
  order: "asc",
359
372
  limit: 50,
@@ -375,8 +388,8 @@ const inboxMessages = await client.messages.list({
375
388
 
376
389
  ---
377
390
 
378
- ## Cross‑channel behavior (email + Slack in one handler)
379
- The same `UnifiedMessage` shape works for both channels. You only branch on `channel`.
391
+ ## Email handling
392
+ The `UnifiedMessage` shape works for email messages.
380
393
 
381
394
  ```ts
382
395
  import express from "express";
@@ -387,31 +400,17 @@ const client = new CommuneClient({ apiKey: process.env.COMMUNE_API_KEY! });
387
400
 
388
401
  const handler = createWebhookHandler({
389
402
  onEvent: async (message, context) => {
390
- if (message.channel === "email") {
391
- const sender = message.participants.find(p => p.role === "sender")?.identity;
392
- if (!sender) return;
393
-
394
- await client.messages.send({
395
- channel: "email",
396
- to: sender,
397
- text: "Got it — thanks for the message.",
398
- conversation_id: message.conversation_id,
399
- domainId: context.payload.domainId,
400
- inboxId: context.payload.inboxId,
401
- });
402
- }
403
-
404
- if (message.channel === "slack") {
405
- const channelId = message.metadata.slack_channel_id;
406
- if (!channelId) return;
407
-
408
- await client.messages.send({
409
- channel: "slack",
410
- to: channelId,
411
- text: "Replying in thread ✅",
412
- conversation_id: message.conversation_id,
413
- });
414
- }
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
+ });
415
414
  },
416
415
  });
417
416
 
@@ -597,20 +596,20 @@ const handler = createWebhookHandler({
597
596
  ## Full example (single file)
598
597
  A complete copy‑paste example that:
599
598
  - receives webhook
600
- - handles attachments
599
+ - handles incoming attachments
600
+ - sends email with attachments
601
601
  - replies by email
602
- - replies in Slack thread
603
- - fetches conversation history
604
602
 
605
603
  ```ts
606
604
  import express from "express";
607
605
  import { CommuneClient, createWebhookHandler } from "@commune/sdk";
606
+ import fs from "fs";
608
607
 
609
608
  const client = new CommuneClient({ apiKey: process.env.COMMUNE_API_KEY! });
610
609
 
611
610
  const handler = createWebhookHandler({
612
611
  onEvent: async (message, context) => {
613
- // 1) Handle attachments
612
+ // 1) Handle incoming attachments
614
613
  const { attachments } = context.payload;
615
614
  if (attachments && attachments.length > 0) {
616
615
  console.log(`Received ${attachments.length} attachments`);
@@ -626,35 +625,29 @@ const handler = createWebhookHandler({
626
625
  }
627
626
  }
628
627
 
629
- // 2) Email reply (same thread)
630
- if (message.channel === "email") {
631
- const sender = message.participants.find(p => p.role === "sender")?.identity;
632
- if (!sender) return;
633
-
634
- await client.messages.send({
635
- channel: "email",
636
- to: sender,
637
- text: "Thanks! We received your email.",
638
- conversation_id: message.conversation_id,
639
- domainId: context.payload.domainId,
640
- inboxId: context.payload.inboxId,
641
- });
642
-
643
- return;
644
- }
645
-
646
- // 3) Slack reply (same thread)
647
- if (message.channel === "slack") {
648
- const channelId = message.metadata.slack_channel_id;
649
- if (!channelId) return;
650
-
651
- await client.messages.send({
652
- channel: "slack",
653
- to: channelId,
654
- text: "Thanks! Replying in thread.",
655
- conversation_id: message.conversation_id,
656
- });
657
- }
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
+ });
658
651
  },
659
652
  });
660
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.61",
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",