@hasna/contacts 0.3.2 → 0.4.1

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/dist/index.js CHANGED
@@ -1,4 +1,16 @@
1
1
  // @bun
2
+ var __defProp = Object.defineProperty;
3
+ var __export = (target, all) => {
4
+ for (var name in all)
5
+ __defProp(target, name, {
6
+ get: all[name],
7
+ enumerable: true,
8
+ configurable: true,
9
+ set: (newValue) => all[name] = () => newValue
10
+ });
11
+ };
12
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
13
+
2
14
  // src/db/database.ts
3
15
  import { Database } from "bun:sqlite";
4
16
  import { existsSync, mkdirSync } from "fs";
@@ -16,8 +28,50 @@ function ensureDir(filePath) {
16
28
  if (!existsSync(dir))
17
29
  mkdirSync(dir, { recursive: true });
18
30
  }
19
- var MIGRATIONS = [
20
- `
31
+ function getDatabase(path) {
32
+ if (_db)
33
+ return _db;
34
+ const dbPath = path || getDbPath();
35
+ ensureDir(dbPath);
36
+ const db = new Database(dbPath, { create: true });
37
+ db.exec("PRAGMA journal_mode=WAL");
38
+ db.exec("PRAGMA foreign_keys=ON");
39
+ runMigrations(db);
40
+ _db = db;
41
+ return db;
42
+ }
43
+ function resetDatabase() {
44
+ _db = null;
45
+ }
46
+ function uuid() {
47
+ return crypto.randomUUID();
48
+ }
49
+ function now() {
50
+ return new Date().toISOString();
51
+ }
52
+ function runMigrations(db) {
53
+ try {
54
+ const row = db.query("SELECT MAX(version) as v FROM _migrations").get();
55
+ const current = row?.v ?? -1;
56
+ for (let i = current + 1;i < MIGRATIONS.length; i++) {
57
+ db.exec(MIGRATIONS[i]);
58
+ db.exec(`INSERT OR REPLACE INTO _migrations(version) VALUES(${i})`);
59
+ }
60
+ } catch {
61
+ for (const m of MIGRATIONS) {
62
+ try {
63
+ db.exec(m);
64
+ } catch {}
65
+ }
66
+ try {
67
+ db.exec(`INSERT OR REPLACE INTO _migrations(version) VALUES(${MIGRATIONS.length - 1})`);
68
+ } catch {}
69
+ }
70
+ }
71
+ var MIGRATIONS, _db = null;
72
+ var init_database = __esm(() => {
73
+ MIGRATIONS = [
74
+ `
21
75
  CREATE TABLE IF NOT EXISTS companies (
22
76
  id TEXT PRIMARY KEY,
23
77
  name TEXT NOT NULL,
@@ -170,7 +224,7 @@ var MIGRATIONS = [
170
224
 
171
225
  CREATE TABLE IF NOT EXISTS _migrations (version INTEGER PRIMARY KEY);
172
226
  `,
173
- `
227
+ `
174
228
  ALTER TABLE contacts ADD COLUMN last_contacted_at TEXT;
175
229
  ALTER TABLE contacts ADD COLUMN website TEXT;
176
230
  ALTER TABLE contacts ADD COLUMN preferred_contact_method TEXT;
@@ -189,7 +243,7 @@ var MIGRATIONS = [
189
243
  PRIMARY KEY (contact_id, group_id)
190
244
  );
191
245
  `,
192
- `
246
+ `
193
247
  ALTER TABLE contacts ADD COLUMN status TEXT DEFAULT 'active';
194
248
  ALTER TABLE contacts ADD COLUMN follow_up_at TEXT;
195
249
  ALTER TABLE contacts ADD COLUMN archived INTEGER NOT NULL DEFAULT 0;
@@ -203,7 +257,7 @@ var MIGRATIONS = [
203
257
  PRIMARY KEY (company_id, group_id)
204
258
  );
205
259
  `,
206
- `
260
+ `
207
261
  CREATE TABLE IF NOT EXISTS company_relationships (
208
262
  id TEXT PRIMARY KEY,
209
263
  contact_id TEXT NOT NULL REFERENCES contacts(id) ON DELETE CASCADE,
@@ -216,7 +270,7 @@ var MIGRATIONS = [
216
270
  CREATE INDEX IF NOT EXISTS idx_company_relationships_contact ON company_relationships(contact_id);
217
271
  CREATE INDEX IF NOT EXISTS idx_company_relationships_company ON company_relationships(company_id);
218
272
  `,
219
- `
273
+ `
220
274
  CREATE TABLE IF NOT EXISTS contact_notes (
221
275
  id TEXT PRIMARY KEY,
222
276
  contact_id TEXT NOT NULL REFERENCES contacts(id) ON DELETE CASCADE,
@@ -227,7 +281,7 @@ var MIGRATIONS = [
227
281
 
228
282
  CREATE INDEX IF NOT EXISTS idx_contact_notes_contact ON contact_notes(contact_id);
229
283
  `,
230
- `
284
+ `
231
285
  ALTER TABLE companies ADD COLUMN is_owned_entity INTEGER NOT NULL DEFAULT 0;
232
286
  ALTER TABLE companies ADD COLUMN entity_type TEXT CHECK(entity_type IN ('operating','holding','dissolved','nonprofit','trust','branch','other'));
233
287
 
@@ -305,77 +359,71 @@ var MIGRATIONS = [
305
359
  );
306
360
 
307
361
  ALTER TABLE contact_notes ADD COLUMN company_id TEXT REFERENCES companies(id) ON DELETE SET NULL;
308
- `
309
- ];
310
- var _db = null;
311
- function getDatabase(path) {
312
- if (_db)
313
- return _db;
314
- const dbPath = path || getDbPath();
315
- ensureDir(dbPath);
316
- const db = new Database(dbPath, { create: true });
317
- db.exec("PRAGMA journal_mode=WAL");
318
- db.exec("PRAGMA foreign_keys=ON");
319
- runMigrations(db);
320
- _db = db;
321
- return db;
322
- }
323
- function resetDatabase() {
324
- _db = null;
325
- }
326
- function uuid() {
327
- return crypto.randomUUID();
328
- }
329
- function now() {
330
- return new Date().toISOString();
331
- }
332
- function runMigrations(db) {
333
- try {
334
- const row = db.query("SELECT MAX(version) as v FROM _migrations").get();
335
- const current = row?.v ?? -1;
336
- for (let i = current + 1;i < MIGRATIONS.length; i++) {
337
- db.exec(MIGRATIONS[i]);
338
- db.exec(`INSERT OR REPLACE INTO _migrations(version) VALUES(${i})`);
339
- }
340
- } catch {
341
- for (const m of MIGRATIONS) {
342
- try {
343
- db.exec(m);
344
- } catch {}
345
- }
346
- try {
347
- db.exec(`INSERT OR REPLACE INTO _migrations(version) VALUES(${MIGRATIONS.length - 1})`);
348
- } catch {}
349
- }
350
- }
351
- // src/types/index.ts
352
- class ContactNotFoundError extends Error {
353
- constructor(id) {
354
- super(`Contact not found: ${id}`);
355
- this.name = "ContactNotFoundError";
356
- }
357
- }
362
+ `,
363
+ `
364
+ ALTER TABLE contacts ADD COLUMN do_not_contact INTEGER NOT NULL DEFAULT 0;
365
+ ALTER TABLE contacts ADD COLUMN priority INTEGER NOT NULL DEFAULT 3 CHECK(priority BETWEEN 1 AND 5);
366
+ ALTER TABLE contacts ADD COLUMN timezone TEXT;
358
367
 
359
- class CompanyNotFoundError extends Error {
360
- constructor(id) {
361
- super(`Company not found: ${id}`);
362
- this.name = "CompanyNotFoundError";
363
- }
364
- }
368
+ CREATE TABLE IF NOT EXISTS deals (
369
+ id TEXT PRIMARY KEY,
370
+ title TEXT NOT NULL,
371
+ contact_id TEXT REFERENCES contacts(id) ON DELETE SET NULL,
372
+ company_id TEXT REFERENCES companies(id) ON DELETE SET NULL,
373
+ stage TEXT NOT NULL DEFAULT 'lead' CHECK(stage IN ('lead','qualified','proposal','negotiation','won','lost','cancelled')),
374
+ value_usd REAL,
375
+ currency TEXT NOT NULL DEFAULT 'USD',
376
+ close_date TEXT,
377
+ notes TEXT,
378
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
379
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
380
+ );
365
381
 
366
- class TagNotFoundError extends Error {
367
- constructor(id) {
368
- super(`Tag not found: ${id}`);
369
- this.name = "TagNotFoundError";
370
- }
371
- }
382
+ CREATE TABLE IF NOT EXISTS events (
383
+ id TEXT PRIMARY KEY,
384
+ title TEXT NOT NULL,
385
+ type TEXT NOT NULL DEFAULT 'meeting' CHECK(type IN ('meeting','call','lunch','email','demo','conference','intro','other')),
386
+ event_date TEXT NOT NULL,
387
+ duration_min INTEGER,
388
+ contact_ids TEXT NOT NULL DEFAULT '[]',
389
+ company_id TEXT REFERENCES companies(id) ON DELETE SET NULL,
390
+ notes TEXT,
391
+ outcome TEXT,
392
+ deal_id TEXT REFERENCES deals(id) ON DELETE SET NULL,
393
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
394
+ );
395
+ `
396
+ ];
397
+ });
372
398
 
373
- class DuplicateTagNameError extends Error {
374
- constructor(name) {
375
- super(`Tag with name already exists: ${name}`);
376
- this.name = "DuplicateTagNameError";
377
- }
378
- }
399
+ // src/types/index.ts
400
+ var ContactNotFoundError, CompanyNotFoundError, TagNotFoundError, DuplicateTagNameError;
401
+ var init_types = __esm(() => {
402
+ ContactNotFoundError = class ContactNotFoundError extends Error {
403
+ constructor(id) {
404
+ super(`Contact not found: ${id}`);
405
+ this.name = "ContactNotFoundError";
406
+ }
407
+ };
408
+ CompanyNotFoundError = class CompanyNotFoundError extends Error {
409
+ constructor(id) {
410
+ super(`Company not found: ${id}`);
411
+ this.name = "CompanyNotFoundError";
412
+ }
413
+ };
414
+ TagNotFoundError = class TagNotFoundError extends Error {
415
+ constructor(id) {
416
+ super(`Tag not found: ${id}`);
417
+ this.name = "TagNotFoundError";
418
+ }
419
+ };
420
+ DuplicateTagNameError = class DuplicateTagNameError extends Error {
421
+ constructor(name) {
422
+ super(`Tag with name already exists: ${name}`);
423
+ this.name = "DuplicateTagNameError";
424
+ }
425
+ };
426
+ });
379
427
 
380
428
  // src/db/activity.ts
381
429
  function rowToActivity(row) {
@@ -409,8 +457,29 @@ function getActivity(id, db) {
409
457
  const row = d.query(`SELECT * FROM activity_log WHERE id = ?`).get(id);
410
458
  return row ? rowToActivity(row) : null;
411
459
  }
460
+ var init_activity = __esm(() => {
461
+ init_database();
462
+ });
412
463
 
413
464
  // src/db/contacts.ts
465
+ var exports_contacts = {};
466
+ __export(exports_contacts, {
467
+ updateContact: () => updateContact,
468
+ unarchiveContact: () => unarchiveContact,
469
+ searchContacts: () => searchContacts,
470
+ mergeContacts: () => mergeContacts,
471
+ listRecentContacts: () => listRecentContacts,
472
+ listContacts: () => listContacts,
473
+ listColdContacts: () => listColdContacts,
474
+ getContactByEmail: () => getContactByEmail,
475
+ getContact: () => getContact,
476
+ deleteContact: () => deleteContact,
477
+ createContact: () => createContact,
478
+ autoLinkContactToCompany: () => autoLinkContactToCompany,
479
+ archiveContact: () => archiveContact,
480
+ addPhoneToContact: () => addPhoneToContact,
481
+ addEmailToContact: () => addEmailToContact
482
+ });
414
483
  function rowToContact(row) {
415
484
  return {
416
485
  ...row,
@@ -420,7 +489,10 @@ function rowToContact(row) {
420
489
  status: row.status ?? "active",
421
490
  follow_up_at: row.follow_up_at ?? null,
422
491
  archived: !!row.archived,
423
- project_id: row.project_id ?? null
492
+ project_id: row.project_id ?? null,
493
+ do_not_contact: !!row.do_not_contact,
494
+ priority: row.priority ?? 3,
495
+ timezone: row.timezone ?? null
424
496
  };
425
497
  }
426
498
  function rowToEmail(row) {
@@ -505,8 +577,8 @@ function createContact(input, db) {
505
577
  const firstName = input.first_name ?? "";
506
578
  const lastName = input.last_name ?? "";
507
579
  const displayName = input.display_name ?? (firstName || lastName ? `${firstName} ${lastName}`.trim() : "Unnamed Contact");
508
- d.run(`INSERT INTO contacts (id, first_name, last_name, display_name, nickname, avatar_url, notes, birthday, company_id, job_title, source, custom_fields, last_contacted_at, website, preferred_contact_method, status, follow_up_at, project_id, created_at, updated_at)
509
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
580
+ d.run(`INSERT INTO contacts (id, first_name, last_name, display_name, nickname, avatar_url, notes, birthday, company_id, job_title, source, custom_fields, last_contacted_at, website, preferred_contact_method, status, follow_up_at, project_id, do_not_contact, priority, timezone, created_at, updated_at)
581
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
510
582
  id,
511
583
  firstName,
512
584
  lastName,
@@ -525,6 +597,9 @@ function createContact(input, db) {
525
597
  input.status ?? "active",
526
598
  input.follow_up_at ?? null,
527
599
  input.project_id ?? null,
600
+ input.do_not_contact ? 1 : 0,
601
+ input.priority ?? 3,
602
+ input.timezone ?? null,
528
603
  timestamp,
529
604
  timestamp
530
605
  ]);
@@ -568,12 +643,18 @@ function listContacts(opts = {}, db) {
568
643
  last_contacted_after,
569
644
  last_contacted_before,
570
645
  order_by = "display_name",
571
- order_dir = "asc"
646
+ order_dir = "asc",
647
+ include_dnc = false,
648
+ priority_min,
649
+ updated_since
572
650
  } = opts;
573
651
  const conditions = [];
574
652
  const params = [];
575
653
  conditions.push("c.archived = ?");
576
654
  params.push(archived ? 1 : 0);
655
+ if (!include_dnc) {
656
+ conditions.push("c.do_not_contact = 0");
657
+ }
577
658
  if (company_id) {
578
659
  conditions.push("c.company_id = ?");
579
660
  params.push(company_id);
@@ -611,6 +692,14 @@ function listContacts(opts = {}, db) {
611
692
  conditions.push("c.last_contacted_at <= ?");
612
693
  params.push(last_contacted_before);
613
694
  }
695
+ if (priority_min !== undefined) {
696
+ conditions.push("c.priority >= ?");
697
+ params.push(priority_min);
698
+ }
699
+ if (updated_since) {
700
+ conditions.push("c.updated_at >= ?");
701
+ params.push(updated_since);
702
+ }
614
703
  const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
615
704
  const validOrderBy = ["display_name", "created_at", "updated_at", "last_contacted_at", "follow_up_at"].includes(order_by) ? order_by : "display_name";
616
705
  const validOrderDir = order_dir === "desc" ? "DESC" : "ASC";
@@ -694,6 +783,18 @@ function updateContact(id, input, db) {
694
783
  setClauses.push("project_id = ?");
695
784
  params.push(input.project_id);
696
785
  }
786
+ if (input.do_not_contact !== undefined) {
787
+ setClauses.push("do_not_contact = ?");
788
+ params.push(input.do_not_contact ? 1 : 0);
789
+ }
790
+ if (input.priority !== undefined) {
791
+ setClauses.push("priority = ?");
792
+ params.push(input.priority ?? 3);
793
+ }
794
+ if (input.timezone !== undefined) {
795
+ setClauses.push("timezone = ?");
796
+ params.push(input.timezone);
797
+ }
697
798
  params.push(id);
698
799
  d.run(`UPDATE contacts SET ${setClauses.join(", ")} WHERE id = ?`, params);
699
800
  if (input.emails_add?.length) {
@@ -892,6 +993,15 @@ function unarchiveContact(id, db) {
892
993
  const updated = d.query(`SELECT * FROM contacts WHERE id = ?`).get(id);
893
994
  return loadContactDetails(d, rowToContact(updated));
894
995
  }
996
+ function listColdContacts(days, db) {
997
+ const d = db || getDatabase();
998
+ const rows = d.query(`SELECT c.* FROM contacts c
999
+ WHERE c.archived = 0 AND c.do_not_contact = 0
1000
+ AND (c.last_contacted_at IS NULL OR c.last_contacted_at < datetime('now', ? || ' days'))
1001
+ ORDER BY c.last_contacted_at ASC NULLS FIRST
1002
+ LIMIT 100`).all(`-${days}`);
1003
+ return rows.map((row) => loadContactDetails(d, rowToContact(row)));
1004
+ }
895
1005
  function autoLinkContactToCompany(contactId, db) {
896
1006
  const d = db || getDatabase();
897
1007
  const row = d.query(`SELECT * FROM contacts WHERE id = ?`).get(contactId);
@@ -911,7 +1021,20 @@ function autoLinkContactToCompany(contactId, db) {
911
1021
  const updated = d.query(`SELECT * FROM contacts WHERE id = ?`).get(contactId);
912
1022
  return loadContactDetails(d, rowToContact(updated));
913
1023
  }
1024
+ var init_contacts = __esm(() => {
1025
+ init_types();
1026
+ init_database();
1027
+ init_activity();
1028
+ });
1029
+
1030
+ // src/index.ts
1031
+ init_database();
1032
+ init_contacts();
1033
+
914
1034
  // src/db/companies.ts
1035
+ init_types();
1036
+ init_database();
1037
+ init_activity();
915
1038
  function rowToCompany2(row) {
916
1039
  return {
917
1040
  ...row,
@@ -1157,7 +1280,10 @@ function listCompanyEmployees(companyId, db) {
1157
1280
  status: r.status ?? "active",
1158
1281
  follow_up_at: r.follow_up_at ?? null,
1159
1282
  archived: !!r.archived,
1160
- project_id: r.project_id ?? null
1283
+ project_id: r.project_id ?? null,
1284
+ do_not_contact: !!r.do_not_contact,
1285
+ priority: r.priority ?? 3,
1286
+ timezone: r.timezone ?? null
1161
1287
  }));
1162
1288
  }
1163
1289
  function archiveCompany(id, db) {
@@ -1184,6 +1310,8 @@ function listOwnedEntities(db) {
1184
1310
  return listCompanies({ is_owned_entity: true }, db);
1185
1311
  }
1186
1312
  // src/db/tags.ts
1313
+ init_types();
1314
+ init_database();
1187
1315
  function rowToTag2(row) {
1188
1316
  return { ...row };
1189
1317
  }
@@ -1282,7 +1410,10 @@ function listContactsByTag(tagId, db) {
1282
1410
  status: r.status ?? "active",
1283
1411
  follow_up_at: r.follow_up_at ?? null,
1284
1412
  archived: !!r.archived,
1285
- project_id: r.project_id ?? null
1413
+ project_id: r.project_id ?? null,
1414
+ do_not_contact: !!r.do_not_contact,
1415
+ priority: r.priority ?? 3,
1416
+ timezone: r.timezone ?? null
1286
1417
  }));
1287
1418
  }
1288
1419
  function addTagToCompany(companyId, tagId, db) {
@@ -1300,6 +1431,8 @@ function removeTagFromCompany(companyId, tagId, db) {
1300
1431
  d.run(`DELETE FROM company_tags WHERE company_id = ? AND tag_id = ?`, [companyId, tagId]);
1301
1432
  }
1302
1433
  // src/db/relationships.ts
1434
+ init_types();
1435
+ init_database();
1303
1436
  function rowToRelationship(row) {
1304
1437
  return {
1305
1438
  ...row,
@@ -1416,6 +1549,7 @@ function getEntityTeam(companyId, db) {
1416
1549
  }));
1417
1550
  }
1418
1551
  // src/db/org-members.ts
1552
+ init_database();
1419
1553
  function rowToOrgMember(row) {
1420
1554
  return {
1421
1555
  id: row.id,
@@ -1497,6 +1631,7 @@ function listOrgMembersForContact(contactId, db) {
1497
1631
  return rows.map(rowToOrgMember);
1498
1632
  }
1499
1633
  // src/db/vendor-comms.ts
1634
+ init_database();
1500
1635
  function rowToVendorComm(row) {
1501
1636
  return {
1502
1637
  id: row.id,
@@ -1643,6 +1778,7 @@ function markFollowUpDone(id, db) {
1643
1778
  return rowToVendorComm(d.query(`SELECT * FROM vendor_communications WHERE id = ?`).get(id));
1644
1779
  }
1645
1780
  // src/db/contact-tasks.ts
1781
+ init_database();
1646
1782
  function rowToContactTask(row) {
1647
1783
  return {
1648
1784
  id: row.id,
@@ -1786,6 +1922,7 @@ function checkEscalations(db) {
1786
1922
  return results;
1787
1923
  }
1788
1924
  // src/db/applications.ts
1925
+ init_database();
1789
1926
  function rowToApplication(row) {
1790
1927
  return {
1791
1928
  id: row.id,
@@ -1947,7 +2084,209 @@ function listPendingApplications(db) {
1947
2084
  ORDER BY created_at DESC`).all();
1948
2085
  return rows.map(rowToApplication);
1949
2086
  }
2087
+ // src/db/deals.ts
2088
+ init_database();
2089
+ function rowToDeal(row) {
2090
+ return {
2091
+ id: row.id,
2092
+ title: row.title,
2093
+ contact_id: row.contact_id,
2094
+ company_id: row.company_id,
2095
+ stage: row.stage,
2096
+ value_usd: row.value_usd,
2097
+ currency: row.currency,
2098
+ close_date: row.close_date,
2099
+ notes: row.notes,
2100
+ created_at: row.created_at,
2101
+ updated_at: row.updated_at
2102
+ };
2103
+ }
2104
+ function createDeal(input, db) {
2105
+ const d = db || getDatabase();
2106
+ const id = uuid();
2107
+ const timestamp = now();
2108
+ d.run(`INSERT INTO deals (id, title, contact_id, company_id, stage, value_usd, currency, close_date, notes, created_at, updated_at)
2109
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
2110
+ id,
2111
+ input.title,
2112
+ input.contact_id ?? null,
2113
+ input.company_id ?? null,
2114
+ input.stage ?? "lead",
2115
+ input.value_usd ?? null,
2116
+ input.currency ?? "USD",
2117
+ input.close_date ?? null,
2118
+ input.notes ?? null,
2119
+ timestamp,
2120
+ timestamp
2121
+ ]);
2122
+ return rowToDeal(d.query(`SELECT * FROM deals WHERE id = ?`).get(id));
2123
+ }
2124
+ function getDeal(id, db) {
2125
+ const d = db || getDatabase();
2126
+ const row = d.query(`SELECT * FROM deals WHERE id = ?`).get(id);
2127
+ return row ? rowToDeal(row) : null;
2128
+ }
2129
+ function listDeals(opts = {}, db) {
2130
+ const d = db || getDatabase();
2131
+ const conditions = [];
2132
+ const params = [];
2133
+ if (opts.stage) {
2134
+ conditions.push("stage = ?");
2135
+ params.push(opts.stage);
2136
+ }
2137
+ if (opts.contact_id) {
2138
+ conditions.push("contact_id = ?");
2139
+ params.push(opts.contact_id);
2140
+ }
2141
+ if (opts.company_id) {
2142
+ conditions.push("company_id = ?");
2143
+ params.push(opts.company_id);
2144
+ }
2145
+ const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
2146
+ const rows = d.query(`SELECT * FROM deals ${where} ORDER BY created_at DESC`).all(...params);
2147
+ return rows.map(rowToDeal);
2148
+ }
2149
+ function updateDeal(id, input, db) {
2150
+ const d = db || getDatabase();
2151
+ const existing = d.query(`SELECT * FROM deals WHERE id = ?`).get(id);
2152
+ if (!existing)
2153
+ return null;
2154
+ const setClauses = ["updated_at = ?"];
2155
+ const params = [now()];
2156
+ if (input.title !== undefined) {
2157
+ setClauses.push("title = ?");
2158
+ params.push(input.title);
2159
+ }
2160
+ if (input.contact_id !== undefined) {
2161
+ setClauses.push("contact_id = ?");
2162
+ params.push(input.contact_id ?? null);
2163
+ }
2164
+ if (input.company_id !== undefined) {
2165
+ setClauses.push("company_id = ?");
2166
+ params.push(input.company_id ?? null);
2167
+ }
2168
+ if (input.stage !== undefined) {
2169
+ setClauses.push("stage = ?");
2170
+ params.push(input.stage);
2171
+ }
2172
+ if (input.value_usd !== undefined) {
2173
+ setClauses.push("value_usd = ?");
2174
+ params.push(input.value_usd ?? null);
2175
+ }
2176
+ if (input.currency !== undefined) {
2177
+ setClauses.push("currency = ?");
2178
+ params.push(input.currency);
2179
+ }
2180
+ if (input.close_date !== undefined) {
2181
+ setClauses.push("close_date = ?");
2182
+ params.push(input.close_date ?? null);
2183
+ }
2184
+ if (input.notes !== undefined) {
2185
+ setClauses.push("notes = ?");
2186
+ params.push(input.notes ?? null);
2187
+ }
2188
+ params.push(id);
2189
+ d.run(`UPDATE deals SET ${setClauses.join(", ")} WHERE id = ?`, params);
2190
+ return rowToDeal(d.query(`SELECT * FROM deals WHERE id = ?`).get(id));
2191
+ }
2192
+ function deleteDeal(id, db) {
2193
+ const d = db || getDatabase();
2194
+ d.run(`DELETE FROM deals WHERE id = ?`, [id]);
2195
+ }
2196
+ function getDealsByStage(db) {
2197
+ const d = db || getDatabase();
2198
+ const rows = d.query(`SELECT * FROM deals ORDER BY stage, created_at DESC`).all();
2199
+ const result = {};
2200
+ for (const row of rows) {
2201
+ if (!result[row.stage])
2202
+ result[row.stage] = [];
2203
+ result[row.stage].push(rowToDeal(row));
2204
+ }
2205
+ return result;
2206
+ }
2207
+ // src/db/events.ts
2208
+ init_database();
2209
+ function rowToEvent(row) {
2210
+ let contact_ids = [];
2211
+ try {
2212
+ contact_ids = JSON.parse(row.contact_ids);
2213
+ } catch {
2214
+ contact_ids = [];
2215
+ }
2216
+ return {
2217
+ id: row.id,
2218
+ title: row.title,
2219
+ type: row.type,
2220
+ event_date: row.event_date,
2221
+ duration_min: row.duration_min,
2222
+ contact_ids,
2223
+ company_id: row.company_id,
2224
+ notes: row.notes,
2225
+ outcome: row.outcome,
2226
+ deal_id: row.deal_id,
2227
+ created_at: row.created_at
2228
+ };
2229
+ }
2230
+ function logEvent(input, db) {
2231
+ const d = db || getDatabase();
2232
+ const id = uuid();
2233
+ const timestamp = now();
2234
+ d.run(`INSERT INTO events (id, title, type, event_date, duration_min, contact_ids, company_id, notes, outcome, deal_id, created_at)
2235
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
2236
+ id,
2237
+ input.title,
2238
+ input.type ?? "meeting",
2239
+ input.event_date,
2240
+ input.duration_min ?? null,
2241
+ JSON.stringify(input.contact_ids ?? []),
2242
+ input.company_id ?? null,
2243
+ input.notes ?? null,
2244
+ input.outcome ?? null,
2245
+ input.deal_id ?? null,
2246
+ timestamp
2247
+ ]);
2248
+ return rowToEvent(d.query(`SELECT * FROM events WHERE id = ?`).get(id));
2249
+ }
2250
+ function getEvent(id, db) {
2251
+ const d = db || getDatabase();
2252
+ const row = d.query(`SELECT * FROM events WHERE id = ?`).get(id);
2253
+ return row ? rowToEvent(row) : null;
2254
+ }
2255
+ function listEvents(opts = {}, db) {
2256
+ const d = db || getDatabase();
2257
+ const conditions = [];
2258
+ const params = [];
2259
+ if (opts.contact_id) {
2260
+ conditions.push("contact_ids LIKE ?");
2261
+ params.push(`%${opts.contact_id}%`);
2262
+ }
2263
+ if (opts.company_id) {
2264
+ conditions.push("company_id = ?");
2265
+ params.push(opts.company_id);
2266
+ }
2267
+ if (opts.type) {
2268
+ conditions.push("type = ?");
2269
+ params.push(opts.type);
2270
+ }
2271
+ if (opts.date_from) {
2272
+ conditions.push("event_date >= ?");
2273
+ params.push(opts.date_from);
2274
+ }
2275
+ if (opts.date_to) {
2276
+ conditions.push("event_date <= ?");
2277
+ params.push(opts.date_to);
2278
+ }
2279
+ const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
2280
+ const rows = d.query(`SELECT * FROM events ${where} ORDER BY event_date DESC`).all(...params);
2281
+ return rows.map(rowToEvent);
2282
+ }
2283
+ function deleteEvent(id, db) {
2284
+ const d = db || getDatabase();
2285
+ d.run(`DELETE FROM events WHERE id = ?`, [id]);
2286
+ }
1950
2287
  // src/db/notes.ts
2288
+ init_types();
2289
+ init_database();
1951
2290
  function addNote(contactId, body, createdBy, db, companyId) {
1952
2291
  const d = db || getDatabase();
1953
2292
  const contact = d.query(`SELECT id FROM contacts WHERE id = ?`).get(contactId);
@@ -1974,6 +2313,7 @@ function getNote(noteId, db) {
1974
2313
  return d.query(`SELECT * FROM contact_notes WHERE id = ?`).get(noteId);
1975
2314
  }
1976
2315
  // src/db/groups.ts
2316
+ init_database();
1977
2317
  function createGroup(db, input) {
1978
2318
  const id = uuid();
1979
2319
  db.query(`INSERT INTO groups(id, name, description, created_at, updated_at) VALUES(?,?,?,?,?)`).run(id, input.name, input.description ?? null, now(), now());
@@ -2042,6 +2382,250 @@ function listCompaniesInGroup(db, groupId) {
2042
2382
  function listGroupsForCompany(db, companyId) {
2043
2383
  return db.query(`SELECT g.* FROM groups g JOIN company_groups cog ON g.id = cog.group_id WHERE cog.company_id = ? ORDER BY g.name`).all(companyId);
2044
2384
  }
2385
+
2386
+ // src/index.ts
2387
+ init_activity();
2388
+
2389
+ // src/lib/upcoming.ts
2390
+ init_database();
2391
+ function getUpcomingItems(days = 7, db) {
2392
+ const _db2 = db || getDatabase();
2393
+ const items = [];
2394
+ const now3 = new Date;
2395
+ const future = new Date(now3.getTime() + days * 86400000);
2396
+ const todayStr = now3.toISOString().slice(0, 10);
2397
+ const futureStr = future.toISOString().slice(0, 10);
2398
+ const followUps = _db2.query(`SELECT c.id, c.display_name, c.follow_up_at FROM contacts c WHERE c.follow_up_at IS NOT NULL AND c.follow_up_at <= ? AND c.do_not_contact = 0`).all(futureStr);
2399
+ for (const r of followUps) {
2400
+ items.push({ date: r.follow_up_at, type: "follow_up", contact_id: r.id, contact_name: r.display_name, title: `Follow up with ${r.display_name}`, urgency: r.follow_up_at < todayStr ? "overdue" : r.follow_up_at === todayStr ? "today" : "upcoming" });
2401
+ }
2402
+ const tasks = _db2.query(`SELECT ct.*, c.display_name FROM contact_tasks ct JOIN contacts c ON ct.contact_id = c.id WHERE ct.deadline IS NOT NULL AND ct.deadline <= ? AND ct.status NOT IN ('completed','cancelled')`).all(futureStr);
2403
+ for (const t of tasks) {
2404
+ items.push({ date: t.deadline, type: "task_deadline", contact_id: t.contact_id, contact_name: t.display_name, title: t.title, urgency: t.deadline < todayStr ? "overdue" : t.deadline === todayStr ? "today" : "upcoming" });
2405
+ }
2406
+ const apps = _db2.query(`SELECT a.*, c.display_name as contact_name FROM applications a LEFT JOIN contacts c ON a.primary_contact_id = c.id WHERE a.follow_up_date IS NOT NULL AND a.follow_up_date <= ?`).all(futureStr);
2407
+ for (const a of apps) {
2408
+ items.push({ date: a.follow_up_date, type: "application_followup", contact_name: a.contact_name ?? undefined, title: `Follow up: ${a.program_name}`, urgency: a.follow_up_date < todayStr ? "overdue" : a.follow_up_date === todayStr ? "today" : "upcoming" });
2409
+ }
2410
+ const vendorFU = _db2.query(`SELECT vc.*, co.name as company_name FROM vendor_communications vc JOIN companies co ON vc.company_id = co.id WHERE vc.follow_up_date IS NOT NULL AND vc.follow_up_date <= ? AND vc.follow_up_done = 0`).all(futureStr);
2411
+ for (const v of vendorFU) {
2412
+ items.push({ date: v.follow_up_date, type: "vendor_followup", company_id: v.company_id, company_name: v.company_name, title: `Follow up with ${v.company_name}: ${v.subject || v.type}`, urgency: v.follow_up_date < todayStr ? "overdue" : v.follow_up_date === todayStr ? "today" : "upcoming" });
2413
+ }
2414
+ const contacts = _db2.query(`SELECT id, display_name, birthday FROM contacts WHERE birthday IS NOT NULL AND do_not_contact = 0`).all();
2415
+ for (const c of contacts) {
2416
+ const bday = new Date(c.birthday);
2417
+ const thisYear = new Date(now3.getFullYear(), bday.getMonth(), bday.getDate());
2418
+ const nextBday = thisYear >= now3 ? thisYear : new Date(now3.getFullYear() + 1, bday.getMonth(), bday.getDate());
2419
+ const nextStr = nextBday.toISOString().slice(0, 10);
2420
+ if (nextStr <= futureStr) {
2421
+ items.push({ date: nextStr, type: "birthday", contact_id: c.id, contact_name: c.display_name, title: `Birthday: ${c.display_name}`, urgency: nextStr === todayStr ? "today" : "upcoming" });
2422
+ }
2423
+ }
2424
+ return items.sort((a, b) => a.date.localeCompare(b.date));
2425
+ }
2426
+ // src/lib/stats.ts
2427
+ init_database();
2428
+ function getNetworkStats(db) {
2429
+ const _db2 = db || getDatabase();
2430
+ const q = (sql) => _db2.query(sql).get();
2431
+ const today = new Date().toISOString().slice(0, 10);
2432
+ const d30 = new Date(Date.now() - 30 * 86400000).toISOString().slice(0, 10);
2433
+ const d60 = new Date(Date.now() - 60 * 86400000).toISOString().slice(0, 10);
2434
+ return {
2435
+ total_contacts: q(`SELECT COUNT(*) c FROM contacts WHERE archived=0`).c,
2436
+ total_companies: q(`SELECT COUNT(*) c FROM companies WHERE archived=0`).c,
2437
+ owned_entities: q(`SELECT COUNT(*) c FROM companies WHERE is_owned_entity=1`).c,
2438
+ total_tags: q(`SELECT COUNT(*) c FROM tags`).c,
2439
+ total_groups: q(`SELECT COUNT(*) c FROM groups`).c,
2440
+ total_deals: q(`SELECT COUNT(*) c FROM deals WHERE stage NOT IN ('won','lost','cancelled')`).c,
2441
+ total_events: q(`SELECT COUNT(*) c FROM events`).c,
2442
+ cold_30d: q(`SELECT COUNT(*) c FROM contacts WHERE archived=0 AND do_not_contact=0 AND (last_contacted_at IS NULL OR last_contacted_at < '${d30}')`).c,
2443
+ cold_60d: q(`SELECT COUNT(*) c FROM contacts WHERE archived=0 AND do_not_contact=0 AND (last_contacted_at IS NULL OR last_contacted_at < '${d60}')`).c,
2444
+ cold_never: q(`SELECT COUNT(*) c FROM contacts WHERE archived=0 AND do_not_contact=0 AND last_contacted_at IS NULL`).c,
2445
+ contacts_with_email: q(`SELECT COUNT(DISTINCT contact_id) c FROM emails WHERE contact_id IS NOT NULL`).c,
2446
+ contacts_with_phone: q(`SELECT COUNT(DISTINCT contact_id) c FROM phones WHERE contact_id IS NOT NULL`).c,
2447
+ contacts_no_company: q(`SELECT COUNT(*) c FROM contacts WHERE archived=0 AND company_id IS NULL`).c,
2448
+ overdue_tasks: q(`SELECT COUNT(*) c FROM contact_tasks WHERE deadline < '${today}' AND status NOT IN ('completed','cancelled')`).c,
2449
+ pending_applications: q(`SELECT COUNT(*) c FROM applications WHERE status IN ('submitted','pending','follow_up_needed')`).c,
2450
+ missing_invoices: q(`SELECT COUNT(*) c FROM vendor_communications WHERE type='invoice_request' AND status IN ('awaiting_response','no_response')`).c,
2451
+ upcoming_7d: q(`SELECT COUNT(*) c FROM contacts WHERE follow_up_at BETWEEN '${today}' AND date('${today}','+7 days')`).c,
2452
+ notes_count: q(`SELECT COUNT(*) c FROM contact_notes`).c,
2453
+ active_deals_value: q(`SELECT COALESCE(SUM(value_usd),0) c FROM deals WHERE stage NOT IN ('won','lost','cancelled') AND currency='USD'`).c
2454
+ };
2455
+ }
2456
+ // src/lib/audit.ts
2457
+ init_database();
2458
+ function auditContact(contact) {
2459
+ const missing = [];
2460
+ const suggestions = [];
2461
+ let score = 0;
2462
+ if (contact.emails?.length)
2463
+ score += 20;
2464
+ else {
2465
+ missing.push("email");
2466
+ suggestions.push("Add an email address");
2467
+ }
2468
+ if (contact.phones?.length)
2469
+ score += 15;
2470
+ else {
2471
+ missing.push("phone");
2472
+ suggestions.push("Add a phone number");
2473
+ }
2474
+ if (contact.company_id)
2475
+ score += 15;
2476
+ else {
2477
+ missing.push("company");
2478
+ suggestions.push("Link to a company");
2479
+ }
2480
+ if (contact.last_contacted_at)
2481
+ score += 20;
2482
+ else {
2483
+ missing.push("last_contacted_at");
2484
+ suggestions.push("Log a contact interaction");
2485
+ }
2486
+ if (contact.tags?.length)
2487
+ score += 10;
2488
+ else {
2489
+ missing.push("tags");
2490
+ suggestions.push("Add at least one tag");
2491
+ }
2492
+ if (contact.notes)
2493
+ score += 10;
2494
+ else {
2495
+ missing.push("notes");
2496
+ suggestions.push("Add notes");
2497
+ }
2498
+ if (contact.job_title)
2499
+ score += 10;
2500
+ else {
2501
+ missing.push("job_title");
2502
+ suggestions.push("Add a job title");
2503
+ }
2504
+ return { contact_id: contact.id, display_name: contact.display_name, score, missing, suggestions };
2505
+ }
2506
+ async function listContactAudit(db) {
2507
+ const _db2 = db || getDatabase();
2508
+ const { listContacts: listContacts2 } = await Promise.resolve().then(() => (init_contacts(), exports_contacts));
2509
+ const { contacts } = listContacts2({ limit: 500, include_dnc: true }, _db2);
2510
+ return contacts.map(auditContact).sort((a, b) => a.score - b.score);
2511
+ }
2512
+ // src/lib/timeline.ts
2513
+ init_database();
2514
+ function getContactTimeline(contactId, limit = 50, db) {
2515
+ const _db2 = db || getDatabase();
2516
+ const items = [];
2517
+ const notes = _db2.query(`SELECT * FROM contact_notes WHERE contact_id = ? ORDER BY created_at DESC LIMIT 50`).all(contactId);
2518
+ for (const n of notes) {
2519
+ items.push({ date: n.created_at, type: "note", title: "Note", body: n.body });
2520
+ }
2521
+ const events = _db2.query(`SELECT * FROM events WHERE contact_ids LIKE ? ORDER BY event_date DESC LIMIT 50`).all(`%${contactId}%`);
2522
+ for (const e of events) {
2523
+ items.push({ date: e.event_date, type: "event", title: `${e.type}: ${e.title}`, body: e.notes ?? undefined, metadata: { outcome: e.outcome, duration_min: e.duration_min } });
2524
+ }
2525
+ const tasks = _db2.query(`SELECT * FROM contact_tasks WHERE contact_id = ? ORDER BY created_at DESC LIMIT 30`).all(contactId);
2526
+ for (const t of tasks) {
2527
+ items.push({ date: t.created_at, type: "task_created", title: `Task created: ${t.title}`, metadata: { deadline: t.deadline, priority: t.priority } });
2528
+ if (t.status === "completed") {
2529
+ items.push({ date: t.updated_at, type: "task_completed", title: `Task completed: ${t.title}` });
2530
+ }
2531
+ }
2532
+ const comms = _db2.query(`SELECT vc.*, co.name as company_name FROM vendor_communications vc JOIN companies co ON vc.company_id = co.id WHERE vc.contact_id = ? ORDER BY vc.comm_date DESC LIMIT 20`).all(contactId);
2533
+ for (const c of comms) {
2534
+ items.push({ date: c.comm_date, type: "vendor_comm", title: `${c.type} \u2014 ${c.company_name}`, body: c.subject ?? undefined });
2535
+ }
2536
+ const activity = _db2.query(`SELECT * FROM activity_log WHERE contact_id = ? ORDER BY created_at DESC LIMIT 30`).all(contactId);
2537
+ for (const a of activity) {
2538
+ items.push({ date: a.created_at, type: "interaction", title: a.action, body: a.details ?? undefined });
2539
+ }
2540
+ return items.sort((a, b) => b.date.localeCompare(a.date)).slice(0, limit);
2541
+ }
2542
+ // src/lib/brief.ts
2543
+ init_database();
2544
+ init_contacts();
2545
+ function generateBrief(contactId, db) {
2546
+ const _db2 = db || getDatabase();
2547
+ const contact = getContact(contactId, _db2);
2548
+ const notes = listNotes(contactId, _db2);
2549
+ const allTasks = listContactTasks({ contact_id: contactId }, _db2);
2550
+ const tasks = allTasks.filter((t) => !["completed", "cancelled"].includes(t.status));
2551
+ const overdueTasks = allTasks.filter((t) => t.deadline && t.deadline < new Date().toISOString() && !["completed", "cancelled"].includes(t.status));
2552
+ const companyRels = listCompanyRelationships({ contact_id: contactId }, _db2);
2553
+ const recentTimeline = getContactTimeline(contactId, 5, _db2);
2554
+ const daysSince = contact.last_contacted_at ? Math.floor((Date.now() - new Date(contact.last_contacted_at).getTime()) / 86400000) : null;
2555
+ const lines = [];
2556
+ lines.push(`# ${contact.display_name}`);
2557
+ if (contact.job_title)
2558
+ lines.push(`**Role:** ${contact.job_title}${contact.company_id ? ` (linked to company)` : ""}`);
2559
+ if (contact.emails?.length) {
2560
+ const primary = contact.emails.find((e) => e.is_primary) || contact.emails[0];
2561
+ if (primary)
2562
+ lines.push(`**Email:** ${primary.address}`);
2563
+ }
2564
+ if (contact.phones?.length) {
2565
+ const primary = contact.phones.find((p) => p.is_primary) || contact.phones[0];
2566
+ if (primary)
2567
+ lines.push(`**Phone:** ${primary.number}`);
2568
+ }
2569
+ if (contact.preferred_contact_method)
2570
+ lines.push(`**Preferred contact:** ${contact.preferred_contact_method}`);
2571
+ lines.push("");
2572
+ lines.push(`## Status`);
2573
+ lines.push(`- Last contacted: ${daysSince !== null ? `${daysSince} days ago` : "never"}`);
2574
+ lines.push(`- Status: ${contact.status || "active"}`);
2575
+ if (contact.follow_up_at)
2576
+ lines.push(`- Follow-up scheduled: ${contact.follow_up_at}`);
2577
+ if (overdueTasks.length)
2578
+ lines.push(`- OVERDUE TASKS: ${overdueTasks.length}`);
2579
+ if (companyRels.length) {
2580
+ lines.push("");
2581
+ lines.push(`## Entity Relationships`);
2582
+ for (const r of companyRels)
2583
+ lines.push(`- ${r.relationship_type} \u2014 ${r.notes || ""}`);
2584
+ }
2585
+ if (tasks.length) {
2586
+ lines.push("");
2587
+ lines.push(`## Open Tasks`);
2588
+ for (const t of tasks)
2589
+ lines.push(`- [${t.priority}] ${t.title}${t.deadline ? ` (due ${t.deadline})` : ""}`);
2590
+ }
2591
+ if (notes.length) {
2592
+ lines.push("");
2593
+ lines.push(`## Recent Notes`);
2594
+ for (const n of notes.slice(0, 3))
2595
+ lines.push(`**${n.created_at.slice(0, 10)}:** ${n.body}`);
2596
+ }
2597
+ if (recentTimeline.length) {
2598
+ lines.push("");
2599
+ lines.push(`## Recent Activity`);
2600
+ for (const item of recentTimeline)
2601
+ lines.push(`- ${item.date.slice(0, 10)} ${item.title}`);
2602
+ }
2603
+ if (contact.notes) {
2604
+ lines.push("");
2605
+ lines.push(`## Background Notes`);
2606
+ lines.push(contact.notes);
2607
+ }
2608
+ return lines.join(`
2609
+ `);
2610
+ }
2611
+ // src/lib/apple-contacts.ts
2612
+ async function exportFromApple() {
2613
+ if (process.platform !== "darwin")
2614
+ throw new Error("Apple Contacts sync is only available on macOS");
2615
+ const script = `tell application "Contacts" to return vcard of every person`;
2616
+ const proc = Bun.spawn(["osascript", "-e", script], { stdout: "pipe", stderr: "pipe" });
2617
+ const output = await new Response(proc.stdout).text();
2618
+ await proc.exited;
2619
+ return output;
2620
+ }
2621
+ async function importToApple(vcfData) {
2622
+ if (process.platform !== "darwin")
2623
+ throw new Error("Apple Contacts sync is only available on macOS");
2624
+ const tmpFile = `/tmp/contacts-import-${Date.now()}.vcf`;
2625
+ await Bun.write(tmpFile, vcfData);
2626
+ const proc = Bun.spawn(["open", tmpFile], { stdout: "pipe", stderr: "pipe" });
2627
+ await proc.exited;
2628
+ }
2045
2629
  // src/lib/connector.ts
2046
2630
  import { join as join2 } from "path";
2047
2631
  import { existsSync as existsSync2, readFileSync } from "fs";
@@ -2133,6 +2717,505 @@ function readConnectorTokens(name, profile = "default") {
2133
2717
  throw new ConnectorAuthError(name, "tokens file unreadable");
2134
2718
  }
2135
2719
  }
2720
+ // src/lib/import.ts
2721
+ function parseCsv(text) {
2722
+ const lines = text.split(/\r?\n/);
2723
+ if (lines.length < 2)
2724
+ return [];
2725
+ const headers = parseCsvLine(lines[0]);
2726
+ const rows = [];
2727
+ for (let i = 1;i < lines.length; i++) {
2728
+ const line = lines[i].trim();
2729
+ if (!line)
2730
+ continue;
2731
+ const values = parseCsvLine(line);
2732
+ const row = {};
2733
+ headers.forEach((h, idx) => {
2734
+ row[h.trim()] = values[idx]?.trim() ?? "";
2735
+ });
2736
+ rows.push(row);
2737
+ }
2738
+ return rows;
2739
+ }
2740
+ function parseCsvLine(line) {
2741
+ const fields = [];
2742
+ let current = "";
2743
+ let inQuotes = false;
2744
+ for (let i = 0;i < line.length; i++) {
2745
+ const ch = line[i];
2746
+ if (ch === '"') {
2747
+ if (inQuotes && line[i + 1] === '"') {
2748
+ current += '"';
2749
+ i++;
2750
+ } else {
2751
+ inQuotes = !inQuotes;
2752
+ }
2753
+ } else if (ch === "," && !inQuotes) {
2754
+ fields.push(current);
2755
+ current = "";
2756
+ } else {
2757
+ current += ch;
2758
+ }
2759
+ }
2760
+ fields.push(current);
2761
+ return fields;
2762
+ }
2763
+ function csvRowToContact(row) {
2764
+ const firstName = row["First Name"] ?? row["first_name"] ?? row["Given Name"] ?? "";
2765
+ const lastName = row["Last Name"] ?? row["last_name"] ?? row["Family Name"] ?? "";
2766
+ const displayName = row["Name"] ?? row["display_name"] ?? row["Full Name"] ?? [firstName, lastName].filter(Boolean).join(" ") ?? "";
2767
+ if (!displayName && !firstName && !lastName)
2768
+ return null;
2769
+ const contact = {
2770
+ display_name: displayName || [firstName, lastName].filter(Boolean).join(" ") || "Unnamed",
2771
+ first_name: firstName || undefined,
2772
+ last_name: lastName || undefined,
2773
+ job_title: row["Job Title"] ?? row["job_title"] ?? row["Title"] ?? undefined,
2774
+ notes: row["Notes"] ?? row["notes"] ?? undefined,
2775
+ birthday: row["Birthday"] ?? row["birthday"] ?? undefined,
2776
+ source: "import"
2777
+ };
2778
+ const emails = [];
2779
+ for (let i = 1;i <= 5; i++) {
2780
+ const val = row[`Email ${i} - Value`] ?? row[`Email Address ${i}`] ?? (i === 1 ? row["Email"] ?? row["email"] ?? row["Email Address"] : undefined);
2781
+ const rawType = row[`Email ${i} - Type`] ?? (i === 1 ? "work" : "other");
2782
+ if (val) {
2783
+ const type = rawType?.toLowerCase() === "personal" ? "personal" : rawType?.toLowerCase() === "other" ? "other" : "work";
2784
+ emails.push({ address: val, type, is_primary: i === 1 });
2785
+ }
2786
+ }
2787
+ if (emails.length)
2788
+ contact.emails = emails;
2789
+ const phones = [];
2790
+ for (let i = 1;i <= 5; i++) {
2791
+ const val = row[`Phone ${i} - Value`] ?? row[`Phone ${i}`] ?? (i === 1 ? row["Phone"] ?? row["phone"] ?? row["Mobile"] : undefined);
2792
+ const rawType = row[`Phone ${i} - Type`] ?? (i === 1 ? "mobile" : "other");
2793
+ if (val) {
2794
+ const type = rawType?.toLowerCase().includes("mobile") || rawType?.toLowerCase().includes("cell") ? "mobile" : rawType?.toLowerCase().includes("work") ? "work" : rawType?.toLowerCase().includes("home") ? "home" : rawType?.toLowerCase().includes("fax") ? "fax" : "other";
2795
+ phones.push({ number: val, type, is_primary: i === 1 });
2796
+ }
2797
+ }
2798
+ if (phones.length)
2799
+ contact.phones = phones;
2800
+ return contact;
2801
+ }
2802
+ function importFromCsv(data) {
2803
+ const rows = parseCsv(data);
2804
+ return rows.map(csvRowToContact).filter(Boolean);
2805
+ }
2806
+ function parseVcf(data) {
2807
+ const contacts = [];
2808
+ const blocks = data.split(/BEGIN:VCARD/i).filter((b) => b.trim());
2809
+ for (const block of blocks) {
2810
+ try {
2811
+ const contact = parseVcfBlock(`BEGIN:VCARD
2812
+ ` + block);
2813
+ if (contact)
2814
+ contacts.push(contact);
2815
+ } catch {}
2816
+ }
2817
+ return contacts;
2818
+ }
2819
+ function parseVcfBlock(block) {
2820
+ const unfolded = block.replace(/\r?\n[ \t]/g, "");
2821
+ const lines = unfolded.split(/\r?\n/).filter((l) => l.trim());
2822
+ const contact = { source: "import" };
2823
+ const emails = [];
2824
+ const phones = [];
2825
+ const addresses = [];
2826
+ const socials = [];
2827
+ for (const line of lines) {
2828
+ if (/^BEGIN:VCARD$/i.test(line) || /^END:VCARD$/i.test(line) || /^VERSION:/i.test(line))
2829
+ continue;
2830
+ const colonIdx = line.indexOf(":");
2831
+ if (colonIdx === -1)
2832
+ continue;
2833
+ const propPart = line.slice(0, colonIdx);
2834
+ const value = line.slice(colonIdx + 1).trim();
2835
+ const semicolonIdx = propPart.indexOf(";");
2836
+ const propName = (semicolonIdx === -1 ? propPart : propPart.slice(0, semicolonIdx)).toUpperCase();
2837
+ const params = semicolonIdx !== -1 ? propPart.slice(semicolonIdx + 1) : "";
2838
+ switch (propName) {
2839
+ case "FN":
2840
+ contact.display_name = decodeVcfValue(value);
2841
+ break;
2842
+ case "N": {
2843
+ const parts = value.split(";");
2844
+ contact.last_name = decodeVcfValue(parts[0] ?? "") || undefined;
2845
+ contact.first_name = decodeVcfValue(parts[1] ?? "") || undefined;
2846
+ break;
2847
+ }
2848
+ case "NICKNAME":
2849
+ contact.nickname = decodeVcfValue(value) || undefined;
2850
+ break;
2851
+ case "TITLE":
2852
+ contact.job_title = decodeVcfValue(value) || undefined;
2853
+ break;
2854
+ case "NOTE":
2855
+ contact.notes = decodeVcfValue(value) || undefined;
2856
+ break;
2857
+ case "BDAY":
2858
+ contact.birthday = value.replace(/^(\d{4})(\d{2})(\d{2})$/, "$1-$2-$3");
2859
+ break;
2860
+ case "EMAIL": {
2861
+ const typeMatch = params.match(/TYPE=([^;]+)/i);
2862
+ const rawLabel = typeMatch ? typeMatch[1].toLowerCase() : "work";
2863
+ const type = rawLabel.includes("personal") ? "personal" : rawLabel.includes("other") ? "other" : "work";
2864
+ const isPrimary = params.includes("PREF") || emails.length === 0;
2865
+ emails.push({ address: decodeVcfValue(value), type, is_primary: isPrimary });
2866
+ break;
2867
+ }
2868
+ case "TEL": {
2869
+ const typeMatch = params.match(/TYPE=([^;]+)/i);
2870
+ const rawLabel = typeMatch ? typeMatch[1].toLowerCase() : "mobile";
2871
+ const type = rawLabel.includes("cell") || rawLabel.includes("mobile") ? "mobile" : rawLabel.includes("work") ? "work" : rawLabel.includes("home") ? "home" : rawLabel.includes("fax") ? "fax" : "other";
2872
+ const isPrimary = params.includes("PREF") || phones.length === 0;
2873
+ phones.push({ number: decodeVcfValue(value), type, is_primary: isPrimary });
2874
+ break;
2875
+ }
2876
+ case "ADR": {
2877
+ const parts = value.split(";");
2878
+ const typeMatch = params.match(/TYPE=([^;]+)/i);
2879
+ const rawLabel = typeMatch ? typeMatch[1].toLowerCase().split(",")[0] : "physical";
2880
+ const type = rawLabel.includes("home") || rawLabel.includes("physical") ? "physical" : rawLabel.includes("mail") ? "mailing" : rawLabel.includes("bill") ? "billing" : "other";
2881
+ addresses.push({
2882
+ type,
2883
+ street: decodeVcfValue(parts[2] ?? "") || undefined,
2884
+ city: decodeVcfValue(parts[3] ?? "") || undefined,
2885
+ state: decodeVcfValue(parts[4] ?? "") || undefined,
2886
+ zip: decodeVcfValue(parts[5] ?? "") || undefined,
2887
+ country: decodeVcfValue(parts[6] ?? "") || undefined,
2888
+ is_primary: addresses.length === 0
2889
+ });
2890
+ break;
2891
+ }
2892
+ case "URL": {
2893
+ const url = decodeVcfValue(value);
2894
+ const platform = detectPlatform(url);
2895
+ socials.push({ platform, url, handle: url });
2896
+ break;
2897
+ }
2898
+ case "X-SOCIALPROFILE": {
2899
+ const typeMatch = params.match(/TYPE=([^;]+)/i);
2900
+ const platform = normalizePlatform(typeMatch?.[1] ?? "other");
2901
+ socials.push({ platform, handle: decodeVcfValue(value), url: value });
2902
+ break;
2903
+ }
2904
+ }
2905
+ }
2906
+ if (!contact.display_name) {
2907
+ if (contact.first_name || contact.last_name) {
2908
+ contact.display_name = [contact.first_name, contact.last_name].filter(Boolean).join(" ");
2909
+ } else {
2910
+ return null;
2911
+ }
2912
+ }
2913
+ if (emails.length)
2914
+ contact.emails = emails;
2915
+ if (phones.length)
2916
+ contact.phones = phones;
2917
+ if (addresses.length)
2918
+ contact.addresses = addresses;
2919
+ if (socials.length)
2920
+ contact.social_profiles = socials;
2921
+ return contact;
2922
+ }
2923
+ function decodeVcfValue(val) {
2924
+ return val.replace(/\\n/g, `
2925
+ `).replace(/\\,/g, ",").replace(/\\;/g, ";").replace(/\\\\/g, "\\");
2926
+ }
2927
+ function detectPlatform(url) {
2928
+ const lower = url.toLowerCase();
2929
+ if (lower.includes("twitter.com") || lower.includes("x.com"))
2930
+ return "twitter";
2931
+ if (lower.includes("linkedin.com"))
2932
+ return "linkedin";
2933
+ if (lower.includes("github.com"))
2934
+ return "github";
2935
+ if (lower.includes("instagram.com"))
2936
+ return "instagram";
2937
+ if (lower.includes("facebook.com"))
2938
+ return "facebook";
2939
+ if (lower.includes("youtube.com"))
2940
+ return "youtube";
2941
+ if (lower.includes("telegram"))
2942
+ return "telegram";
2943
+ if (lower.includes("discord"))
2944
+ return "discord";
2945
+ if (lower.includes("tiktok"))
2946
+ return "tiktok";
2947
+ if (lower.includes("bluesky") || lower.includes("bsky"))
2948
+ return "bluesky";
2949
+ return "other";
2950
+ }
2951
+ function normalizePlatform(raw) {
2952
+ const lower = raw.toLowerCase();
2953
+ const platforms = [
2954
+ "twitter",
2955
+ "linkedin",
2956
+ "github",
2957
+ "instagram",
2958
+ "telegram",
2959
+ "discord",
2960
+ "youtube",
2961
+ "tiktok",
2962
+ "bluesky",
2963
+ "facebook",
2964
+ "whatsapp",
2965
+ "snapchat",
2966
+ "reddit"
2967
+ ];
2968
+ for (const p of platforms) {
2969
+ if (lower.includes(p))
2970
+ return p;
2971
+ }
2972
+ return "other";
2973
+ }
2974
+ function importFromJson(data) {
2975
+ let parsed;
2976
+ try {
2977
+ parsed = JSON.parse(data);
2978
+ } catch {
2979
+ throw new Error("Invalid JSON");
2980
+ }
2981
+ if (!Array.isArray(parsed)) {
2982
+ if (typeof parsed === "object" && parsed !== null) {
2983
+ parsed = [parsed];
2984
+ } else {
2985
+ throw new Error("JSON must be an array of contacts");
2986
+ }
2987
+ }
2988
+ return parsed.map((obj) => {
2989
+ const displayName = obj.display_name ?? obj.name ?? [obj.first_name ?? "", obj.last_name ?? ""].filter(Boolean).join(" ") ?? "Unnamed";
2990
+ return {
2991
+ ...obj,
2992
+ display_name: displayName,
2993
+ source: "import"
2994
+ };
2995
+ });
2996
+ }
2997
+ function parseLinkedInCsvLine(line) {
2998
+ return parseCsvLine(line);
2999
+ }
3000
+ function parseLinkedIn(csv) {
3001
+ const lines = csv.split(`
3002
+ `).filter((l) => l.trim());
3003
+ if (!lines.length)
3004
+ return [];
3005
+ const headers = parseLinkedInCsvLine(lines[0]).map((h) => h.replace(/"/g, "").trim());
3006
+ const firstNameIdx = headers.findIndex((h) => h === "First Name");
3007
+ const lastNameIdx = headers.findIndex((h) => h === "Last Name");
3008
+ const emailIdx = headers.findIndex((h) => h === "Email Address");
3009
+ const companyIdx = headers.findIndex((h) => h === "Company");
3010
+ const positionIdx = headers.findIndex((h) => h === "Position");
3011
+ const urlIdx = headers.findIndex((h) => h === "URL");
3012
+ const connectedIdx = headers.findIndex((h) => h === "Connected On");
3013
+ const results = [];
3014
+ for (let i = 1;i < lines.length; i++) {
3015
+ const cols = parseLinkedInCsvLine(lines[i]);
3016
+ const firstName = firstNameIdx >= 0 ? (cols[firstNameIdx] ?? "").trim() : "";
3017
+ const lastName = lastNameIdx >= 0 ? (cols[lastNameIdx] ?? "").trim() : "";
3018
+ if (!firstName && !lastName)
3019
+ continue;
3020
+ const contact = {
3021
+ first_name: firstName,
3022
+ last_name: lastName,
3023
+ display_name: `${firstName} ${lastName}`.trim(),
3024
+ source: "import"
3025
+ };
3026
+ if (emailIdx >= 0 && cols[emailIdx]?.trim()) {
3027
+ contact.emails = [{ address: cols[emailIdx].trim(), type: "work", is_primary: true }];
3028
+ }
3029
+ if (positionIdx >= 0 && cols[positionIdx]?.trim()) {
3030
+ contact.job_title = cols[positionIdx].trim();
3031
+ }
3032
+ if (urlIdx >= 0 && cols[urlIdx]?.trim()) {
3033
+ contact.social_profiles = [{ platform: "linkedin", url: cols[urlIdx].trim(), is_primary: true }];
3034
+ }
3035
+ if (companyIdx >= 0 && cols[companyIdx]?.trim()) {
3036
+ const connectedNote = connectedIdx >= 0 && cols[connectedIdx]?.trim() ? ` Connected on LinkedIn: ${cols[connectedIdx].trim()}` : "";
3037
+ contact.notes = `Company: ${cols[companyIdx].trim()}${connectedNote}`;
3038
+ } else if (connectedIdx >= 0 && cols[connectedIdx]?.trim()) {
3039
+ contact.notes = `Connected on LinkedIn: ${cols[connectedIdx].trim()}`;
3040
+ }
3041
+ results.push(contact);
3042
+ }
3043
+ return results;
3044
+ }
3045
+ function isLinkedInFormat(data) {
3046
+ const firstLine = data.split(`
3047
+ `)[0] ?? "";
3048
+ const lower = firstLine.toLowerCase();
3049
+ return lower.includes("first name") && lower.includes("url") && lower.includes("connected on");
3050
+ }
3051
+ async function importContacts(format, data) {
3052
+ switch (format) {
3053
+ case "csv":
3054
+ if (isLinkedInFormat(data))
3055
+ return parseLinkedIn(data);
3056
+ return importFromCsv(data);
3057
+ case "vcf":
3058
+ return parseVcf(data);
3059
+ case "json":
3060
+ return importFromJson(data);
3061
+ default:
3062
+ throw new Error(`Unsupported import format: ${format}`);
3063
+ }
3064
+ }
3065
+ // src/lib/export.ts
3066
+ function toJson(contacts) {
3067
+ return JSON.stringify(contacts, null, 2);
3068
+ }
3069
+ function escapeCsvField(val) {
3070
+ if (val == null)
3071
+ return "";
3072
+ const str = String(val);
3073
+ if (str.includes(",") || str.includes('"') || str.includes(`
3074
+ `)) {
3075
+ return `"${str.replace(/"/g, '""')}"`;
3076
+ }
3077
+ return str;
3078
+ }
3079
+ function toCsv(contacts) {
3080
+ const headers = [
3081
+ "First Name",
3082
+ "Last Name",
3083
+ "Name",
3084
+ "Nickname",
3085
+ "Job Title",
3086
+ "Company",
3087
+ "Email 1 - Value",
3088
+ "Email 1 - Type",
3089
+ "Email 2 - Value",
3090
+ "Email 2 - Type",
3091
+ "Phone 1 - Value",
3092
+ "Phone 1 - Type",
3093
+ "Phone 2 - Value",
3094
+ "Phone 2 - Type",
3095
+ "Address 1 - Street",
3096
+ "Address 1 - City",
3097
+ "Address 1 - State",
3098
+ "Address 1 - Postal Code",
3099
+ "Address 1 - Country",
3100
+ "Address 1 - Type",
3101
+ "Birthday",
3102
+ "Notes",
3103
+ "Tags"
3104
+ ];
3105
+ const rows = [headers.map(escapeCsvField).join(",")];
3106
+ for (const c of contacts) {
3107
+ const emails = c.emails ?? [];
3108
+ const phones = c.phones ?? [];
3109
+ const addrs = c.addresses ?? [];
3110
+ const tags = (c.tags ?? []).map((t) => t.name).join(";");
3111
+ const row = [
3112
+ c.first_name,
3113
+ c.last_name,
3114
+ c.display_name,
3115
+ c.nickname,
3116
+ c.job_title,
3117
+ c.company?.name,
3118
+ emails[0]?.address,
3119
+ emails[0]?.type,
3120
+ emails[1]?.address,
3121
+ emails[1]?.type,
3122
+ phones[0]?.number,
3123
+ phones[0]?.type,
3124
+ phones[1]?.number,
3125
+ phones[1]?.type,
3126
+ addrs[0]?.street,
3127
+ addrs[0]?.city,
3128
+ addrs[0]?.state,
3129
+ addrs[0]?.zip,
3130
+ addrs[0]?.country,
3131
+ addrs[0]?.type,
3132
+ c.birthday,
3133
+ c.notes,
3134
+ tags
3135
+ ];
3136
+ rows.push(row.map(escapeCsvField).join(","));
3137
+ }
3138
+ return rows.join(`
3139
+ `);
3140
+ }
3141
+ function escapeVcfValue(val) {
3142
+ if (!val)
3143
+ return "";
3144
+ return val.replace(/\\/g, "\\\\").replace(/,/g, "\\,").replace(/;/g, "\\;").replace(/\n/g, "\\n");
3145
+ }
3146
+ function foldVcfLine(line) {
3147
+ if (line.length <= 75)
3148
+ return line;
3149
+ const parts = [line.slice(0, 75)];
3150
+ let i = 75;
3151
+ while (i < line.length) {
3152
+ parts.push(" " + line.slice(i, i + 74));
3153
+ i += 74;
3154
+ }
3155
+ return parts.join(`\r
3156
+ `);
3157
+ }
3158
+ function toVcf(contacts) {
3159
+ const cards = [];
3160
+ for (const c of contacts) {
3161
+ const lines = ["BEGIN:VCARD", "VERSION:3.0"];
3162
+ lines.push(`FN:${escapeVcfValue(c.display_name)}`);
3163
+ lines.push(`N:${escapeVcfValue(c.last_name)};${escapeVcfValue(c.first_name)};;;`);
3164
+ if (c.nickname)
3165
+ lines.push(`NICKNAME:${escapeVcfValue(c.nickname)}`);
3166
+ if (c.job_title)
3167
+ lines.push(`TITLE:${escapeVcfValue(c.job_title)}`);
3168
+ if (c.company?.name)
3169
+ lines.push(`ORG:${escapeVcfValue(c.company.name)}`);
3170
+ if (c.birthday)
3171
+ lines.push(`BDAY:${c.birthday.replace(/-/g, "")}`);
3172
+ for (let i = 0;i < (c.emails ?? []).length; i++) {
3173
+ const e = c.emails[i];
3174
+ const pref = i === 0 || e.is_primary ? ";PREF" : "";
3175
+ lines.push(`EMAIL;TYPE=${e.type.toUpperCase()}${pref}:${escapeVcfValue(e.address)}`);
3176
+ }
3177
+ for (let i = 0;i < (c.phones ?? []).length; i++) {
3178
+ const p = c.phones[i];
3179
+ const pref = i === 0 || p.is_primary ? ";PREF" : "";
3180
+ const vcfType = p.type === "mobile" ? "CELL" : p.type.toUpperCase();
3181
+ lines.push(`TEL;TYPE=${vcfType}${pref}:${escapeVcfValue(p.number)}`);
3182
+ }
3183
+ for (let i = 0;i < (c.addresses ?? []).length; i++) {
3184
+ const a = c.addresses[i];
3185
+ const pref = i === 0 || a.is_primary ? ";PREF" : "";
3186
+ lines.push(`ADR;TYPE=${a.type.toUpperCase()}${pref}:;;${escapeVcfValue(a.street)};${escapeVcfValue(a.city)};${escapeVcfValue(a.state)};${escapeVcfValue(a.zip)};${escapeVcfValue(a.country)}`);
3187
+ }
3188
+ for (const sp of c.social_profiles ?? []) {
3189
+ if (sp.url)
3190
+ lines.push(`URL;TYPE=${sp.platform.toUpperCase()}:${escapeVcfValue(sp.url)}`);
3191
+ if (sp.handle)
3192
+ lines.push(`X-SOCIALPROFILE;TYPE=${sp.platform.toLowerCase()}:${escapeVcfValue(sp.handle)}`);
3193
+ }
3194
+ if (c.notes)
3195
+ lines.push(`NOTE:${escapeVcfValue(c.notes)}`);
3196
+ if (c.tags && c.tags.length > 0) {
3197
+ lines.push(`CATEGORIES:${c.tags.map((t) => escapeVcfValue(t.name)).join(",")}`);
3198
+ }
3199
+ lines.push(`UID:${c.id}`);
3200
+ lines.push("END:VCARD");
3201
+ cards.push(lines.map(foldVcfLine).join(`\r
3202
+ `));
3203
+ }
3204
+ return cards.join(`\r
3205
+ `);
3206
+ }
3207
+ async function exportContacts(format, contacts) {
3208
+ switch (format) {
3209
+ case "json":
3210
+ return toJson(contacts);
3211
+ case "csv":
3212
+ return toCsv(contacts);
3213
+ case "vcf":
3214
+ return toVcf(contacts);
3215
+ default:
3216
+ throw new Error(`Unsupported export format: ${format}`);
3217
+ }
3218
+ }
2136
3219
  // src/lib/gmail-import.ts
2137
3220
  function parseAddressHeader(header) {
2138
3221
  const results = [];
@@ -2390,11 +3473,15 @@ async function pullGoogleContactsAsInputs(opts = {}) {
2390
3473
  const people = opts.query ? await searchGoogleContacts(opts.query, opts) : await listGoogleContacts(opts);
2391
3474
  return people.filter((p) => p.emailAddresses?.some((e) => e.value)).map(googlePersonToContactInput);
2392
3475
  }
3476
+
3477
+ // src/index.ts
3478
+ init_types();
2393
3479
  export {
2394
3480
  updateVendorCommunication,
2395
3481
  updateTag,
2396
3482
  updateOrgMember,
2397
3483
  updateGroup,
3484
+ updateDeal,
2398
3485
  updateContactTask,
2399
3486
  updateContact,
2400
3487
  updateCompany,
@@ -2415,10 +3502,12 @@ export {
2415
3502
  pushContactToGoogle,
2416
3503
  pullGoogleContactsAsInputs,
2417
3504
  parseName,
3505
+ parseLinkedIn,
2418
3506
  parseAddressHeader,
2419
3507
  mergeContacts,
2420
3508
  markFollowUpDone,
2421
3509
  logVendorCommunication,
3510
+ logEvent,
2422
3511
  logActivity,
2423
3512
  listVendorCommunications,
2424
3513
  listTags,
@@ -2438,25 +3527,38 @@ export {
2438
3527
  listGroups,
2439
3528
  listGoogleContacts,
2440
3529
  listFollowUpDue,
3530
+ listEvents,
3531
+ listDeals,
2441
3532
  listContactsInGroup,
2442
3533
  listContactsByTag,
2443
3534
  listContacts,
2444
3535
  listContactTasks,
3536
+ listContactAudit,
2445
3537
  listCompanyRelationships,
2446
3538
  listCompanyEmployees,
2447
3539
  listCompaniesInGroup,
2448
3540
  listCompanies,
3541
+ listColdContacts,
2449
3542
  listApplications,
2450
3543
  listActivity,
3544
+ importToApple,
3545
+ importFromCsv,
3546
+ importContacts,
2451
3547
  googlePersonToContactInput,
3548
+ getUpcomingItems,
2452
3549
  getTagByName,
2453
3550
  getTag,
2454
3551
  getRelationship,
2455
3552
  getOrgMember,
2456
3553
  getNote,
3554
+ getNetworkStats,
2457
3555
  getGroup,
3556
+ getEvent,
2458
3557
  getEntityTeam,
3558
+ getDealsByStage,
3559
+ getDeal,
2459
3560
  getDatabase,
3561
+ getContactTimeline,
2460
3562
  getContactTask,
2461
3563
  getContactByEmail,
2462
3564
  getContact,
@@ -2464,13 +3566,18 @@ export {
2464
3566
  getCompany,
2465
3567
  getApplication,
2466
3568
  getActivity,
3569
+ generateBrief,
2467
3570
  extractContactsFromGmail,
3571
+ exportFromApple,
3572
+ exportContacts,
2468
3573
  domainToCompany,
2469
3574
  deleteVendorCommunication,
2470
3575
  deleteTag,
2471
3576
  deleteRelationship,
2472
3577
  deleteNote,
2473
3578
  deleteGroup,
3579
+ deleteEvent,
3580
+ deleteDeal,
2474
3581
  deleteContactTask,
2475
3582
  deleteContact,
2476
3583
  deleteCompanyRelationship,
@@ -2479,6 +3586,7 @@ export {
2479
3586
  createTag,
2480
3587
  createRelationship,
2481
3588
  createGroup,
3589
+ createDeal,
2482
3590
  createContactTask,
2483
3591
  createContact,
2484
3592
  createCompanyRelationship,
@@ -2487,6 +3595,7 @@ export {
2487
3595
  contactToGoogleArgs,
2488
3596
  checkEscalations,
2489
3597
  autoLinkContactToCompany,
3598
+ auditContact,
2490
3599
  archiveContact,
2491
3600
  archiveCompany,
2492
3601
  addTagToContact,