clawdbot-pipedrive 1.0.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 +98 -0
- package/index.ts +498 -0
- package/package.json +24 -0
package/README.md
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# clawdbot-pipedrive
|
|
2
|
+
|
|
3
|
+
Pipedrive CRM integration plugin for [Clawdbot](https://clawd.bot).
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Deals**: Search, list, create, update, delete deals
|
|
8
|
+
- **Persons**: Search, get, create, update contacts
|
|
9
|
+
- **Organizations**: Search, get, create companies
|
|
10
|
+
- **Activities**: List, create, update, delete tasks/calls/meetings
|
|
11
|
+
- **Pipelines & Stages**: List pipelines and stages
|
|
12
|
+
- **Notes**: List and create notes on any entity
|
|
13
|
+
- **Users**: List users, get current user
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
### Option 1: Install from npm (when published)
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
clawdbot plugins install clawdbot-pipedrive
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Option 2: Manual installation
|
|
24
|
+
|
|
25
|
+
Copy this folder to `~/.clawdbot/extensions/pipedrive/`
|
|
26
|
+
|
|
27
|
+
Or create a symlink for development:
|
|
28
|
+
```bash
|
|
29
|
+
ln -s /path/to/clawdbot-pipedrive ~/.clawdbot/extensions/pipedrive
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Configuration
|
|
33
|
+
|
|
34
|
+
Add to your `~/.clawdbot/config.json`:
|
|
35
|
+
|
|
36
|
+
```json
|
|
37
|
+
{
|
|
38
|
+
"plugins": {
|
|
39
|
+
"entries": {
|
|
40
|
+
"pipedrive": {
|
|
41
|
+
"enabled": true,
|
|
42
|
+
"config": {
|
|
43
|
+
"apiKey": "your-pipedrive-api-token",
|
|
44
|
+
"domain": "yourcompany"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Getting your API Key
|
|
53
|
+
|
|
54
|
+
1. Log in to Pipedrive
|
|
55
|
+
2. Go to **Settings** (gear icon) → **Personal preferences** → **API**
|
|
56
|
+
3. Copy your **Personal API token**
|
|
57
|
+
|
|
58
|
+
### Finding your Domain
|
|
59
|
+
|
|
60
|
+
Your domain is the subdomain of your Pipedrive URL:
|
|
61
|
+
- If you access Pipedrive at `https://acme.pipedrive.com`, your domain is `acme`
|
|
62
|
+
|
|
63
|
+
## Available Tools
|
|
64
|
+
|
|
65
|
+
| Tool | Description |
|
|
66
|
+
|------|-------------|
|
|
67
|
+
| `pipedrive_search_deals` | Search deals by term |
|
|
68
|
+
| `pipedrive_get_deal` | Get deal details by ID |
|
|
69
|
+
| `pipedrive_list_deals` | List deals with filters |
|
|
70
|
+
| `pipedrive_create_deal` | Create a new deal |
|
|
71
|
+
| `pipedrive_update_deal` | Update an existing deal |
|
|
72
|
+
| `pipedrive_delete_deal` | Delete a deal |
|
|
73
|
+
| `pipedrive_search_persons` | Search contacts |
|
|
74
|
+
| `pipedrive_get_person` | Get contact details |
|
|
75
|
+
| `pipedrive_create_person` | Create a contact |
|
|
76
|
+
| `pipedrive_update_person` | Update a contact |
|
|
77
|
+
| `pipedrive_search_organizations` | Search organizations |
|
|
78
|
+
| `pipedrive_get_organization` | Get organization details |
|
|
79
|
+
| `pipedrive_create_organization` | Create an organization |
|
|
80
|
+
| `pipedrive_list_activities` | List activities with filters |
|
|
81
|
+
| `pipedrive_get_activity` | Get activity details |
|
|
82
|
+
| `pipedrive_create_activity` | Create a task/call/meeting |
|
|
83
|
+
| `pipedrive_update_activity` | Update an activity |
|
|
84
|
+
| `pipedrive_delete_activity` | Delete an activity |
|
|
85
|
+
| `pipedrive_list_pipelines` | List all pipelines |
|
|
86
|
+
| `pipedrive_list_stages` | List pipeline stages |
|
|
87
|
+
| `pipedrive_list_notes` | List notes |
|
|
88
|
+
| `pipedrive_create_note` | Create a note |
|
|
89
|
+
| `pipedrive_list_users` | List all users |
|
|
90
|
+
| `pipedrive_get_current_user` | Get current user |
|
|
91
|
+
|
|
92
|
+
## Custom Skills
|
|
93
|
+
|
|
94
|
+
For organization-specific workflows (naming conventions, required fields, etc.), create a skill at `~/.clawdbot/skills/your-crm/SKILL.md`. Skills provide instructions to the AI without modifying the plugin.
|
|
95
|
+
|
|
96
|
+
## License
|
|
97
|
+
|
|
98
|
+
MIT
|
package/index.ts
ADDED
|
@@ -0,0 +1,498 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
|
|
3
|
+
type PipedriveConfig = {
|
|
4
|
+
apiKey?: string;
|
|
5
|
+
domain?: string;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
type ClawdbotPluginApi = {
|
|
9
|
+
pluginConfig: unknown;
|
|
10
|
+
registerTool: (tool: {
|
|
11
|
+
name: string;
|
|
12
|
+
description: string;
|
|
13
|
+
parameters: unknown;
|
|
14
|
+
execute: (id: string, params: Record<string, unknown>) => Promise<{ content: { type: string; text: string }[] }>;
|
|
15
|
+
}) => void;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
type ClawdbotPluginDefinition = {
|
|
19
|
+
id: string;
|
|
20
|
+
name: string;
|
|
21
|
+
description: string;
|
|
22
|
+
version: string;
|
|
23
|
+
configSchema: {
|
|
24
|
+
parse: (v: unknown) => unknown;
|
|
25
|
+
uiHints: Record<string, { label: string; sensitive?: boolean; placeholder?: string; help?: string }>;
|
|
26
|
+
};
|
|
27
|
+
register: (api: ClawdbotPluginApi) => void;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const plugin: ClawdbotPluginDefinition = {
|
|
31
|
+
id: "pipedrive",
|
|
32
|
+
name: "Pipedrive CRM",
|
|
33
|
+
description: "Interact with Pipedrive deals, persons, organizations, and activities",
|
|
34
|
+
version: "1.0.0",
|
|
35
|
+
|
|
36
|
+
configSchema: {
|
|
37
|
+
parse: (v) => v as PipedriveConfig,
|
|
38
|
+
uiHints: {
|
|
39
|
+
apiKey: {
|
|
40
|
+
label: "API Key",
|
|
41
|
+
sensitive: true,
|
|
42
|
+
help: "Your Pipedrive API token (Settings > Personal preferences > API)",
|
|
43
|
+
},
|
|
44
|
+
domain: {
|
|
45
|
+
label: "Company Domain",
|
|
46
|
+
placeholder: "yourcompany",
|
|
47
|
+
help: "The subdomain of your Pipedrive account (e.g., 'acme' from acme.pipedrive.com)",
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
register(api) {
|
|
53
|
+
const cfg = api.pluginConfig as PipedriveConfig;
|
|
54
|
+
|
|
55
|
+
if (!cfg.apiKey || !cfg.domain) {
|
|
56
|
+
console.warn("[pipedrive] Plugin not configured: missing apiKey or domain");
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const baseUrl = `https://${cfg.domain}.pipedrive.com/api/v1`;
|
|
61
|
+
|
|
62
|
+
async function pipedriveRequest(endpoint: string, options?: RequestInit) {
|
|
63
|
+
const url = new URL(`${baseUrl}${endpoint}`);
|
|
64
|
+
url.searchParams.set("api_token", cfg.apiKey!);
|
|
65
|
+
const res = await fetch(url.toString(), {
|
|
66
|
+
...options,
|
|
67
|
+
headers: {
|
|
68
|
+
"Content-Type": "application/json",
|
|
69
|
+
...options?.headers,
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
if (!res.ok) {
|
|
73
|
+
const error = await res.text();
|
|
74
|
+
throw new Error(`Pipedrive API error (${res.status}): ${error}`);
|
|
75
|
+
}
|
|
76
|
+
return res.json();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ============ DEALS ============
|
|
80
|
+
|
|
81
|
+
api.registerTool({
|
|
82
|
+
name: "pipedrive_search_deals",
|
|
83
|
+
description: "Search Pipedrive deals by term",
|
|
84
|
+
parameters: Type.Object({
|
|
85
|
+
term: Type.String({ description: "Search term" }),
|
|
86
|
+
status: Type.Optional(
|
|
87
|
+
Type.String({ description: "Filter by status: open, won, lost, deleted, all_not_deleted" })
|
|
88
|
+
),
|
|
89
|
+
}),
|
|
90
|
+
async execute(_id, params) {
|
|
91
|
+
const { term, status } = params as { term: string; status?: string };
|
|
92
|
+
let endpoint = `/deals/search?term=${encodeURIComponent(term)}`;
|
|
93
|
+
if (status) endpoint += `&status=${status}`;
|
|
94
|
+
const data = await pipedriveRequest(endpoint);
|
|
95
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
api.registerTool({
|
|
100
|
+
name: "pipedrive_get_deal",
|
|
101
|
+
description: "Get details of a specific deal by ID",
|
|
102
|
+
parameters: Type.Object({
|
|
103
|
+
id: Type.Number({ description: "Deal ID" }),
|
|
104
|
+
}),
|
|
105
|
+
async execute(_id, params) {
|
|
106
|
+
const { id } = params as { id: number };
|
|
107
|
+
const data = await pipedriveRequest(`/deals/${id}`);
|
|
108
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
api.registerTool({
|
|
113
|
+
name: "pipedrive_list_deals",
|
|
114
|
+
description: "List deals with optional filters",
|
|
115
|
+
parameters: Type.Object({
|
|
116
|
+
status: Type.Optional(Type.String({ description: "Filter by status: open, won, lost, deleted, all_not_deleted" })),
|
|
117
|
+
stage_id: Type.Optional(Type.Number({ description: "Filter by pipeline stage ID" })),
|
|
118
|
+
user_id: Type.Optional(Type.Number({ description: "Filter by owner user ID" })),
|
|
119
|
+
limit: Type.Optional(Type.Number({ description: "Number of results (default 100, max 500)" })),
|
|
120
|
+
start: Type.Optional(Type.Number({ description: "Pagination start (default 0)" })),
|
|
121
|
+
}),
|
|
122
|
+
async execute(_id, params) {
|
|
123
|
+
const { status, stage_id, user_id, limit, start } = params as {
|
|
124
|
+
status?: string;
|
|
125
|
+
stage_id?: number;
|
|
126
|
+
user_id?: number;
|
|
127
|
+
limit?: number;
|
|
128
|
+
start?: number;
|
|
129
|
+
};
|
|
130
|
+
const query = new URLSearchParams();
|
|
131
|
+
if (status) query.set("status", status);
|
|
132
|
+
if (stage_id) query.set("stage_id", String(stage_id));
|
|
133
|
+
if (user_id) query.set("user_id", String(user_id));
|
|
134
|
+
if (limit) query.set("limit", String(limit));
|
|
135
|
+
if (start) query.set("start", String(start));
|
|
136
|
+
const data = await pipedriveRequest(`/deals?${query}`);
|
|
137
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
api.registerTool({
|
|
142
|
+
name: "pipedrive_create_deal",
|
|
143
|
+
description: "Create a new deal in Pipedrive",
|
|
144
|
+
parameters: Type.Object({
|
|
145
|
+
title: Type.String({ description: "Deal title (required)" }),
|
|
146
|
+
value: Type.Optional(Type.Number({ description: "Deal value" })),
|
|
147
|
+
currency: Type.Optional(Type.String({ description: "Currency code (e.g., USD, EUR)" })),
|
|
148
|
+
person_id: Type.Optional(Type.Number({ description: "Associated person/contact ID" })),
|
|
149
|
+
org_id: Type.Optional(Type.Number({ description: "Associated organization ID" })),
|
|
150
|
+
stage_id: Type.Optional(Type.Number({ description: "Pipeline stage ID" })),
|
|
151
|
+
user_id: Type.Optional(Type.Number({ description: "Owner user ID" })),
|
|
152
|
+
expected_close_date: Type.Optional(Type.String({ description: "Expected close date (YYYY-MM-DD)" })),
|
|
153
|
+
}),
|
|
154
|
+
async execute(_id, params) {
|
|
155
|
+
const data = await pipedriveRequest("/deals", {
|
|
156
|
+
method: "POST",
|
|
157
|
+
body: JSON.stringify(params),
|
|
158
|
+
});
|
|
159
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
api.registerTool({
|
|
164
|
+
name: "pipedrive_update_deal",
|
|
165
|
+
description: "Update an existing deal",
|
|
166
|
+
parameters: Type.Object({
|
|
167
|
+
id: Type.Number({ description: "Deal ID to update (required)" }),
|
|
168
|
+
title: Type.Optional(Type.String({ description: "New title" })),
|
|
169
|
+
value: Type.Optional(Type.Number({ description: "New value" })),
|
|
170
|
+
currency: Type.Optional(Type.String({ description: "Currency code" })),
|
|
171
|
+
status: Type.Optional(Type.String({ description: "Status: open, won, lost, deleted" })),
|
|
172
|
+
stage_id: Type.Optional(Type.Number({ description: "Move to stage ID" })),
|
|
173
|
+
user_id: Type.Optional(Type.Number({ description: "New owner user ID" })),
|
|
174
|
+
expected_close_date: Type.Optional(Type.String({ description: "Expected close date (YYYY-MM-DD)" })),
|
|
175
|
+
lost_reason: Type.Optional(Type.String({ description: "Reason for losing (when status=lost)" })),
|
|
176
|
+
}),
|
|
177
|
+
async execute(_id, params) {
|
|
178
|
+
const { id, ...updateParams } = params as { id: number } & Record<string, unknown>;
|
|
179
|
+
const data = await pipedriveRequest(`/deals/${id}`, {
|
|
180
|
+
method: "PUT",
|
|
181
|
+
body: JSON.stringify(updateParams),
|
|
182
|
+
});
|
|
183
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
184
|
+
},
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
api.registerTool({
|
|
188
|
+
name: "pipedrive_delete_deal",
|
|
189
|
+
description: "Delete a deal",
|
|
190
|
+
parameters: Type.Object({
|
|
191
|
+
id: Type.Number({ description: "Deal ID to delete" }),
|
|
192
|
+
}),
|
|
193
|
+
async execute(_id, params) {
|
|
194
|
+
const { id } = params as { id: number };
|
|
195
|
+
const data = await pipedriveRequest(`/deals/${id}`, { method: "DELETE" });
|
|
196
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// ============ PERSONS (CONTACTS) ============
|
|
201
|
+
|
|
202
|
+
api.registerTool({
|
|
203
|
+
name: "pipedrive_search_persons",
|
|
204
|
+
description: "Search for persons/contacts in Pipedrive",
|
|
205
|
+
parameters: Type.Object({
|
|
206
|
+
term: Type.String({ description: "Search term (name, email, phone)" }),
|
|
207
|
+
}),
|
|
208
|
+
async execute(_id, params) {
|
|
209
|
+
const { term } = params as { term: string };
|
|
210
|
+
const data = await pipedriveRequest(`/persons/search?term=${encodeURIComponent(term)}`);
|
|
211
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
api.registerTool({
|
|
216
|
+
name: "pipedrive_get_person",
|
|
217
|
+
description: "Get details of a specific person by ID",
|
|
218
|
+
parameters: Type.Object({
|
|
219
|
+
id: Type.Number({ description: "Person ID" }),
|
|
220
|
+
}),
|
|
221
|
+
async execute(_id, params) {
|
|
222
|
+
const { id } = params as { id: number };
|
|
223
|
+
const data = await pipedriveRequest(`/persons/${id}`);
|
|
224
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
225
|
+
},
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
api.registerTool({
|
|
229
|
+
name: "pipedrive_create_person",
|
|
230
|
+
description: "Create a new person/contact",
|
|
231
|
+
parameters: Type.Object({
|
|
232
|
+
name: Type.String({ description: "Person name (required)" }),
|
|
233
|
+
email: Type.Optional(Type.String({ description: "Email address" })),
|
|
234
|
+
phone: Type.Optional(Type.String({ description: "Phone number" })),
|
|
235
|
+
org_id: Type.Optional(Type.Number({ description: "Associated organization ID" })),
|
|
236
|
+
}),
|
|
237
|
+
async execute(_id, params) {
|
|
238
|
+
const data = await pipedriveRequest("/persons", {
|
|
239
|
+
method: "POST",
|
|
240
|
+
body: JSON.stringify(params),
|
|
241
|
+
});
|
|
242
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
243
|
+
},
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
api.registerTool({
|
|
247
|
+
name: "pipedrive_update_person",
|
|
248
|
+
description: "Update an existing person/contact",
|
|
249
|
+
parameters: Type.Object({
|
|
250
|
+
id: Type.Number({ description: "Person ID to update (required)" }),
|
|
251
|
+
name: Type.Optional(Type.String({ description: "New name" })),
|
|
252
|
+
email: Type.Optional(Type.String({ description: "New email" })),
|
|
253
|
+
phone: Type.Optional(Type.String({ description: "New phone" })),
|
|
254
|
+
org_id: Type.Optional(Type.Number({ description: "New organization ID" })),
|
|
255
|
+
}),
|
|
256
|
+
async execute(_id, params) {
|
|
257
|
+
const { id, ...updateParams } = params as { id: number } & Record<string, unknown>;
|
|
258
|
+
const data = await pipedriveRequest(`/persons/${id}`, {
|
|
259
|
+
method: "PUT",
|
|
260
|
+
body: JSON.stringify(updateParams),
|
|
261
|
+
});
|
|
262
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
263
|
+
},
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
// ============ ORGANIZATIONS ============
|
|
267
|
+
|
|
268
|
+
api.registerTool({
|
|
269
|
+
name: "pipedrive_search_organizations",
|
|
270
|
+
description: "Search for organizations in Pipedrive",
|
|
271
|
+
parameters: Type.Object({
|
|
272
|
+
term: Type.String({ description: "Search term (organization name)" }),
|
|
273
|
+
}),
|
|
274
|
+
async execute(_id, params) {
|
|
275
|
+
const { term } = params as { term: string };
|
|
276
|
+
const data = await pipedriveRequest(`/organizations/search?term=${encodeURIComponent(term)}`);
|
|
277
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
278
|
+
},
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
api.registerTool({
|
|
282
|
+
name: "pipedrive_get_organization",
|
|
283
|
+
description: "Get details of a specific organization by ID",
|
|
284
|
+
parameters: Type.Object({
|
|
285
|
+
id: Type.Number({ description: "Organization ID" }),
|
|
286
|
+
}),
|
|
287
|
+
async execute(_id, params) {
|
|
288
|
+
const { id } = params as { id: number };
|
|
289
|
+
const data = await pipedriveRequest(`/organizations/${id}`);
|
|
290
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
291
|
+
},
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
api.registerTool({
|
|
295
|
+
name: "pipedrive_create_organization",
|
|
296
|
+
description: "Create a new organization",
|
|
297
|
+
parameters: Type.Object({
|
|
298
|
+
name: Type.String({ description: "Organization name (required)" }),
|
|
299
|
+
address: Type.Optional(Type.String({ description: "Address" })),
|
|
300
|
+
}),
|
|
301
|
+
async execute(_id, params) {
|
|
302
|
+
const data = await pipedriveRequest("/organizations", {
|
|
303
|
+
method: "POST",
|
|
304
|
+
body: JSON.stringify(params),
|
|
305
|
+
});
|
|
306
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
307
|
+
},
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
// ============ ACTIVITIES ============
|
|
311
|
+
|
|
312
|
+
api.registerTool({
|
|
313
|
+
name: "pipedrive_list_activities",
|
|
314
|
+
description: "List activities (tasks, calls, meetings) with optional filters",
|
|
315
|
+
parameters: Type.Object({
|
|
316
|
+
deal_id: Type.Optional(Type.Number({ description: "Filter by deal ID" })),
|
|
317
|
+
person_id: Type.Optional(Type.Number({ description: "Filter by person ID" })),
|
|
318
|
+
org_id: Type.Optional(Type.Number({ description: "Filter by organization ID" })),
|
|
319
|
+
done: Type.Optional(Type.Number({ description: "Filter by completion: 0 = not done, 1 = done" })),
|
|
320
|
+
type: Type.Optional(Type.String({ description: "Filter by type: call, meeting, task, deadline, email, lunch" })),
|
|
321
|
+
limit: Type.Optional(Type.Number({ description: "Number of results (default 100)" })),
|
|
322
|
+
start: Type.Optional(Type.Number({ description: "Pagination start" })),
|
|
323
|
+
}),
|
|
324
|
+
async execute(_id, params) {
|
|
325
|
+
const query = new URLSearchParams();
|
|
326
|
+
for (const [key, value] of Object.entries(params)) {
|
|
327
|
+
if (value !== undefined) query.set(key, String(value));
|
|
328
|
+
}
|
|
329
|
+
const data = await pipedriveRequest(`/activities?${query}`);
|
|
330
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
331
|
+
},
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
api.registerTool({
|
|
335
|
+
name: "pipedrive_get_activity",
|
|
336
|
+
description: "Get details of a specific activity by ID",
|
|
337
|
+
parameters: Type.Object({
|
|
338
|
+
id: Type.Number({ description: "Activity ID" }),
|
|
339
|
+
}),
|
|
340
|
+
async execute(_id, params) {
|
|
341
|
+
const { id } = params as { id: number };
|
|
342
|
+
const data = await pipedriveRequest(`/activities/${id}`);
|
|
343
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
344
|
+
},
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
api.registerTool({
|
|
348
|
+
name: "pipedrive_create_activity",
|
|
349
|
+
description: "Create a new activity (task, call, meeting, etc.)",
|
|
350
|
+
parameters: Type.Object({
|
|
351
|
+
subject: Type.String({ description: "Activity subject/title (required)" }),
|
|
352
|
+
type: Type.String({ description: "Activity type: call, meeting, task, deadline, email, lunch (required)" }),
|
|
353
|
+
due_date: Type.Optional(Type.String({ description: "Due date in YYYY-MM-DD format" })),
|
|
354
|
+
due_time: Type.Optional(Type.String({ description: "Due time in HH:MM format" })),
|
|
355
|
+
duration: Type.Optional(Type.String({ description: "Duration in HH:MM format" })),
|
|
356
|
+
deal_id: Type.Optional(Type.Number({ description: "Associated deal ID" })),
|
|
357
|
+
person_id: Type.Optional(Type.Number({ description: "Associated person ID" })),
|
|
358
|
+
org_id: Type.Optional(Type.Number({ description: "Associated organization ID" })),
|
|
359
|
+
note: Type.Optional(Type.String({ description: "Activity notes/description" })),
|
|
360
|
+
done: Type.Optional(Type.Number({ description: "Mark as done: 0 = not done, 1 = done" })),
|
|
361
|
+
}),
|
|
362
|
+
async execute(_id, params) {
|
|
363
|
+
const data = await pipedriveRequest("/activities", {
|
|
364
|
+
method: "POST",
|
|
365
|
+
body: JSON.stringify(params),
|
|
366
|
+
});
|
|
367
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
368
|
+
},
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
api.registerTool({
|
|
372
|
+
name: "pipedrive_update_activity",
|
|
373
|
+
description: "Update an existing activity",
|
|
374
|
+
parameters: Type.Object({
|
|
375
|
+
id: Type.Number({ description: "Activity ID to update (required)" }),
|
|
376
|
+
subject: Type.Optional(Type.String({ description: "New subject" })),
|
|
377
|
+
type: Type.Optional(Type.String({ description: "New type" })),
|
|
378
|
+
due_date: Type.Optional(Type.String({ description: "New due date (YYYY-MM-DD)" })),
|
|
379
|
+
due_time: Type.Optional(Type.String({ description: "New due time (HH:MM)" })),
|
|
380
|
+
done: Type.Optional(Type.Number({ description: "Mark as done: 0 = not done, 1 = done" })),
|
|
381
|
+
note: Type.Optional(Type.String({ description: "New notes" })),
|
|
382
|
+
}),
|
|
383
|
+
async execute(_id, params) {
|
|
384
|
+
const { id, ...updateParams } = params as { id: number } & Record<string, unknown>;
|
|
385
|
+
const data = await pipedriveRequest(`/activities/${id}`, {
|
|
386
|
+
method: "PUT",
|
|
387
|
+
body: JSON.stringify(updateParams),
|
|
388
|
+
});
|
|
389
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
390
|
+
},
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
api.registerTool({
|
|
394
|
+
name: "pipedrive_delete_activity",
|
|
395
|
+
description: "Delete an activity",
|
|
396
|
+
parameters: Type.Object({
|
|
397
|
+
id: Type.Number({ description: "Activity ID to delete" }),
|
|
398
|
+
}),
|
|
399
|
+
async execute(_id, params) {
|
|
400
|
+
const { id } = params as { id: number };
|
|
401
|
+
const data = await pipedriveRequest(`/activities/${id}`, { method: "DELETE" });
|
|
402
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
403
|
+
},
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
// ============ PIPELINES & STAGES ============
|
|
407
|
+
|
|
408
|
+
api.registerTool({
|
|
409
|
+
name: "pipedrive_list_pipelines",
|
|
410
|
+
description: "List all pipelines",
|
|
411
|
+
parameters: Type.Object({}),
|
|
412
|
+
async execute() {
|
|
413
|
+
const data = await pipedriveRequest("/pipelines");
|
|
414
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
415
|
+
},
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
api.registerTool({
|
|
419
|
+
name: "pipedrive_list_stages",
|
|
420
|
+
description: "List all stages, optionally filtered by pipeline",
|
|
421
|
+
parameters: Type.Object({
|
|
422
|
+
pipeline_id: Type.Optional(Type.Number({ description: "Filter by pipeline ID" })),
|
|
423
|
+
}),
|
|
424
|
+
async execute(_id, params) {
|
|
425
|
+
const { pipeline_id } = params as { pipeline_id?: number };
|
|
426
|
+
let endpoint = "/stages";
|
|
427
|
+
if (pipeline_id) endpoint += `?pipeline_id=${pipeline_id}`;
|
|
428
|
+
const data = await pipedriveRequest(endpoint);
|
|
429
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
430
|
+
},
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
// ============ NOTES ============
|
|
434
|
+
|
|
435
|
+
api.registerTool({
|
|
436
|
+
name: "pipedrive_list_notes",
|
|
437
|
+
description: "List notes for a deal, person, or organization",
|
|
438
|
+
parameters: Type.Object({
|
|
439
|
+
deal_id: Type.Optional(Type.Number({ description: "Filter by deal ID" })),
|
|
440
|
+
person_id: Type.Optional(Type.Number({ description: "Filter by person ID" })),
|
|
441
|
+
org_id: Type.Optional(Type.Number({ description: "Filter by organization ID" })),
|
|
442
|
+
limit: Type.Optional(Type.Number({ description: "Number of results" })),
|
|
443
|
+
}),
|
|
444
|
+
async execute(_id, params) {
|
|
445
|
+
const query = new URLSearchParams();
|
|
446
|
+
for (const [key, value] of Object.entries(params)) {
|
|
447
|
+
if (value !== undefined) query.set(key, String(value));
|
|
448
|
+
}
|
|
449
|
+
const data = await pipedriveRequest(`/notes?${query}`);
|
|
450
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
451
|
+
},
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
api.registerTool({
|
|
455
|
+
name: "pipedrive_create_note",
|
|
456
|
+
description: "Create a note on a deal, person, or organization",
|
|
457
|
+
parameters: Type.Object({
|
|
458
|
+
content: Type.String({ description: "Note content (required)" }),
|
|
459
|
+
deal_id: Type.Optional(Type.Number({ description: "Attach to deal ID" })),
|
|
460
|
+
person_id: Type.Optional(Type.Number({ description: "Attach to person ID" })),
|
|
461
|
+
org_id: Type.Optional(Type.Number({ description: "Attach to organization ID" })),
|
|
462
|
+
}),
|
|
463
|
+
async execute(_id, params) {
|
|
464
|
+
const data = await pipedriveRequest("/notes", {
|
|
465
|
+
method: "POST",
|
|
466
|
+
body: JSON.stringify(params),
|
|
467
|
+
});
|
|
468
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
469
|
+
},
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
// ============ USERS ============
|
|
473
|
+
|
|
474
|
+
api.registerTool({
|
|
475
|
+
name: "pipedrive_list_users",
|
|
476
|
+
description: "List all users in the Pipedrive account",
|
|
477
|
+
parameters: Type.Object({}),
|
|
478
|
+
async execute() {
|
|
479
|
+
const data = await pipedriveRequest("/users");
|
|
480
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
481
|
+
},
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
api.registerTool({
|
|
485
|
+
name: "pipedrive_get_current_user",
|
|
486
|
+
description: "Get the current authenticated user's details",
|
|
487
|
+
parameters: Type.Object({}),
|
|
488
|
+
async execute() {
|
|
489
|
+
const data = await pipedriveRequest("/users/me");
|
|
490
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
491
|
+
},
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
console.log(`[pipedrive] Registered ${22} tools for ${cfg.domain}.pipedrive.com`);
|
|
495
|
+
},
|
|
496
|
+
};
|
|
497
|
+
|
|
498
|
+
export default plugin;
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "clawdbot-pipedrive",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Pipedrive CRM integration for Clawdbot",
|
|
6
|
+
"author": "graileanu",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/graileanu/clawdbot-pipedrive"
|
|
11
|
+
},
|
|
12
|
+
"clawdbot": {
|
|
13
|
+
"extensions": ["./index.ts"]
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@sinclair/typebox": "0.34.47"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"clawdbot",
|
|
20
|
+
"clawdbot-plugin",
|
|
21
|
+
"pipedrive",
|
|
22
|
+
"crm"
|
|
23
|
+
]
|
|
24
|
+
}
|