@hasna/contacts 0.2.7 → 0.2.8
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/lib/connector.d.ts +44 -0
- package/dist/lib/connector.d.ts.map +1 -0
- package/dist/lib/gmail-import.d.ts +18 -5
- package/dist/lib/gmail-import.d.ts.map +1 -1
- package/dist/lib/google-contacts.d.ts +115 -0
- package/dist/lib/google-contacts.d.ts.map +1 -0
- package/dist/mcp/index.js +48 -27
- package/package.json +1 -1
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic connector runner — the scalable foundation for all external service
|
|
3
|
+
* integrations. Any connector (Gmail, Google Contacts, HubSpot, Salesforce…)
|
|
4
|
+
* uses this same interface; only the `name` and `args` differ.
|
|
5
|
+
*
|
|
6
|
+
* Prerequisites: the target connector must be globally installed and authed:
|
|
7
|
+
* bun install -g @hasnaxyz/connect-{name}
|
|
8
|
+
* connect-{name} auth login
|
|
9
|
+
*/
|
|
10
|
+
export interface ConnectorRunOptions {
|
|
11
|
+
/** Connector profile to use (default: "default") */
|
|
12
|
+
profile?: string;
|
|
13
|
+
/** Timeout in ms (default: 30_000) */
|
|
14
|
+
timeout?: number;
|
|
15
|
+
}
|
|
16
|
+
export declare class ConnectorNotInstalledError extends Error {
|
|
17
|
+
readonly connectorName: string;
|
|
18
|
+
constructor(connectorName: string);
|
|
19
|
+
}
|
|
20
|
+
export declare class ConnectorAuthError extends Error {
|
|
21
|
+
readonly connectorName: string;
|
|
22
|
+
constructor(connectorName: string, detail?: string);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Execute any connector operation and return the parsed JSON output.
|
|
26
|
+
*
|
|
27
|
+
* Usage:
|
|
28
|
+
* const messages = await runConnector("gmail", ["messages", "list", "-q", "from:acme.com"]);
|
|
29
|
+
* const contacts = await runConnector("googlecontacts", ["contacts", "list"]);
|
|
30
|
+
*/
|
|
31
|
+
export declare function runConnector(name: string, args: string[], opts?: ConnectorRunOptions): Promise<unknown>;
|
|
32
|
+
/**
|
|
33
|
+
* Returns the path to a connector's token file.
|
|
34
|
+
* Useful for implementations that need direct API access with refreshed tokens.
|
|
35
|
+
* Checks canonical path first, then legacy fallback.
|
|
36
|
+
*/
|
|
37
|
+
export declare function getConnectorTokenPath(name: string, profile?: string): string | null;
|
|
38
|
+
/**
|
|
39
|
+
* Read and parse connector tokens from disk.
|
|
40
|
+
* Useful when a library needs a raw access token (e.g. for batched REST calls
|
|
41
|
+
* that would be too slow to do one-at-a-time through the CLI).
|
|
42
|
+
*/
|
|
43
|
+
export declare function readConnectorTokens(name: string, profile?: string): Record<string, unknown>;
|
|
44
|
+
//# sourceMappingURL=connector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connector.d.ts","sourceRoot":"","sources":["../../src/lib/connector.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH,MAAM,WAAW,mBAAmB;IAClC,oDAAoD;IACpD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,sCAAsC;IACtC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,0BAA2B,SAAQ,KAAK;aACvB,aAAa,EAAE,MAAM;gBAArB,aAAa,EAAE,MAAM;CAOlD;AAED,qBAAa,kBAAmB,SAAQ,KAAK;aACf,aAAa,EAAE,MAAM;gBAArB,aAAa,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM;CAQnE;AAED;;;;;;GAMG;AACH,wBAAsB,YAAY,CAChC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EAAE,EACd,IAAI,GAAE,mBAAwB,GAC7B,OAAO,CAAC,OAAO,CAAC,CAyDlB;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,SAAY,GAAG,MAAM,GAAG,IAAI,CAUtF;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,SAAY,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAQ9F"}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Gmail contact import — fetches messages matching a query
|
|
3
|
-
* senders/recipients, and returns them as
|
|
2
|
+
* Gmail contact import — fetches messages matching a query via the connect-gmail
|
|
3
|
+
* connector, extracts unique senders/recipients, and returns them as
|
|
4
|
+
* CreateContactInput objects ready to upsert into the local DB.
|
|
4
5
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* (requires connect-gmail auth login to be completed first).
|
|
6
|
+
* Auth: connect-gmail auth login (run once per profile)
|
|
7
|
+
* Install: bun install -g @hasnaxyz/connect-gmail
|
|
8
8
|
*/
|
|
9
9
|
import type { CreateContactInput } from "../types/index.js";
|
|
10
10
|
/**
|
|
@@ -15,6 +15,18 @@ export declare function parseAddressHeader(header: string): Array<{
|
|
|
15
15
|
name: string;
|
|
16
16
|
email: string;
|
|
17
17
|
}>;
|
|
18
|
+
/**
|
|
19
|
+
* Infer a company name from an email domain.
|
|
20
|
+
* Returns null for personal/generic domains (gmail, yahoo, hotmail, etc.).
|
|
21
|
+
*/
|
|
22
|
+
export declare function domainToCompany(email: string): string | null;
|
|
23
|
+
/**
|
|
24
|
+
* Parse display name into first/last name.
|
|
25
|
+
*/
|
|
26
|
+
export declare function parseName(displayName: string): {
|
|
27
|
+
first_name?: string;
|
|
28
|
+
last_name?: string;
|
|
29
|
+
};
|
|
18
30
|
export interface GmailImportOptions {
|
|
19
31
|
query: string;
|
|
20
32
|
/** Max messages to scan (default 200, max 500) */
|
|
@@ -32,6 +44,7 @@ export interface ExtractedContact {
|
|
|
32
44
|
}
|
|
33
45
|
/**
|
|
34
46
|
* Fetch messages matching `query` from Gmail and extract unique contacts.
|
|
47
|
+
* Uses connect-gmail's stored tokens for auth — no API keys needed in code.
|
|
35
48
|
* Does NOT write to the database — returns prepared CreateContactInput objects.
|
|
36
49
|
*/
|
|
37
50
|
export declare function extractContactsFromGmail(opts: GmailImportOptions): Promise<ExtractedContact[]>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gmail-import.d.ts","sourceRoot":"","sources":["../../src/lib/gmail-import.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;
|
|
1
|
+
{"version":3,"file":"gmail-import.d.ts","sourceRoot":"","sources":["../../src/lib/gmail-import.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAK5D;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAmBzF;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAgB5D;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,WAAW,EAAE,MAAM,GAAG;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAO1F;AAID,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,kDAAkD;IAClD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gDAAgD;IAChD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,aAAa,EAAE,kBAAkB,CAAC;CACnC;AAQD;;;;GAIG;AACH,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,kBAAkB,GACvB,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAsF7B"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Google Contacts sync — bidirectional sync between local DB and Google Contacts
|
|
3
|
+
* via the connect-googlecontacts connector.
|
|
4
|
+
*
|
|
5
|
+
* Auth: connect-googlecontacts auth login (run once per profile)
|
|
6
|
+
* Install: bun install -g @hasnaxyz/connect-googlecontacts
|
|
7
|
+
*
|
|
8
|
+
* Data format: Google People API
|
|
9
|
+
* https://developers.google.com/people/api/rest/v1/people
|
|
10
|
+
*/
|
|
11
|
+
import type { ContactWithDetails, CreateContactInput } from "../types/index.js";
|
|
12
|
+
import { ConnectorRunOptions } from "./connector.js";
|
|
13
|
+
interface PeopleName {
|
|
14
|
+
displayName?: string;
|
|
15
|
+
givenName?: string;
|
|
16
|
+
familyName?: string;
|
|
17
|
+
middleName?: string;
|
|
18
|
+
}
|
|
19
|
+
interface PeopleEmail {
|
|
20
|
+
value?: string;
|
|
21
|
+
type?: string;
|
|
22
|
+
formattedType?: string;
|
|
23
|
+
}
|
|
24
|
+
interface PeoplePhone {
|
|
25
|
+
value?: string;
|
|
26
|
+
type?: string;
|
|
27
|
+
formattedType?: string;
|
|
28
|
+
}
|
|
29
|
+
interface PeopleOrg {
|
|
30
|
+
name?: string;
|
|
31
|
+
title?: string;
|
|
32
|
+
department?: string;
|
|
33
|
+
}
|
|
34
|
+
interface PeopleAddress {
|
|
35
|
+
streetAddress?: string;
|
|
36
|
+
city?: string;
|
|
37
|
+
region?: string;
|
|
38
|
+
postalCode?: string;
|
|
39
|
+
country?: string;
|
|
40
|
+
type?: string;
|
|
41
|
+
}
|
|
42
|
+
interface PeopleBio {
|
|
43
|
+
value?: string;
|
|
44
|
+
}
|
|
45
|
+
interface PeopleUrl {
|
|
46
|
+
value?: string;
|
|
47
|
+
type?: string;
|
|
48
|
+
}
|
|
49
|
+
export interface GooglePerson {
|
|
50
|
+
resourceName: string;
|
|
51
|
+
etag?: string;
|
|
52
|
+
names?: PeopleName[];
|
|
53
|
+
emailAddresses?: PeopleEmail[];
|
|
54
|
+
phoneNumbers?: PeoplePhone[];
|
|
55
|
+
organizations?: PeopleOrg[];
|
|
56
|
+
addresses?: PeopleAddress[];
|
|
57
|
+
biographies?: PeopleBio[];
|
|
58
|
+
urls?: PeopleUrl[];
|
|
59
|
+
birthdays?: Array<{
|
|
60
|
+
date?: {
|
|
61
|
+
year?: number;
|
|
62
|
+
month?: number;
|
|
63
|
+
day?: number;
|
|
64
|
+
};
|
|
65
|
+
}>;
|
|
66
|
+
}
|
|
67
|
+
export interface GoogleContactsSyncOptions extends ConnectorRunOptions {
|
|
68
|
+
/** Max contacts to pull per page (connector handles pagination automatically) */
|
|
69
|
+
page_size?: number;
|
|
70
|
+
/** Only import contacts matching this query */
|
|
71
|
+
query?: string;
|
|
72
|
+
}
|
|
73
|
+
export interface GoogleContactsPushOptions extends ConnectorRunOptions {
|
|
74
|
+
/** If true, update existing Google contact if resourceName is stored in custom_fields */
|
|
75
|
+
update_existing?: boolean;
|
|
76
|
+
}
|
|
77
|
+
export interface SyncResult {
|
|
78
|
+
imported: number;
|
|
79
|
+
updated: number;
|
|
80
|
+
skipped: number;
|
|
81
|
+
errors: string[];
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Convert a Google People API person to a CreateContactInput.
|
|
85
|
+
*/
|
|
86
|
+
export declare function googlePersonToContactInput(person: GooglePerson): CreateContactInput;
|
|
87
|
+
/**
|
|
88
|
+
* Convert a local ContactWithDetails to Google People API create/update args.
|
|
89
|
+
* Returns the CLI flags for `connect-googlecontacts contacts create`.
|
|
90
|
+
*/
|
|
91
|
+
export declare function contactToGoogleArgs(contact: ContactWithDetails): string[];
|
|
92
|
+
/**
|
|
93
|
+
* List all contacts from Google Contacts.
|
|
94
|
+
*/
|
|
95
|
+
export declare function listGoogleContacts(opts?: GoogleContactsSyncOptions): Promise<GooglePerson[]>;
|
|
96
|
+
/**
|
|
97
|
+
* Search contacts in Google Contacts.
|
|
98
|
+
*/
|
|
99
|
+
export declare function searchGoogleContacts(query: string, opts?: ConnectorRunOptions): Promise<GooglePerson[]>;
|
|
100
|
+
/**
|
|
101
|
+
* Create a new contact in Google Contacts from a local contact.
|
|
102
|
+
* Returns the Google resource name (e.g. "people/c12345").
|
|
103
|
+
*/
|
|
104
|
+
export declare function pushContactToGoogle(contact: ContactWithDetails, opts?: GoogleContactsPushOptions): Promise<{
|
|
105
|
+
resourceName: string;
|
|
106
|
+
action: "created" | "updated";
|
|
107
|
+
}>;
|
|
108
|
+
/**
|
|
109
|
+
* Sync all Google Contacts into the local database.
|
|
110
|
+
* Caller is responsible for the actual upsert (pass a db upsert function).
|
|
111
|
+
* Returns structured CreateContactInput objects ready for upsert.
|
|
112
|
+
*/
|
|
113
|
+
export declare function pullGoogleContactsAsInputs(opts?: GoogleContactsSyncOptions): Promise<CreateContactInput[]>;
|
|
114
|
+
export {};
|
|
115
|
+
//# sourceMappingURL=google-contacts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"google-contacts.d.ts","sourceRoot":"","sources":["../../src/lib/google-contacts.ts"],"names":[],"mappings":"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"}
|
package/dist/mcp/index.js
CHANGED
|
@@ -1773,28 +1773,42 @@ async function exportContacts(format, contacts) {
|
|
|
1773
1773
|
}
|
|
1774
1774
|
}
|
|
1775
1775
|
|
|
1776
|
-
// src/lib/
|
|
1776
|
+
// src/lib/connector.ts
|
|
1777
|
+
import { join as join2 } from "path";
|
|
1777
1778
|
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
1778
1779
|
import { homedir } from "os";
|
|
1779
|
-
|
|
1780
|
-
|
|
1780
|
+
class ConnectorAuthError extends Error {
|
|
1781
|
+
connectorName;
|
|
1782
|
+
constructor(connectorName, detail) {
|
|
1783
|
+
super(`connect-${connectorName} is not authenticated. ` + `Run: connect-${connectorName} auth login` + (detail ? ` (${detail})` : ""));
|
|
1784
|
+
this.connectorName = connectorName;
|
|
1785
|
+
this.name = "ConnectorAuthError";
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
function getConnectorTokenPath(name, profile = "default") {
|
|
1781
1789
|
const bases = [
|
|
1782
|
-
join2(homedir(), ".connectors",
|
|
1783
|
-
join2(homedir(), ".connect",
|
|
1790
|
+
join2(homedir(), ".connectors", `connect-${name}`, "profiles", profile, "tokens.json"),
|
|
1791
|
+
join2(homedir(), ".connect", `connect-${name}`, "profiles", profile, "tokens.json"),
|
|
1792
|
+
join2(homedir(), ".connect", `connect-${name}`, "tokens.json")
|
|
1784
1793
|
];
|
|
1785
|
-
for (const
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
try {
|
|
1789
|
-
const raw = readFileSync(tokenPath, "utf-8");
|
|
1790
|
-
const tokens = JSON.parse(raw);
|
|
1791
|
-
if (tokens.accessToken)
|
|
1792
|
-
return tokens.accessToken;
|
|
1793
|
-
} catch {}
|
|
1794
|
-
}
|
|
1794
|
+
for (const p of bases) {
|
|
1795
|
+
if (existsSync2(p))
|
|
1796
|
+
return p;
|
|
1795
1797
|
}
|
|
1796
|
-
|
|
1798
|
+
return null;
|
|
1797
1799
|
}
|
|
1800
|
+
function readConnectorTokens(name, profile = "default") {
|
|
1801
|
+
const path = getConnectorTokenPath(name, profile);
|
|
1802
|
+
if (!path)
|
|
1803
|
+
throw new ConnectorAuthError(name);
|
|
1804
|
+
try {
|
|
1805
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
1806
|
+
} catch {
|
|
1807
|
+
throw new ConnectorAuthError(name, "tokens file unreadable");
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
// src/lib/gmail-import.ts
|
|
1798
1812
|
function parseAddressHeader(header) {
|
|
1799
1813
|
const results = [];
|
|
1800
1814
|
const parts = header.split(/,(?![^<>]*>)(?![^"]*")/);
|
|
@@ -1856,7 +1870,17 @@ function parseName(displayName) {
|
|
|
1856
1870
|
async function extractContactsFromGmail(opts) {
|
|
1857
1871
|
const maxMessages = Math.min(opts.max_messages ?? 200, 500);
|
|
1858
1872
|
const profile = opts.gmail_profile ?? "default";
|
|
1859
|
-
|
|
1873
|
+
let token;
|
|
1874
|
+
try {
|
|
1875
|
+
const tokens = readConnectorTokens("gmail", profile);
|
|
1876
|
+
token = tokens["accessToken"];
|
|
1877
|
+
if (!token)
|
|
1878
|
+
throw new ConnectorAuthError("gmail", "accessToken missing");
|
|
1879
|
+
} catch (err) {
|
|
1880
|
+
if (err instanceof ConnectorAuthError)
|
|
1881
|
+
throw err;
|
|
1882
|
+
throw new ConnectorAuthError("gmail", String(err));
|
|
1883
|
+
}
|
|
1860
1884
|
const listUrl = new URL("https://gmail.googleapis.com/gmail/v1/users/me/messages");
|
|
1861
1885
|
listUrl.searchParams.set("q", opts.query);
|
|
1862
1886
|
listUrl.searchParams.set("maxResults", String(maxMessages));
|
|
@@ -1864,17 +1888,15 @@ async function extractContactsFromGmail(opts) {
|
|
|
1864
1888
|
headers: { Authorization: `Bearer ${token}` }
|
|
1865
1889
|
});
|
|
1866
1890
|
if (!listResp.ok) {
|
|
1867
|
-
const body = await listResp.text();
|
|
1868
1891
|
if (listResp.status === 401) {
|
|
1869
|
-
throw new
|
|
1892
|
+
throw new ConnectorAuthError("gmail", "token expired \u2014 run connect-gmail auth login");
|
|
1870
1893
|
}
|
|
1871
|
-
throw new Error(`Gmail API error ${listResp.status}: ${
|
|
1894
|
+
throw new Error(`Gmail API error ${listResp.status}: ${await listResp.text()}`);
|
|
1872
1895
|
}
|
|
1873
1896
|
const listData = await listResp.json();
|
|
1874
1897
|
const messageRefs = listData.messages ?? [];
|
|
1875
|
-
if (messageRefs.length === 0)
|
|
1898
|
+
if (messageRefs.length === 0)
|
|
1876
1899
|
return [];
|
|
1877
|
-
}
|
|
1878
1900
|
const seen = new Map;
|
|
1879
1901
|
const batchSize = 20;
|
|
1880
1902
|
for (let i = 0;i < messageRefs.length; i += batchSize) {
|
|
@@ -1882,9 +1904,9 @@ async function extractContactsFromGmail(opts) {
|
|
|
1882
1904
|
const fetches = batch.map((ref) => {
|
|
1883
1905
|
const url = new URL(`https://gmail.googleapis.com/gmail/v1/users/me/messages/${ref.id}`);
|
|
1884
1906
|
url.searchParams.set("format", "metadata");
|
|
1885
|
-
url.searchParams.
|
|
1886
|
-
url.searchParams.
|
|
1887
|
-
url.searchParams.
|
|
1907
|
+
url.searchParams.append("metadataHeaders", "From");
|
|
1908
|
+
url.searchParams.append("metadataHeaders", "To");
|
|
1909
|
+
url.searchParams.append("metadataHeaders", "Cc");
|
|
1888
1910
|
return fetch(url.toString(), {
|
|
1889
1911
|
headers: { Authorization: `Bearer ${token}` }
|
|
1890
1912
|
}).then((r) => r.ok ? r.json() : null);
|
|
@@ -1893,8 +1915,7 @@ async function extractContactsFromGmail(opts) {
|
|
|
1893
1915
|
for (const msg of results) {
|
|
1894
1916
|
if (!msg?.payload?.headers)
|
|
1895
1917
|
continue;
|
|
1896
|
-
const
|
|
1897
|
-
const getHeader = (name) => headers.find((h) => h.name.toLowerCase() === name.toLowerCase())?.value ?? "";
|
|
1918
|
+
const getHeader = (name) => msg.payload.headers.find((h) => h.name.toLowerCase() === name.toLowerCase())?.value ?? "";
|
|
1898
1919
|
const addresses = [
|
|
1899
1920
|
...parseAddressHeader(getHeader("From")),
|
|
1900
1921
|
...parseAddressHeader(getHeader("To")),
|