clawdbot-pipedrive 1.2.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.
Files changed (3) hide show
  1. package/README.md +9 -7
  2. package/index.ts +278 -67
  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, 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
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
@@ -80,15 +80,12 @@ function setupSkillTemplate(): void {
80
80
  mkdirSync(skillDir, { recursive: true });
81
81
 
82
82
  if (!existsSync(skillFile)) {
83
- // First install - create the skill file
84
83
  writeFileSync(skillFile, SKILL_TEMPLATE);
85
84
  console.log(`[pipedrive] Created skill template: ${skillFile}`);
86
85
  console.log("[pipedrive] Customize this file with your organization's workflows.");
87
86
  } else {
88
- // Skill exists - check if template has changed
89
87
  const existing = readFileSync(skillFile, "utf-8");
90
88
  if (existing !== SKILL_TEMPLATE) {
91
- // Save latest template for comparison
92
89
  writeFileSync(latestFile, SKILL_TEMPLATE);
93
90
  console.log(`[pipedrive] Skill file exists: ${skillFile} (not modified)`);
94
91
  console.log(`[pipedrive] New template available: ${latestFile}`);
@@ -130,8 +127,8 @@ type ClawdbotPluginDefinition = {
130
127
  const plugin: ClawdbotPluginDefinition = {
131
128
  id: "pipedrive",
132
129
  name: "Pipedrive CRM",
133
- description: "Interact with Pipedrive deals, persons, organizations, and activities",
134
- version: "1.0.0",
130
+ description: "Interact with Pipedrive deals, persons, organizations, and activities (API v2)",
131
+ version: "2.0.0",
135
132
 
136
133
  configSchema: {
137
134
  parse: (v) => v as PipedriveConfig,
@@ -150,7 +147,6 @@ const plugin: ClawdbotPluginDefinition = {
150
147
  },
151
148
 
152
149
  register(api) {
153
- // Set up skill template on first run
154
150
  setupSkillTemplate();
155
151
 
156
152
  const cfg = api.pluginConfig as PipedriveConfig;
@@ -160,16 +156,20 @@ const plugin: ClawdbotPluginDefinition = {
160
156
  return;
161
157
  }
162
158
 
163
- const baseUrl = `https://${cfg.domain}.pipedrive.com/api/v1`;
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
164
161
 
165
- async function pipedriveRequest(endpoint: string, options?: RequestInit) {
162
+ async function pipedriveRequest(endpoint: string, options?: RequestInit & { useV1?: boolean }) {
163
+ const baseUrl = options?.useV1 ? baseUrlV1 : baseUrlV2;
166
164
  const url = new URL(`${baseUrl}${endpoint}`);
167
165
  url.searchParams.set("api_token", cfg.apiKey!);
166
+
167
+ const { useV1, ...fetchOptions } = options || {};
168
168
  const res = await fetch(url.toString(), {
169
- ...options,
169
+ ...fetchOptions,
170
170
  headers: {
171
171
  "Content-Type": "application/json",
172
- ...options?.headers,
172
+ ...fetchOptions?.headers,
173
173
  },
174
174
  });
175
175
  if (!res.ok) {
@@ -179,7 +179,7 @@ const plugin: ClawdbotPluginDefinition = {
179
179
  return res.json();
180
180
  }
181
181
 
182
- // ============ DEALS ============
182
+ // ============ DEALS (v2) ============
183
183
 
184
184
  api.registerTool({
185
185
  name: "pipedrive_search_deals",
@@ -187,14 +187,16 @@ const plugin: ClawdbotPluginDefinition = {
187
187
  parameters: Type.Object({
188
188
  term: Type.String({ description: "Search term" }),
189
189
  status: Type.Optional(
190
- Type.String({ description: "Filter by status: open, won, lost, deleted, all_not_deleted" })
190
+ Type.String({ description: "Filter by status: open, won, lost, deleted" })
191
191
  ),
192
+ limit: Type.Optional(Type.Number({ description: "Number of results (default 100)" })),
192
193
  }),
193
194
  async execute(_id, params) {
194
- const { term, status } = params as { term: string; status?: string };
195
- let endpoint = `/deals/search?term=${encodeURIComponent(term)}`;
196
- if (status) endpoint += `&status=${status}`;
197
- const data = await pipedriveRequest(endpoint);
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}`);
198
200
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
199
201
  },
200
202
  });
@@ -216,26 +218,22 @@ const plugin: ClawdbotPluginDefinition = {
216
218
  name: "pipedrive_list_deals",
217
219
  description: "List deals with optional filters",
218
220
  parameters: Type.Object({
219
- status: Type.Optional(Type.String({ description: "Filter by status: open, won, lost, deleted, all_not_deleted" })),
221
+ status: Type.Optional(Type.String({ description: "Filter by status: open, won, lost, deleted" })),
220
222
  stage_id: Type.Optional(Type.Number({ description: "Filter by pipeline stage ID" })),
221
- user_id: Type.Optional(Type.Number({ description: "Filter by owner user ID" })),
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" })),
222
227
  limit: Type.Optional(Type.Number({ description: "Number of results (default 100, max 500)" })),
223
- start: Type.Optional(Type.Number({ description: "Pagination start (default 0)" })),
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" })),
224
231
  }),
225
232
  async execute(_id, params) {
226
- const { status, stage_id, user_id, limit, start } = params as {
227
- status?: string;
228
- stage_id?: number;
229
- user_id?: number;
230
- limit?: number;
231
- start?: number;
232
- };
233
233
  const query = new URLSearchParams();
234
- if (status) query.set("status", status);
235
- if (stage_id) query.set("stage_id", String(stage_id));
236
- if (user_id) query.set("user_id", String(user_id));
237
- if (limit) query.set("limit", String(limit));
238
- 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
+ }
239
237
  const data = await pipedriveRequest(`/deals?${query}`);
240
238
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
241
239
  },
@@ -251,7 +249,8 @@ const plugin: ClawdbotPluginDefinition = {
251
249
  person_id: Type.Optional(Type.Number({ description: "Associated person/contact ID" })),
252
250
  org_id: Type.Optional(Type.Number({ description: "Associated organization ID" })),
253
251
  stage_id: Type.Optional(Type.Number({ description: "Pipeline stage ID" })),
254
- user_id: Type.Optional(Type.Number({ description: "Owner user ID" })),
252
+ owner_id: Type.Optional(Type.Number({ description: "Owner user ID" })),
253
+ pipeline_id: Type.Optional(Type.Number({ description: "Pipeline ID" })),
255
254
  expected_close_date: Type.Optional(Type.String({ description: "Expected close date (YYYY-MM-DD)" })),
256
255
  }),
257
256
  async execute(_id, params) {
@@ -273,14 +272,15 @@ const plugin: ClawdbotPluginDefinition = {
273
272
  currency: Type.Optional(Type.String({ description: "Currency code" })),
274
273
  status: Type.Optional(Type.String({ description: "Status: open, won, lost, deleted" })),
275
274
  stage_id: Type.Optional(Type.Number({ description: "Move to stage ID" })),
276
- user_id: Type.Optional(Type.Number({ description: "New owner user ID" })),
275
+ owner_id: Type.Optional(Type.Number({ description: "New owner user ID" })),
276
+ pipeline_id: Type.Optional(Type.Number({ description: "Move to pipeline ID" })),
277
277
  expected_close_date: Type.Optional(Type.String({ description: "Expected close date (YYYY-MM-DD)" })),
278
278
  lost_reason: Type.Optional(Type.String({ description: "Reason for losing (when status=lost)" })),
279
279
  }),
280
280
  async execute(_id, params) {
281
281
  const { id, ...updateParams } = params as { id: number } & Record<string, unknown>;
282
282
  const data = await pipedriveRequest(`/deals/${id}`, {
283
- method: "PUT",
283
+ method: "PATCH", // v2 uses PATCH instead of PUT
284
284
  body: JSON.stringify(updateParams),
285
285
  });
286
286
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
@@ -289,7 +289,7 @@ const plugin: ClawdbotPluginDefinition = {
289
289
 
290
290
  api.registerTool({
291
291
  name: "pipedrive_delete_deal",
292
- description: "Delete a deal",
292
+ description: "Delete a deal (marks as deleted, 30-day retention)",
293
293
  parameters: Type.Object({
294
294
  id: Type.Number({ description: "Deal ID to delete" }),
295
295
  }),
@@ -300,17 +300,20 @@ const plugin: ClawdbotPluginDefinition = {
300
300
  },
301
301
  });
302
302
 
303
- // ============ PERSONS (CONTACTS) ============
303
+ // ============ PERSONS (v2) ============
304
304
 
305
305
  api.registerTool({
306
306
  name: "pipedrive_search_persons",
307
- description: "Search for persons/contacts in Pipedrive",
307
+ description: "Search for persons/contacts by name, email, phone, or notes",
308
308
  parameters: Type.Object({
309
309
  term: Type.String({ description: "Search term (name, email, phone)" }),
310
+ limit: Type.Optional(Type.Number({ description: "Number of results" })),
310
311
  }),
311
312
  async execute(_id, params) {
312
- const { term } = params as { term: string };
313
- const data = await pipedriveRequest(`/persons/search?term=${encodeURIComponent(term)}`);
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}`);
314
317
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
315
318
  },
316
319
  });
@@ -328,6 +331,27 @@ const plugin: ClawdbotPluginDefinition = {
328
331
  },
329
332
  });
330
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
+
331
355
  api.registerTool({
332
356
  name: "pipedrive_create_person",
333
357
  description: "Create a new person/contact",
@@ -336,11 +360,18 @@ const plugin: ClawdbotPluginDefinition = {
336
360
  email: Type.Optional(Type.String({ description: "Email address" })),
337
361
  phone: Type.Optional(Type.String({ description: "Phone number" })),
338
362
  org_id: Type.Optional(Type.Number({ description: "Associated organization ID" })),
363
+ owner_id: Type.Optional(Type.Number({ description: "Owner user ID" })),
339
364
  }),
340
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
+
341
372
  const data = await pipedriveRequest("/persons", {
342
373
  method: "POST",
343
- body: JSON.stringify(params),
374
+ body: JSON.stringify(body),
344
375
  });
345
376
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
346
377
  },
@@ -355,28 +386,49 @@ const plugin: ClawdbotPluginDefinition = {
355
386
  email: Type.Optional(Type.String({ description: "New email" })),
356
387
  phone: Type.Optional(Type.String({ description: "New phone" })),
357
388
  org_id: Type.Optional(Type.Number({ description: "New organization ID" })),
389
+ owner_id: Type.Optional(Type.Number({ description: "New owner user ID" })),
358
390
  }),
359
391
  async execute(_id, params) {
360
- const { id, ...updateParams } = params as { id: number } & Record<string, unknown>;
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
+
361
397
  const data = await pipedriveRequest(`/persons/${id}`, {
362
- method: "PUT",
363
- body: JSON.stringify(updateParams),
398
+ method: "PATCH",
399
+ body: JSON.stringify(body),
364
400
  });
365
401
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
366
402
  },
367
403
  });
368
404
 
369
- // ============ ORGANIZATIONS ============
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) ============
370
419
 
371
420
  api.registerTool({
372
421
  name: "pipedrive_search_organizations",
373
- description: "Search for organizations in Pipedrive",
422
+ description: "Search for organizations by name, address, or notes",
374
423
  parameters: Type.Object({
375
424
  term: Type.String({ description: "Search term (organization name)" }),
425
+ limit: Type.Optional(Type.Number({ description: "Number of results" })),
376
426
  }),
377
427
  async execute(_id, params) {
378
- const { term } = params as { term: string };
379
- const data = await pipedriveRequest(`/organizations/search?term=${encodeURIComponent(term)}`);
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}`);
380
432
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
381
433
  },
382
434
  });
@@ -394,12 +446,33 @@ const plugin: ClawdbotPluginDefinition = {
394
446
  },
395
447
  });
396
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
+
397
469
  api.registerTool({
398
470
  name: "pipedrive_create_organization",
399
471
  description: "Create a new organization",
400
472
  parameters: Type.Object({
401
473
  name: Type.String({ description: "Organization name (required)" }),
402
474
  address: Type.Optional(Type.String({ description: "Address" })),
475
+ owner_id: Type.Optional(Type.Number({ description: "Owner user ID" })),
403
476
  }),
404
477
  async execute(_id, params) {
405
478
  const data = await pipedriveRequest("/organizations", {
@@ -410,7 +483,39 @@ const plugin: ClawdbotPluginDefinition = {
410
483
  },
411
484
  });
412
485
 
413
- // ============ ACTIVITIES ============
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) ============
414
519
 
415
520
  api.registerTool({
416
521
  name: "pipedrive_list_activities",
@@ -419,10 +524,13 @@ const plugin: ClawdbotPluginDefinition = {
419
524
  deal_id: Type.Optional(Type.Number({ description: "Filter by deal ID" })),
420
525
  person_id: Type.Optional(Type.Number({ description: "Filter by person ID" })),
421
526
  org_id: Type.Optional(Type.Number({ description: "Filter by organization ID" })),
422
- done: Type.Optional(Type.Number({ description: "Filter by completion: 0 = not done, 1 = done" })),
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" })),
423
529
  type: Type.Optional(Type.String({ description: "Filter by type: call, meeting, task, deadline, email, lunch" })),
424
530
  limit: Type.Optional(Type.Number({ description: "Number of results (default 100)" })),
425
- start: Type.Optional(Type.Number({ description: "Pagination start" })),
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" })),
426
534
  }),
427
535
  async execute(_id, params) {
428
536
  const query = new URLSearchParams();
@@ -460,7 +568,8 @@ const plugin: ClawdbotPluginDefinition = {
460
568
  person_id: Type.Optional(Type.Number({ description: "Associated person ID" })),
461
569
  org_id: Type.Optional(Type.Number({ description: "Associated organization ID" })),
462
570
  note: Type.Optional(Type.String({ description: "Activity notes/description" })),
463
- done: Type.Optional(Type.Number({ description: "Mark as done: 0 = not done, 1 = done" })),
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" })),
464
573
  }),
465
574
  async execute(_id, params) {
466
575
  const data = await pipedriveRequest("/activities", {
@@ -480,13 +589,14 @@ const plugin: ClawdbotPluginDefinition = {
480
589
  type: Type.Optional(Type.String({ description: "New type" })),
481
590
  due_date: Type.Optional(Type.String({ description: "New due date (YYYY-MM-DD)" })),
482
591
  due_time: Type.Optional(Type.String({ description: "New due time (HH:MM)" })),
483
- done: Type.Optional(Type.Number({ description: "Mark as done: 0 = not done, 1 = done" })),
592
+ done: Type.Optional(Type.Boolean({ description: "Mark as done: true = done, false = not done" })),
484
593
  note: Type.Optional(Type.String({ description: "New notes" })),
594
+ owner_id: Type.Optional(Type.Number({ description: "New owner user ID" })),
485
595
  }),
486
596
  async execute(_id, params) {
487
597
  const { id, ...updateParams } = params as { id: number } & Record<string, unknown>;
488
598
  const data = await pipedriveRequest(`/activities/${id}`, {
489
- method: "PUT",
599
+ method: "PATCH",
490
600
  body: JSON.stringify(updateParams),
491
601
  });
492
602
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
@@ -495,7 +605,7 @@ const plugin: ClawdbotPluginDefinition = {
495
605
 
496
606
  api.registerTool({
497
607
  name: "pipedrive_delete_activity",
498
- description: "Delete an activity",
608
+ description: "Delete an activity (marks as deleted, 30-day retention)",
499
609
  parameters: Type.Object({
500
610
  id: Type.Number({ description: "Activity ID to delete" }),
501
611
  }),
@@ -506,34 +616,74 @@ const plugin: ClawdbotPluginDefinition = {
506
616
  },
507
617
  });
508
618
 
509
- // ============ PIPELINES & STAGES ============
619
+ // ============ PIPELINES (v2) ============
510
620
 
511
621
  api.registerTool({
512
622
  name: "pipedrive_list_pipelines",
513
623
  description: "List all pipelines",
514
- parameters: Type.Object({}),
515
- async execute() {
516
- const data = await pipedriveRequest("/pipelines");
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);
635
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
636
+ },
637
+ });
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}`);
517
648
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
518
649
  },
519
650
  });
520
651
 
652
+ // ============ STAGES (v2) ============
653
+
521
654
  api.registerTool({
522
655
  name: "pipedrive_list_stages",
523
656
  description: "List all stages, optionally filtered by pipeline",
524
657
  parameters: Type.Object({
525
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" })),
526
661
  }),
527
662
  async execute(_id, params) {
528
- const { pipeline_id } = params as { pipeline_id?: number };
529
- let endpoint = "/stages";
530
- if (pipeline_id) endpoint += `?pipeline_id=${pipeline_id}`;
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";
531
668
  const data = await pipedriveRequest(endpoint);
532
669
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
533
670
  },
534
671
  });
535
672
 
536
- // ============ NOTES ============
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) ============
537
687
 
538
688
  api.registerTool({
539
689
  name: "pipedrive_list_notes",
@@ -543,13 +693,27 @@ const plugin: ClawdbotPluginDefinition = {
543
693
  person_id: Type.Optional(Type.Number({ description: "Filter by person ID" })),
544
694
  org_id: Type.Optional(Type.Number({ description: "Filter by organization ID" })),
545
695
  limit: Type.Optional(Type.Number({ description: "Number of results" })),
696
+ start: Type.Optional(Type.Number({ description: "Pagination offset" })),
546
697
  }),
547
698
  async execute(_id, params) {
548
699
  const query = new URLSearchParams();
549
700
  for (const [key, value] of Object.entries(params)) {
550
701
  if (value !== undefined) query.set(key, String(value));
551
702
  }
552
- 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 });
553
717
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
554
718
  },
555
719
  });
@@ -567,19 +731,51 @@ const plugin: ClawdbotPluginDefinition = {
567
731
  const data = await pipedriveRequest("/notes", {
568
732
  method: "POST",
569
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,
570
753
  });
571
754
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
572
755
  },
573
756
  });
574
757
 
575
- // ============ USERS ============
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) ============
576
772
 
577
773
  api.registerTool({
578
774
  name: "pipedrive_list_users",
579
775
  description: "List all users in the Pipedrive account",
580
776
  parameters: Type.Object({}),
581
777
  async execute() {
582
- const data = await pipedriveRequest("/users");
778
+ const data = await pipedriveRequest("/users", { useV1: true });
583
779
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
584
780
  },
585
781
  });
@@ -589,12 +785,27 @@ const plugin: ClawdbotPluginDefinition = {
589
785
  description: "Get the current authenticated user's details",
590
786
  parameters: Type.Object({}),
591
787
  async execute() {
592
- 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 });
593
802
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
594
803
  },
595
804
  });
596
805
 
597
- console.log(`[pipedrive] Registered ${22} tools for ${cfg.domain}.pipedrive.com`);
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`);
598
809
  },
599
810
  };
600
811
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawdbot-pipedrive",
3
- "version": "1.2.0",
3
+ "version": "2.0.0",
4
4
  "type": "module",
5
5
  "description": "Pipedrive CRM integration for Clawdbot",
6
6
  "author": "graileanu",