clawdbot-pipedrive 1.1.0 → 2.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 +9 -7
- package/index.ts +377 -63
- package/install.sh +8 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,15 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
Pipedrive CRM integration plugin for [Clawdbot](https://clawd.bot).
|
|
4
4
|
|
|
5
|
+
**Now using Pipedrive API v2** - 50% lower token costs, better filtering, cursor-based pagination.
|
|
6
|
+
|
|
5
7
|
## Features
|
|
6
8
|
|
|
7
|
-
- **Deals**: Search, list, create, update, delete deals
|
|
8
|
-
- **Persons**: Search,
|
|
9
|
-
- **Organizations**: Search,
|
|
10
|
-
- **Activities**: List, create, update, delete tasks/calls/meetings
|
|
11
|
-
- **Pipelines & Stages**: List pipelines and stages
|
|
12
|
-
- **Notes**: List
|
|
13
|
-
- **Users**: List users, get current user
|
|
9
|
+
- **Deals**: Search, list, create, update, delete deals (v2)
|
|
10
|
+
- **Persons**: Search, list, create, update, delete contacts (v2)
|
|
11
|
+
- **Organizations**: Search, list, create, update, delete companies (v2)
|
|
12
|
+
- **Activities**: List, create, update, delete tasks/calls/meetings (v2)
|
|
13
|
+
- **Pipelines & Stages**: List pipelines and stages (v2)
|
|
14
|
+
- **Notes**: List, create, update, delete notes (v1 - no v2 yet)
|
|
15
|
+
- **Users**: List users, get current user (v1 - no v2 yet)
|
|
14
16
|
|
|
15
17
|
## Installation
|
|
16
18
|
|
package/index.ts
CHANGED
|
@@ -1,4 +1,101 @@
|
|
|
1
1
|
import { Type } from "@sinclair/typebox";
|
|
2
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
|
|
6
|
+
// Skill template - embedded so it works without network access
|
|
7
|
+
const SKILL_TEMPLATE = `# Pipedrive CRM Workflows
|
|
8
|
+
|
|
9
|
+
> Customize this file for your organization's Pipedrive workflows.
|
|
10
|
+
> This file will NOT be overwritten by plugin updates.
|
|
11
|
+
|
|
12
|
+
## Deal Naming Convention
|
|
13
|
+
|
|
14
|
+
When creating deals, use this format:
|
|
15
|
+
- **Title**: \`[Company Name] - [Product/Plan] - [Value]\`
|
|
16
|
+
- Example: \`Acme Corp - Enterprise - $2,500/mo\`
|
|
17
|
+
|
|
18
|
+
## Pipeline Stages
|
|
19
|
+
|
|
20
|
+
| Stage ID | Name | When to use |
|
|
21
|
+
|----------|------|-------------|
|
|
22
|
+
| 1 | Lead | Initial contact |
|
|
23
|
+
| 2 | Qualified | Confirmed interest |
|
|
24
|
+
| 3 | Proposal | Pricing sent |
|
|
25
|
+
| 4 | Negotiation | Active discussions |
|
|
26
|
+
| 5 | Closed Won | Deal signed |
|
|
27
|
+
| 6 | Closed Lost | Deal lost |
|
|
28
|
+
|
|
29
|
+
> **Note**: Replace stage IDs with your actual Pipedrive stage IDs.
|
|
30
|
+
> Find them via: \`pipedrive_list_stages\`
|
|
31
|
+
|
|
32
|
+
## Required Fields
|
|
33
|
+
|
|
34
|
+
When creating deals, always include:
|
|
35
|
+
- \`title\` - Following naming convention above
|
|
36
|
+
- \`value\` - Deal value in your currency
|
|
37
|
+
- \`person_id\` or \`org_id\` - Link to contact/company
|
|
38
|
+
|
|
39
|
+
## Activity Types
|
|
40
|
+
|
|
41
|
+
| Type | Use for | Subject format |
|
|
42
|
+
|------|---------|----------------|
|
|
43
|
+
| \`call\` | Phone calls | "Call: [topic]" |
|
|
44
|
+
| \`meeting\` | Demos, meetings | "Meeting: [purpose]" |
|
|
45
|
+
| \`task\` | Follow-ups, to-dos | "Task: [action]" |
|
|
46
|
+
| \`email\` | Email follow-ups | "Email: [subject]" |
|
|
47
|
+
|
|
48
|
+
## Common Workflows
|
|
49
|
+
|
|
50
|
+
### New Lead
|
|
51
|
+
1. Search if contact exists: \`pipedrive_search_persons\`
|
|
52
|
+
2. Create person if new: \`pipedrive_create_person\`
|
|
53
|
+
3. Create deal: \`pipedrive_create_deal\`
|
|
54
|
+
4. Schedule follow-up: \`pipedrive_create_activity\`
|
|
55
|
+
|
|
56
|
+
### After Demo
|
|
57
|
+
1. Update deal stage: \`pipedrive_update_deal\` with next stage_id
|
|
58
|
+
2. Add notes: \`pipedrive_create_note\`
|
|
59
|
+
3. Create follow-up task: \`pipedrive_create_activity\`
|
|
60
|
+
|
|
61
|
+
### Close Won
|
|
62
|
+
1. Update deal: \`pipedrive_update_deal\` with \`status: "won"\`
|
|
63
|
+
2. Add closing note: \`pipedrive_create_note\`
|
|
64
|
+
|
|
65
|
+
### Close Lost
|
|
66
|
+
1. Update deal: \`pipedrive_update_deal\` with \`status: "lost"\` and \`lost_reason\`
|
|
67
|
+
`;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Sets up the skill template file
|
|
71
|
+
* - Creates skill if it doesn't exist
|
|
72
|
+
* - If skill exists, saves new template as .latest for comparison
|
|
73
|
+
*/
|
|
74
|
+
function setupSkillTemplate(): void {
|
|
75
|
+
const skillDir = join(homedir(), ".clawdbot", "skills", "pipedrive");
|
|
76
|
+
const skillFile = join(skillDir, "SKILL.md");
|
|
77
|
+
const latestFile = join(skillDir, "SKILL.md.latest");
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
mkdirSync(skillDir, { recursive: true });
|
|
81
|
+
|
|
82
|
+
if (!existsSync(skillFile)) {
|
|
83
|
+
writeFileSync(skillFile, SKILL_TEMPLATE);
|
|
84
|
+
console.log(`[pipedrive] Created skill template: ${skillFile}`);
|
|
85
|
+
console.log("[pipedrive] Customize this file with your organization's workflows.");
|
|
86
|
+
} else {
|
|
87
|
+
const existing = readFileSync(skillFile, "utf-8");
|
|
88
|
+
if (existing !== SKILL_TEMPLATE) {
|
|
89
|
+
writeFileSync(latestFile, SKILL_TEMPLATE);
|
|
90
|
+
console.log(`[pipedrive] Skill file exists: ${skillFile} (not modified)`);
|
|
91
|
+
console.log(`[pipedrive] New template available: ${latestFile}`);
|
|
92
|
+
console.log("[pipedrive] Compare with: diff ~/.clawdbot/skills/pipedrive/SKILL.md{,.latest}");
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
} catch (err) {
|
|
96
|
+
console.warn("[pipedrive] Could not set up skill template:", err);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
2
99
|
|
|
3
100
|
type PipedriveConfig = {
|
|
4
101
|
apiKey?: string;
|
|
@@ -30,8 +127,8 @@ type ClawdbotPluginDefinition = {
|
|
|
30
127
|
const plugin: ClawdbotPluginDefinition = {
|
|
31
128
|
id: "pipedrive",
|
|
32
129
|
name: "Pipedrive CRM",
|
|
33
|
-
description: "Interact with Pipedrive deals, persons, organizations, and activities",
|
|
34
|
-
version: "
|
|
130
|
+
description: "Interact with Pipedrive deals, persons, organizations, and activities (API v2)",
|
|
131
|
+
version: "2.0.0",
|
|
35
132
|
|
|
36
133
|
configSchema: {
|
|
37
134
|
parse: (v) => v as PipedriveConfig,
|
|
@@ -50,6 +147,8 @@ const plugin: ClawdbotPluginDefinition = {
|
|
|
50
147
|
},
|
|
51
148
|
|
|
52
149
|
register(api) {
|
|
150
|
+
setupSkillTemplate();
|
|
151
|
+
|
|
53
152
|
const cfg = api.pluginConfig as PipedriveConfig;
|
|
54
153
|
|
|
55
154
|
if (!cfg.apiKey || !cfg.domain) {
|
|
@@ -57,16 +156,20 @@ const plugin: ClawdbotPluginDefinition = {
|
|
|
57
156
|
return;
|
|
58
157
|
}
|
|
59
158
|
|
|
60
|
-
const
|
|
159
|
+
const baseUrlV2 = `https://${cfg.domain}.pipedrive.com/api/v2`;
|
|
160
|
+
const baseUrlV1 = `https://${cfg.domain}.pipedrive.com/api/v1`; // For endpoints not yet in v2
|
|
61
161
|
|
|
62
|
-
async function pipedriveRequest(endpoint: string, options?: RequestInit) {
|
|
162
|
+
async function pipedriveRequest(endpoint: string, options?: RequestInit & { useV1?: boolean }) {
|
|
163
|
+
const baseUrl = options?.useV1 ? baseUrlV1 : baseUrlV2;
|
|
63
164
|
const url = new URL(`${baseUrl}${endpoint}`);
|
|
64
165
|
url.searchParams.set("api_token", cfg.apiKey!);
|
|
166
|
+
|
|
167
|
+
const { useV1, ...fetchOptions } = options || {};
|
|
65
168
|
const res = await fetch(url.toString(), {
|
|
66
|
-
...
|
|
169
|
+
...fetchOptions,
|
|
67
170
|
headers: {
|
|
68
171
|
"Content-Type": "application/json",
|
|
69
|
-
...
|
|
172
|
+
...fetchOptions?.headers,
|
|
70
173
|
},
|
|
71
174
|
});
|
|
72
175
|
if (!res.ok) {
|
|
@@ -76,7 +179,7 @@ const plugin: ClawdbotPluginDefinition = {
|
|
|
76
179
|
return res.json();
|
|
77
180
|
}
|
|
78
181
|
|
|
79
|
-
// ============ DEALS ============
|
|
182
|
+
// ============ DEALS (v2) ============
|
|
80
183
|
|
|
81
184
|
api.registerTool({
|
|
82
185
|
name: "pipedrive_search_deals",
|
|
@@ -84,14 +187,16 @@ const plugin: ClawdbotPluginDefinition = {
|
|
|
84
187
|
parameters: Type.Object({
|
|
85
188
|
term: Type.String({ description: "Search term" }),
|
|
86
189
|
status: Type.Optional(
|
|
87
|
-
Type.String({ description: "Filter by status: open, won, lost, deleted
|
|
190
|
+
Type.String({ description: "Filter by status: open, won, lost, deleted" })
|
|
88
191
|
),
|
|
192
|
+
limit: Type.Optional(Type.Number({ description: "Number of results (default 100)" })),
|
|
89
193
|
}),
|
|
90
194
|
async execute(_id, params) {
|
|
91
|
-
const { term, status } = params as { term: string; status?: string };
|
|
92
|
-
|
|
93
|
-
if (status)
|
|
94
|
-
|
|
195
|
+
const { term, status, limit } = params as { term: string; status?: string; limit?: number };
|
|
196
|
+
const query = new URLSearchParams({ term });
|
|
197
|
+
if (status) query.set("status", status);
|
|
198
|
+
if (limit) query.set("limit", String(limit));
|
|
199
|
+
const data = await pipedriveRequest(`/deals/search?${query}`);
|
|
95
200
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
96
201
|
},
|
|
97
202
|
});
|
|
@@ -113,26 +218,22 @@ const plugin: ClawdbotPluginDefinition = {
|
|
|
113
218
|
name: "pipedrive_list_deals",
|
|
114
219
|
description: "List deals with optional filters",
|
|
115
220
|
parameters: Type.Object({
|
|
116
|
-
status: Type.Optional(Type.String({ description: "Filter by status: open, won, lost, deleted
|
|
221
|
+
status: Type.Optional(Type.String({ description: "Filter by status: open, won, lost, deleted" })),
|
|
117
222
|
stage_id: Type.Optional(Type.Number({ description: "Filter by pipeline stage ID" })),
|
|
118
|
-
|
|
223
|
+
owner_id: Type.Optional(Type.Number({ description: "Filter by owner user ID" })),
|
|
224
|
+
person_id: Type.Optional(Type.Number({ description: "Filter by person ID" })),
|
|
225
|
+
org_id: Type.Optional(Type.Number({ description: "Filter by organization ID" })),
|
|
226
|
+
pipeline_id: Type.Optional(Type.Number({ description: "Filter by pipeline ID" })),
|
|
119
227
|
limit: Type.Optional(Type.Number({ description: "Number of results (default 100, max 500)" })),
|
|
120
|
-
|
|
228
|
+
cursor: Type.Optional(Type.String({ description: "Pagination cursor from previous response" })),
|
|
229
|
+
sort_by: Type.Optional(Type.String({ description: "Sort by: id, add_time, update_time" })),
|
|
230
|
+
sort_direction: Type.Optional(Type.String({ description: "Sort direction: asc, desc" })),
|
|
121
231
|
}),
|
|
122
232
|
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
233
|
const query = new URLSearchParams();
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
if (limit) query.set("limit", String(limit));
|
|
135
|
-
if (start) query.set("start", String(start));
|
|
234
|
+
for (const [key, value] of Object.entries(params)) {
|
|
235
|
+
if (value !== undefined) query.set(key, String(value));
|
|
236
|
+
}
|
|
136
237
|
const data = await pipedriveRequest(`/deals?${query}`);
|
|
137
238
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
138
239
|
},
|
|
@@ -148,7 +249,8 @@ const plugin: ClawdbotPluginDefinition = {
|
|
|
148
249
|
person_id: Type.Optional(Type.Number({ description: "Associated person/contact ID" })),
|
|
149
250
|
org_id: Type.Optional(Type.Number({ description: "Associated organization ID" })),
|
|
150
251
|
stage_id: Type.Optional(Type.Number({ description: "Pipeline stage ID" })),
|
|
151
|
-
|
|
252
|
+
owner_id: Type.Optional(Type.Number({ description: "Owner user ID" })),
|
|
253
|
+
pipeline_id: Type.Optional(Type.Number({ description: "Pipeline ID" })),
|
|
152
254
|
expected_close_date: Type.Optional(Type.String({ description: "Expected close date (YYYY-MM-DD)" })),
|
|
153
255
|
}),
|
|
154
256
|
async execute(_id, params) {
|
|
@@ -170,14 +272,15 @@ const plugin: ClawdbotPluginDefinition = {
|
|
|
170
272
|
currency: Type.Optional(Type.String({ description: "Currency code" })),
|
|
171
273
|
status: Type.Optional(Type.String({ description: "Status: open, won, lost, deleted" })),
|
|
172
274
|
stage_id: Type.Optional(Type.Number({ description: "Move to stage ID" })),
|
|
173
|
-
|
|
275
|
+
owner_id: Type.Optional(Type.Number({ description: "New owner user ID" })),
|
|
276
|
+
pipeline_id: Type.Optional(Type.Number({ description: "Move to pipeline ID" })),
|
|
174
277
|
expected_close_date: Type.Optional(Type.String({ description: "Expected close date (YYYY-MM-DD)" })),
|
|
175
278
|
lost_reason: Type.Optional(Type.String({ description: "Reason for losing (when status=lost)" })),
|
|
176
279
|
}),
|
|
177
280
|
async execute(_id, params) {
|
|
178
281
|
const { id, ...updateParams } = params as { id: number } & Record<string, unknown>;
|
|
179
282
|
const data = await pipedriveRequest(`/deals/${id}`, {
|
|
180
|
-
method: "
|
|
283
|
+
method: "PATCH", // v2 uses PATCH instead of PUT
|
|
181
284
|
body: JSON.stringify(updateParams),
|
|
182
285
|
});
|
|
183
286
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
@@ -186,7 +289,7 @@ const plugin: ClawdbotPluginDefinition = {
|
|
|
186
289
|
|
|
187
290
|
api.registerTool({
|
|
188
291
|
name: "pipedrive_delete_deal",
|
|
189
|
-
description: "Delete a deal",
|
|
292
|
+
description: "Delete a deal (marks as deleted, 30-day retention)",
|
|
190
293
|
parameters: Type.Object({
|
|
191
294
|
id: Type.Number({ description: "Deal ID to delete" }),
|
|
192
295
|
}),
|
|
@@ -197,17 +300,20 @@ const plugin: ClawdbotPluginDefinition = {
|
|
|
197
300
|
},
|
|
198
301
|
});
|
|
199
302
|
|
|
200
|
-
// ============ PERSONS (
|
|
303
|
+
// ============ PERSONS (v2) ============
|
|
201
304
|
|
|
202
305
|
api.registerTool({
|
|
203
306
|
name: "pipedrive_search_persons",
|
|
204
|
-
description: "Search for persons/contacts
|
|
307
|
+
description: "Search for persons/contacts by name, email, phone, or notes",
|
|
205
308
|
parameters: Type.Object({
|
|
206
309
|
term: Type.String({ description: "Search term (name, email, phone)" }),
|
|
310
|
+
limit: Type.Optional(Type.Number({ description: "Number of results" })),
|
|
207
311
|
}),
|
|
208
312
|
async execute(_id, params) {
|
|
209
|
-
const { term } = params as { term: string };
|
|
210
|
-
const
|
|
313
|
+
const { term, limit } = params as { term: string; limit?: number };
|
|
314
|
+
const query = new URLSearchParams({ term });
|
|
315
|
+
if (limit) query.set("limit", String(limit));
|
|
316
|
+
const data = await pipedriveRequest(`/persons/search?${query}`);
|
|
211
317
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
212
318
|
},
|
|
213
319
|
});
|
|
@@ -225,6 +331,27 @@ const plugin: ClawdbotPluginDefinition = {
|
|
|
225
331
|
},
|
|
226
332
|
});
|
|
227
333
|
|
|
334
|
+
api.registerTool({
|
|
335
|
+
name: "pipedrive_list_persons",
|
|
336
|
+
description: "List all persons with optional filters",
|
|
337
|
+
parameters: Type.Object({
|
|
338
|
+
owner_id: Type.Optional(Type.Number({ description: "Filter by owner user ID" })),
|
|
339
|
+
org_id: Type.Optional(Type.Number({ description: "Filter by organization ID" })),
|
|
340
|
+
limit: Type.Optional(Type.Number({ description: "Number of results (default 100)" })),
|
|
341
|
+
cursor: Type.Optional(Type.String({ description: "Pagination cursor" })),
|
|
342
|
+
sort_by: Type.Optional(Type.String({ description: "Sort by: id, add_time, update_time, name" })),
|
|
343
|
+
sort_direction: Type.Optional(Type.String({ description: "Sort direction: asc, desc" })),
|
|
344
|
+
}),
|
|
345
|
+
async execute(_id, params) {
|
|
346
|
+
const query = new URLSearchParams();
|
|
347
|
+
for (const [key, value] of Object.entries(params)) {
|
|
348
|
+
if (value !== undefined) query.set(key, String(value));
|
|
349
|
+
}
|
|
350
|
+
const data = await pipedriveRequest(`/persons?${query}`);
|
|
351
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
352
|
+
},
|
|
353
|
+
});
|
|
354
|
+
|
|
228
355
|
api.registerTool({
|
|
229
356
|
name: "pipedrive_create_person",
|
|
230
357
|
description: "Create a new person/contact",
|
|
@@ -233,11 +360,18 @@ const plugin: ClawdbotPluginDefinition = {
|
|
|
233
360
|
email: Type.Optional(Type.String({ description: "Email address" })),
|
|
234
361
|
phone: Type.Optional(Type.String({ description: "Phone number" })),
|
|
235
362
|
org_id: Type.Optional(Type.Number({ description: "Associated organization ID" })),
|
|
363
|
+
owner_id: Type.Optional(Type.Number({ description: "Owner user ID" })),
|
|
236
364
|
}),
|
|
237
365
|
async execute(_id, params) {
|
|
366
|
+
// v2 expects email/phone as arrays of objects
|
|
367
|
+
const { email, phone, ...rest } = params as { email?: string; phone?: string } & Record<string, unknown>;
|
|
368
|
+
const body: Record<string, unknown> = { ...rest };
|
|
369
|
+
if (email) body.emails = [{ value: email, primary: true, label: "work" }];
|
|
370
|
+
if (phone) body.phones = [{ value: phone, primary: true, label: "work" }];
|
|
371
|
+
|
|
238
372
|
const data = await pipedriveRequest("/persons", {
|
|
239
373
|
method: "POST",
|
|
240
|
-
body: JSON.stringify(
|
|
374
|
+
body: JSON.stringify(body),
|
|
241
375
|
});
|
|
242
376
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
243
377
|
},
|
|
@@ -252,28 +386,49 @@ const plugin: ClawdbotPluginDefinition = {
|
|
|
252
386
|
email: Type.Optional(Type.String({ description: "New email" })),
|
|
253
387
|
phone: Type.Optional(Type.String({ description: "New phone" })),
|
|
254
388
|
org_id: Type.Optional(Type.Number({ description: "New organization ID" })),
|
|
389
|
+
owner_id: Type.Optional(Type.Number({ description: "New owner user ID" })),
|
|
255
390
|
}),
|
|
256
391
|
async execute(_id, params) {
|
|
257
|
-
const { id, ...
|
|
392
|
+
const { id, email, phone, ...rest } = params as { id: number; email?: string; phone?: string } & Record<string, unknown>;
|
|
393
|
+
const body: Record<string, unknown> = { ...rest };
|
|
394
|
+
if (email) body.emails = [{ value: email, primary: true, label: "work" }];
|
|
395
|
+
if (phone) body.phones = [{ value: phone, primary: true, label: "work" }];
|
|
396
|
+
|
|
258
397
|
const data = await pipedriveRequest(`/persons/${id}`, {
|
|
259
|
-
method: "
|
|
260
|
-
body: JSON.stringify(
|
|
398
|
+
method: "PATCH",
|
|
399
|
+
body: JSON.stringify(body),
|
|
261
400
|
});
|
|
262
401
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
263
402
|
},
|
|
264
403
|
});
|
|
265
404
|
|
|
266
|
-
|
|
405
|
+
api.registerTool({
|
|
406
|
+
name: "pipedrive_delete_person",
|
|
407
|
+
description: "Delete a person (marks as deleted, 30-day retention)",
|
|
408
|
+
parameters: Type.Object({
|
|
409
|
+
id: Type.Number({ description: "Person ID to delete" }),
|
|
410
|
+
}),
|
|
411
|
+
async execute(_id, params) {
|
|
412
|
+
const { id } = params as { id: number };
|
|
413
|
+
const data = await pipedriveRequest(`/persons/${id}`, { method: "DELETE" });
|
|
414
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
415
|
+
},
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
// ============ ORGANIZATIONS (v2) ============
|
|
267
419
|
|
|
268
420
|
api.registerTool({
|
|
269
421
|
name: "pipedrive_search_organizations",
|
|
270
|
-
description: "Search for organizations
|
|
422
|
+
description: "Search for organizations by name, address, or notes",
|
|
271
423
|
parameters: Type.Object({
|
|
272
424
|
term: Type.String({ description: "Search term (organization name)" }),
|
|
425
|
+
limit: Type.Optional(Type.Number({ description: "Number of results" })),
|
|
273
426
|
}),
|
|
274
427
|
async execute(_id, params) {
|
|
275
|
-
const { term } = params as { term: string };
|
|
276
|
-
const
|
|
428
|
+
const { term, limit } = params as { term: string; limit?: number };
|
|
429
|
+
const query = new URLSearchParams({ term });
|
|
430
|
+
if (limit) query.set("limit", String(limit));
|
|
431
|
+
const data = await pipedriveRequest(`/organizations/search?${query}`);
|
|
277
432
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
278
433
|
},
|
|
279
434
|
});
|
|
@@ -291,12 +446,33 @@ const plugin: ClawdbotPluginDefinition = {
|
|
|
291
446
|
},
|
|
292
447
|
});
|
|
293
448
|
|
|
449
|
+
api.registerTool({
|
|
450
|
+
name: "pipedrive_list_organizations",
|
|
451
|
+
description: "List all organizations with optional filters",
|
|
452
|
+
parameters: Type.Object({
|
|
453
|
+
owner_id: Type.Optional(Type.Number({ description: "Filter by owner user ID" })),
|
|
454
|
+
limit: Type.Optional(Type.Number({ description: "Number of results (default 100)" })),
|
|
455
|
+
cursor: Type.Optional(Type.String({ description: "Pagination cursor" })),
|
|
456
|
+
sort_by: Type.Optional(Type.String({ description: "Sort by: id, add_time, update_time, name" })),
|
|
457
|
+
sort_direction: Type.Optional(Type.String({ description: "Sort direction: asc, desc" })),
|
|
458
|
+
}),
|
|
459
|
+
async execute(_id, params) {
|
|
460
|
+
const query = new URLSearchParams();
|
|
461
|
+
for (const [key, value] of Object.entries(params)) {
|
|
462
|
+
if (value !== undefined) query.set(key, String(value));
|
|
463
|
+
}
|
|
464
|
+
const data = await pipedriveRequest(`/organizations?${query}`);
|
|
465
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
466
|
+
},
|
|
467
|
+
});
|
|
468
|
+
|
|
294
469
|
api.registerTool({
|
|
295
470
|
name: "pipedrive_create_organization",
|
|
296
471
|
description: "Create a new organization",
|
|
297
472
|
parameters: Type.Object({
|
|
298
473
|
name: Type.String({ description: "Organization name (required)" }),
|
|
299
474
|
address: Type.Optional(Type.String({ description: "Address" })),
|
|
475
|
+
owner_id: Type.Optional(Type.Number({ description: "Owner user ID" })),
|
|
300
476
|
}),
|
|
301
477
|
async execute(_id, params) {
|
|
302
478
|
const data = await pipedriveRequest("/organizations", {
|
|
@@ -307,7 +483,39 @@ const plugin: ClawdbotPluginDefinition = {
|
|
|
307
483
|
},
|
|
308
484
|
});
|
|
309
485
|
|
|
310
|
-
|
|
486
|
+
api.registerTool({
|
|
487
|
+
name: "pipedrive_update_organization",
|
|
488
|
+
description: "Update an existing organization",
|
|
489
|
+
parameters: Type.Object({
|
|
490
|
+
id: Type.Number({ description: "Organization ID to update (required)" }),
|
|
491
|
+
name: Type.Optional(Type.String({ description: "New name" })),
|
|
492
|
+
address: Type.Optional(Type.String({ description: "New address" })),
|
|
493
|
+
owner_id: Type.Optional(Type.Number({ description: "New owner user ID" })),
|
|
494
|
+
}),
|
|
495
|
+
async execute(_id, params) {
|
|
496
|
+
const { id, ...updateParams } = params as { id: number } & Record<string, unknown>;
|
|
497
|
+
const data = await pipedriveRequest(`/organizations/${id}`, {
|
|
498
|
+
method: "PATCH",
|
|
499
|
+
body: JSON.stringify(updateParams),
|
|
500
|
+
});
|
|
501
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
502
|
+
},
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
api.registerTool({
|
|
506
|
+
name: "pipedrive_delete_organization",
|
|
507
|
+
description: "Delete an organization (marks as deleted, 30-day retention)",
|
|
508
|
+
parameters: Type.Object({
|
|
509
|
+
id: Type.Number({ description: "Organization ID to delete" }),
|
|
510
|
+
}),
|
|
511
|
+
async execute(_id, params) {
|
|
512
|
+
const { id } = params as { id: number };
|
|
513
|
+
const data = await pipedriveRequest(`/organizations/${id}`, { method: "DELETE" });
|
|
514
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
515
|
+
},
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
// ============ ACTIVITIES (v2) ============
|
|
311
519
|
|
|
312
520
|
api.registerTool({
|
|
313
521
|
name: "pipedrive_list_activities",
|
|
@@ -316,10 +524,13 @@ const plugin: ClawdbotPluginDefinition = {
|
|
|
316
524
|
deal_id: Type.Optional(Type.Number({ description: "Filter by deal ID" })),
|
|
317
525
|
person_id: Type.Optional(Type.Number({ description: "Filter by person ID" })),
|
|
318
526
|
org_id: Type.Optional(Type.Number({ description: "Filter by organization ID" })),
|
|
319
|
-
|
|
527
|
+
owner_id: Type.Optional(Type.Number({ description: "Filter by owner user ID" })),
|
|
528
|
+
done: Type.Optional(Type.Boolean({ description: "Filter by completion: true = done, false = not done" })),
|
|
320
529
|
type: Type.Optional(Type.String({ description: "Filter by type: call, meeting, task, deadline, email, lunch" })),
|
|
321
530
|
limit: Type.Optional(Type.Number({ description: "Number of results (default 100)" })),
|
|
322
|
-
|
|
531
|
+
cursor: Type.Optional(Type.String({ description: "Pagination cursor" })),
|
|
532
|
+
sort_by: Type.Optional(Type.String({ description: "Sort by: id, add_time, update_time, due_date" })),
|
|
533
|
+
sort_direction: Type.Optional(Type.String({ description: "Sort direction: asc, desc" })),
|
|
323
534
|
}),
|
|
324
535
|
async execute(_id, params) {
|
|
325
536
|
const query = new URLSearchParams();
|
|
@@ -357,7 +568,8 @@ const plugin: ClawdbotPluginDefinition = {
|
|
|
357
568
|
person_id: Type.Optional(Type.Number({ description: "Associated person ID" })),
|
|
358
569
|
org_id: Type.Optional(Type.Number({ description: "Associated organization ID" })),
|
|
359
570
|
note: Type.Optional(Type.String({ description: "Activity notes/description" })),
|
|
360
|
-
done: Type.Optional(Type.
|
|
571
|
+
done: Type.Optional(Type.Boolean({ description: "Mark as done: true = done, false = not done" })),
|
|
572
|
+
owner_id: Type.Optional(Type.Number({ description: "Owner user ID" })),
|
|
361
573
|
}),
|
|
362
574
|
async execute(_id, params) {
|
|
363
575
|
const data = await pipedriveRequest("/activities", {
|
|
@@ -377,13 +589,14 @@ const plugin: ClawdbotPluginDefinition = {
|
|
|
377
589
|
type: Type.Optional(Type.String({ description: "New type" })),
|
|
378
590
|
due_date: Type.Optional(Type.String({ description: "New due date (YYYY-MM-DD)" })),
|
|
379
591
|
due_time: Type.Optional(Type.String({ description: "New due time (HH:MM)" })),
|
|
380
|
-
done: Type.Optional(Type.
|
|
592
|
+
done: Type.Optional(Type.Boolean({ description: "Mark as done: true = done, false = not done" })),
|
|
381
593
|
note: Type.Optional(Type.String({ description: "New notes" })),
|
|
594
|
+
owner_id: Type.Optional(Type.Number({ description: "New owner user ID" })),
|
|
382
595
|
}),
|
|
383
596
|
async execute(_id, params) {
|
|
384
597
|
const { id, ...updateParams } = params as { id: number } & Record<string, unknown>;
|
|
385
598
|
const data = await pipedriveRequest(`/activities/${id}`, {
|
|
386
|
-
method: "
|
|
599
|
+
method: "PATCH",
|
|
387
600
|
body: JSON.stringify(updateParams),
|
|
388
601
|
});
|
|
389
602
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
@@ -392,7 +605,7 @@ const plugin: ClawdbotPluginDefinition = {
|
|
|
392
605
|
|
|
393
606
|
api.registerTool({
|
|
394
607
|
name: "pipedrive_delete_activity",
|
|
395
|
-
description: "Delete an activity",
|
|
608
|
+
description: "Delete an activity (marks as deleted, 30-day retention)",
|
|
396
609
|
parameters: Type.Object({
|
|
397
610
|
id: Type.Number({ description: "Activity ID to delete" }),
|
|
398
611
|
}),
|
|
@@ -403,34 +616,74 @@ const plugin: ClawdbotPluginDefinition = {
|
|
|
403
616
|
},
|
|
404
617
|
});
|
|
405
618
|
|
|
406
|
-
// ============ PIPELINES
|
|
619
|
+
// ============ PIPELINES (v2) ============
|
|
407
620
|
|
|
408
621
|
api.registerTool({
|
|
409
622
|
name: "pipedrive_list_pipelines",
|
|
410
623
|
description: "List all pipelines",
|
|
411
|
-
parameters: Type.Object({
|
|
412
|
-
|
|
413
|
-
|
|
624
|
+
parameters: Type.Object({
|
|
625
|
+
limit: Type.Optional(Type.Number({ description: "Number of results" })),
|
|
626
|
+
cursor: Type.Optional(Type.String({ description: "Pagination cursor" })),
|
|
627
|
+
}),
|
|
628
|
+
async execute(_id, params) {
|
|
629
|
+
const query = new URLSearchParams();
|
|
630
|
+
for (const [key, value] of Object.entries(params)) {
|
|
631
|
+
if (value !== undefined) query.set(key, String(value));
|
|
632
|
+
}
|
|
633
|
+
const endpoint = query.toString() ? `/pipelines?${query}` : "/pipelines";
|
|
634
|
+
const data = await pipedriveRequest(endpoint);
|
|
414
635
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
415
636
|
},
|
|
416
637
|
});
|
|
417
638
|
|
|
639
|
+
api.registerTool({
|
|
640
|
+
name: "pipedrive_get_pipeline",
|
|
641
|
+
description: "Get details of a specific pipeline by ID",
|
|
642
|
+
parameters: Type.Object({
|
|
643
|
+
id: Type.Number({ description: "Pipeline ID" }),
|
|
644
|
+
}),
|
|
645
|
+
async execute(_id, params) {
|
|
646
|
+
const { id } = params as { id: number };
|
|
647
|
+
const data = await pipedriveRequest(`/pipelines/${id}`);
|
|
648
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
649
|
+
},
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
// ============ STAGES (v2) ============
|
|
653
|
+
|
|
418
654
|
api.registerTool({
|
|
419
655
|
name: "pipedrive_list_stages",
|
|
420
656
|
description: "List all stages, optionally filtered by pipeline",
|
|
421
657
|
parameters: Type.Object({
|
|
422
658
|
pipeline_id: Type.Optional(Type.Number({ description: "Filter by pipeline ID" })),
|
|
659
|
+
limit: Type.Optional(Type.Number({ description: "Number of results" })),
|
|
660
|
+
cursor: Type.Optional(Type.String({ description: "Pagination cursor" })),
|
|
423
661
|
}),
|
|
424
662
|
async execute(_id, params) {
|
|
425
|
-
const
|
|
426
|
-
|
|
427
|
-
|
|
663
|
+
const query = new URLSearchParams();
|
|
664
|
+
for (const [key, value] of Object.entries(params)) {
|
|
665
|
+
if (value !== undefined) query.set(key, String(value));
|
|
666
|
+
}
|
|
667
|
+
const endpoint = query.toString() ? `/stages?${query}` : "/stages";
|
|
428
668
|
const data = await pipedriveRequest(endpoint);
|
|
429
669
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
430
670
|
},
|
|
431
671
|
});
|
|
432
672
|
|
|
433
|
-
|
|
673
|
+
api.registerTool({
|
|
674
|
+
name: "pipedrive_get_stage",
|
|
675
|
+
description: "Get details of a specific stage by ID",
|
|
676
|
+
parameters: Type.Object({
|
|
677
|
+
id: Type.Number({ description: "Stage ID" }),
|
|
678
|
+
}),
|
|
679
|
+
async execute(_id, params) {
|
|
680
|
+
const { id } = params as { id: number };
|
|
681
|
+
const data = await pipedriveRequest(`/stages/${id}`);
|
|
682
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
683
|
+
},
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
// ============ NOTES (v1 - no v2 available yet) ============
|
|
434
687
|
|
|
435
688
|
api.registerTool({
|
|
436
689
|
name: "pipedrive_list_notes",
|
|
@@ -440,13 +693,27 @@ const plugin: ClawdbotPluginDefinition = {
|
|
|
440
693
|
person_id: Type.Optional(Type.Number({ description: "Filter by person ID" })),
|
|
441
694
|
org_id: Type.Optional(Type.Number({ description: "Filter by organization ID" })),
|
|
442
695
|
limit: Type.Optional(Type.Number({ description: "Number of results" })),
|
|
696
|
+
start: Type.Optional(Type.Number({ description: "Pagination offset" })),
|
|
443
697
|
}),
|
|
444
698
|
async execute(_id, params) {
|
|
445
699
|
const query = new URLSearchParams();
|
|
446
700
|
for (const [key, value] of Object.entries(params)) {
|
|
447
701
|
if (value !== undefined) query.set(key, String(value));
|
|
448
702
|
}
|
|
449
|
-
const data = await pipedriveRequest(`/notes?${query}
|
|
703
|
+
const data = await pipedriveRequest(`/notes?${query}`, { useV1: true });
|
|
704
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
705
|
+
},
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
api.registerTool({
|
|
709
|
+
name: "pipedrive_get_note",
|
|
710
|
+
description: "Get details of a specific note by ID",
|
|
711
|
+
parameters: Type.Object({
|
|
712
|
+
id: Type.Number({ description: "Note ID" }),
|
|
713
|
+
}),
|
|
714
|
+
async execute(_id, params) {
|
|
715
|
+
const { id } = params as { id: number };
|
|
716
|
+
const data = await pipedriveRequest(`/notes/${id}`, { useV1: true });
|
|
450
717
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
451
718
|
},
|
|
452
719
|
});
|
|
@@ -464,19 +731,51 @@ const plugin: ClawdbotPluginDefinition = {
|
|
|
464
731
|
const data = await pipedriveRequest("/notes", {
|
|
465
732
|
method: "POST",
|
|
466
733
|
body: JSON.stringify(params),
|
|
734
|
+
useV1: true,
|
|
735
|
+
});
|
|
736
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
737
|
+
},
|
|
738
|
+
});
|
|
739
|
+
|
|
740
|
+
api.registerTool({
|
|
741
|
+
name: "pipedrive_update_note",
|
|
742
|
+
description: "Update an existing note",
|
|
743
|
+
parameters: Type.Object({
|
|
744
|
+
id: Type.Number({ description: "Note ID to update (required)" }),
|
|
745
|
+
content: Type.String({ description: "New content" }),
|
|
746
|
+
}),
|
|
747
|
+
async execute(_id, params) {
|
|
748
|
+
const { id, ...updateParams } = params as { id: number } & Record<string, unknown>;
|
|
749
|
+
const data = await pipedriveRequest(`/notes/${id}`, {
|
|
750
|
+
method: "PUT", // v1 uses PUT
|
|
751
|
+
body: JSON.stringify(updateParams),
|
|
752
|
+
useV1: true,
|
|
467
753
|
});
|
|
468
754
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
469
755
|
},
|
|
470
756
|
});
|
|
471
757
|
|
|
472
|
-
|
|
758
|
+
api.registerTool({
|
|
759
|
+
name: "pipedrive_delete_note",
|
|
760
|
+
description: "Delete a note",
|
|
761
|
+
parameters: Type.Object({
|
|
762
|
+
id: Type.Number({ description: "Note ID to delete" }),
|
|
763
|
+
}),
|
|
764
|
+
async execute(_id, params) {
|
|
765
|
+
const { id } = params as { id: number };
|
|
766
|
+
const data = await pipedriveRequest(`/notes/${id}`, { method: "DELETE", useV1: true });
|
|
767
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
768
|
+
},
|
|
769
|
+
});
|
|
770
|
+
|
|
771
|
+
// ============ USERS (v1 - mostly no v2 available) ============
|
|
473
772
|
|
|
474
773
|
api.registerTool({
|
|
475
774
|
name: "pipedrive_list_users",
|
|
476
775
|
description: "List all users in the Pipedrive account",
|
|
477
776
|
parameters: Type.Object({}),
|
|
478
777
|
async execute() {
|
|
479
|
-
const data = await pipedriveRequest("/users");
|
|
778
|
+
const data = await pipedriveRequest("/users", { useV1: true });
|
|
480
779
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
481
780
|
},
|
|
482
781
|
});
|
|
@@ -486,12 +785,27 @@ const plugin: ClawdbotPluginDefinition = {
|
|
|
486
785
|
description: "Get the current authenticated user's details",
|
|
487
786
|
parameters: Type.Object({}),
|
|
488
787
|
async execute() {
|
|
489
|
-
const data = await pipedriveRequest("/users/me");
|
|
788
|
+
const data = await pipedriveRequest("/users/me", { useV1: true });
|
|
789
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
790
|
+
},
|
|
791
|
+
});
|
|
792
|
+
|
|
793
|
+
api.registerTool({
|
|
794
|
+
name: "pipedrive_get_user",
|
|
795
|
+
description: "Get details of a specific user by ID",
|
|
796
|
+
parameters: Type.Object({
|
|
797
|
+
id: Type.Number({ description: "User ID" }),
|
|
798
|
+
}),
|
|
799
|
+
async execute(_id, params) {
|
|
800
|
+
const { id } = params as { id: number };
|
|
801
|
+
const data = await pipedriveRequest(`/users/${id}`, { useV1: true });
|
|
490
802
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
491
803
|
},
|
|
492
804
|
});
|
|
493
805
|
|
|
494
|
-
|
|
806
|
+
const v2Tools = 22;
|
|
807
|
+
const v1Tools = 6;
|
|
808
|
+
console.log(`[pipedrive] Registered ${v2Tools + v1Tools} tools (${v2Tools} v2, ${v1Tools} v1) for ${cfg.domain}.pipedrive.com`);
|
|
495
809
|
},
|
|
496
810
|
};
|
|
497
811
|
|
package/install.sh
CHANGED
|
@@ -28,14 +28,19 @@ echo
|
|
|
28
28
|
echo "[2/3] Setting up skill template..."
|
|
29
29
|
mkdir -p "$SKILL_DIR"
|
|
30
30
|
|
|
31
|
+
LATEST_FILE="$SKILL_DIR/SKILL.md.latest"
|
|
32
|
+
|
|
31
33
|
if [ -f "$SKILL_FILE" ]; then
|
|
32
|
-
echo " $SKILL_FILE already exists
|
|
33
|
-
|
|
34
|
+
echo " $SKILL_FILE already exists (not overwriting your customizations)."
|
|
35
|
+
# Download latest template for comparison
|
|
36
|
+
curl -sL "https://raw.githubusercontent.com/graileanu/clawdbot-pipedrive/master/examples/SKILL-TEMPLATE.md" -o "$LATEST_FILE"
|
|
37
|
+
echo " Latest template saved to: $LATEST_FILE"
|
|
38
|
+
echo " Compare changes: diff $SKILL_FILE $LATEST_FILE"
|
|
34
39
|
else
|
|
35
40
|
# Download template from GitHub
|
|
36
41
|
curl -sL "https://raw.githubusercontent.com/graileanu/clawdbot-pipedrive/master/examples/SKILL-TEMPLATE.md" -o "$SKILL_FILE"
|
|
37
42
|
echo " Created $SKILL_FILE"
|
|
38
|
-
echo "
|
|
43
|
+
echo " Customize this file for your organization's workflows."
|
|
39
44
|
fi
|
|
40
45
|
echo
|
|
41
46
|
|