botinabox 0.5.1 → 0.5.2
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/CHANGELOG.md +8 -0
- package/dist/chunk-DSNJKNEW.js +328 -0
- package/dist/cli.d.ts +4 -0
- package/dist/cli.js +129 -0
- package/dist/connector-B4Mj0P1b.d.ts +79 -0
- package/dist/connectors/google/index.d.ts +3 -1
- package/dist/connectors/google/index.js +37 -297
- package/dist/gmail-connector-2FVYTQJH.js +6 -0
- package/dist/index.d.ts +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,14 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Versioning: [S
|
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
+
## [0.5.2] — 2026-04-04
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **`authenticate()` method on Connector interface** — optional method for connectors that require OAuth or other auth flows. Accepts a `codeProvider` callback for interactive or programmatic authorization.
|
|
14
|
+
- **Google connectors implement `authenticate()`** — `GoogleGmailConnector` and `GoogleCalendarConnector` now support self-authentication: generate consent URL, exchange code for tokens, persist via tokenSaver callback.
|
|
15
|
+
- **`botinabox auth google` CLI** — native CLI command for Google OAuth setup: `botinabox auth google <account-email> --client-id=... --client-secret=...`. Stores tokens in the secrets table.
|
|
16
|
+
|
|
9
17
|
## [0.5.1] — 2026-04-04
|
|
10
18
|
|
|
11
19
|
### Fixed
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
// src/connectors/google/oauth.ts
|
|
2
|
+
var _google;
|
|
3
|
+
async function getGoogle() {
|
|
4
|
+
if (!_google) {
|
|
5
|
+
try {
|
|
6
|
+
const mod = await import("googleapis");
|
|
7
|
+
_google = mod.google;
|
|
8
|
+
} catch {
|
|
9
|
+
throw new Error(
|
|
10
|
+
"googleapis is required for Google connectors. Install it: npm install googleapis"
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return _google;
|
|
15
|
+
}
|
|
16
|
+
async function createOAuth2Client(config) {
|
|
17
|
+
const google = await getGoogle();
|
|
18
|
+
return new google.auth.OAuth2(
|
|
19
|
+
config.clientId,
|
|
20
|
+
config.clientSecret,
|
|
21
|
+
config.redirectUri
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
function getAuthUrl(client, scopes) {
|
|
25
|
+
return client.generateAuthUrl({
|
|
26
|
+
access_type: "offline",
|
|
27
|
+
prompt: "consent",
|
|
28
|
+
scope: scopes
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
async function exchangeCode(client, code) {
|
|
32
|
+
const { tokens } = await client.getToken(code);
|
|
33
|
+
return tokens;
|
|
34
|
+
}
|
|
35
|
+
async function loadTokens(getter, accountKey) {
|
|
36
|
+
const raw = await getter(`google_tokens:${accountKey}`);
|
|
37
|
+
if (!raw) return null;
|
|
38
|
+
try {
|
|
39
|
+
return JSON.parse(raw);
|
|
40
|
+
} catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
async function saveTokens(setter, accountKey, tokens) {
|
|
45
|
+
await setter(`google_tokens:${accountKey}`, JSON.stringify(tokens));
|
|
46
|
+
}
|
|
47
|
+
async function refreshIfNeeded(client, tokens, saver) {
|
|
48
|
+
const buffer = 6e4;
|
|
49
|
+
const isExpired = tokens.expiry_date != null && Date.now() >= tokens.expiry_date - buffer;
|
|
50
|
+
if (!isExpired) return tokens;
|
|
51
|
+
client.setCredentials(tokens);
|
|
52
|
+
const { credentials } = await client.refreshAccessToken();
|
|
53
|
+
const refreshed = {
|
|
54
|
+
access_token: credentials.access_token,
|
|
55
|
+
refresh_token: credentials.refresh_token ?? tokens.refresh_token,
|
|
56
|
+
expiry_date: credentials.expiry_date ?? void 0,
|
|
57
|
+
token_type: credentials.token_type ?? "Bearer"
|
|
58
|
+
};
|
|
59
|
+
if (saver) {
|
|
60
|
+
await saver(refreshed);
|
|
61
|
+
}
|
|
62
|
+
return refreshed;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// src/connectors/google/gmail-connector.ts
|
|
66
|
+
var GoogleGmailConnector = class {
|
|
67
|
+
id = "google-gmail";
|
|
68
|
+
meta = {
|
|
69
|
+
displayName: "Google Gmail",
|
|
70
|
+
provider: "google",
|
|
71
|
+
dataType: "email"
|
|
72
|
+
};
|
|
73
|
+
tokenLoader;
|
|
74
|
+
tokenSaver;
|
|
75
|
+
client = null;
|
|
76
|
+
config = null;
|
|
77
|
+
tokens = null;
|
|
78
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
79
|
+
gmail = null;
|
|
80
|
+
constructor(opts) {
|
|
81
|
+
this.tokenLoader = opts.tokenLoader;
|
|
82
|
+
this.tokenSaver = opts.tokenSaver;
|
|
83
|
+
}
|
|
84
|
+
// ── Lifecycle ──────────────────────────────────────────────────
|
|
85
|
+
async connect(config) {
|
|
86
|
+
this.config = config;
|
|
87
|
+
this.client = await createOAuth2Client(config.oauth);
|
|
88
|
+
this.tokens = await loadTokens(this.tokenLoader, config.account);
|
|
89
|
+
if (!this.tokens) {
|
|
90
|
+
throw new Error(
|
|
91
|
+
`No stored tokens for account ${config.account}. Complete the OAuth flow first.`
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
this.tokens = await refreshIfNeeded(
|
|
95
|
+
this.client,
|
|
96
|
+
this.tokens,
|
|
97
|
+
async (t) => saveTokens(this.tokenSaver, config.account, t)
|
|
98
|
+
);
|
|
99
|
+
this.client.setCredentials(this.tokens);
|
|
100
|
+
const { google } = await import("googleapis");
|
|
101
|
+
this.gmail = google.gmail({ version: "v1", auth: this.client });
|
|
102
|
+
}
|
|
103
|
+
async disconnect() {
|
|
104
|
+
this.client = null;
|
|
105
|
+
this.gmail = null;
|
|
106
|
+
this.tokens = null;
|
|
107
|
+
this.config = null;
|
|
108
|
+
}
|
|
109
|
+
async healthCheck() {
|
|
110
|
+
try {
|
|
111
|
+
this.ensureConnected();
|
|
112
|
+
const res = await this.gmail.users.getProfile({ userId: "me" });
|
|
113
|
+
return { ok: true, account: res.data.emailAddress };
|
|
114
|
+
} catch (err) {
|
|
115
|
+
return { ok: false, error: errorMessage(err) };
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// ── Auth ───────────────────────────────────────────────────────
|
|
119
|
+
async authenticate(codeProvider) {
|
|
120
|
+
if (!this.config) {
|
|
121
|
+
return { success: false, error: "Call connect() first to set config, or pass config and call authenticate() before connect()." };
|
|
122
|
+
}
|
|
123
|
+
try {
|
|
124
|
+
const client = await createOAuth2Client(this.config.oauth);
|
|
125
|
+
const scopes = this.config.scopes ?? [
|
|
126
|
+
"https://www.googleapis.com/auth/gmail.readonly",
|
|
127
|
+
"https://www.googleapis.com/auth/gmail.send"
|
|
128
|
+
];
|
|
129
|
+
const authUrl = getAuthUrl(client, scopes);
|
|
130
|
+
const code = await codeProvider(authUrl);
|
|
131
|
+
const tokens = await exchangeCode(client, code);
|
|
132
|
+
await saveTokens(this.tokenSaver, this.config.account, tokens);
|
|
133
|
+
this.tokens = tokens;
|
|
134
|
+
this.client = client;
|
|
135
|
+
this.client.setCredentials(tokens);
|
|
136
|
+
const { google } = await import("googleapis");
|
|
137
|
+
this.gmail = google.gmail({ version: "v1", auth: this.client });
|
|
138
|
+
return { success: true, account: this.config.account };
|
|
139
|
+
} catch (err) {
|
|
140
|
+
return { success: false, error: errorMessage(err) };
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
// ── Sync ───────────────────────────────────────────────────────
|
|
144
|
+
async sync(options) {
|
|
145
|
+
this.ensureConnected();
|
|
146
|
+
if (options?.cursor) {
|
|
147
|
+
return this.syncIncremental(options.cursor, options.limit);
|
|
148
|
+
}
|
|
149
|
+
return this.syncFull(options);
|
|
150
|
+
}
|
|
151
|
+
/** Incremental sync using Gmail history API. */
|
|
152
|
+
async syncIncremental(startHistoryId, limit) {
|
|
153
|
+
const records = [];
|
|
154
|
+
const errors = [];
|
|
155
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
156
|
+
let pageToken;
|
|
157
|
+
let latestHistoryId = startHistoryId;
|
|
158
|
+
do {
|
|
159
|
+
const res = await this.gmail.users.history.list({
|
|
160
|
+
userId: "me",
|
|
161
|
+
startHistoryId,
|
|
162
|
+
historyTypes: ["messageAdded"],
|
|
163
|
+
...pageToken ? { pageToken } : {}
|
|
164
|
+
});
|
|
165
|
+
latestHistoryId = res.data.historyId ?? latestHistoryId;
|
|
166
|
+
const histories = res.data.history ?? [];
|
|
167
|
+
for (const h of histories) {
|
|
168
|
+
for (const added of h.messagesAdded ?? []) {
|
|
169
|
+
const msgId = added.message?.id;
|
|
170
|
+
if (!msgId || seenIds.has(msgId)) continue;
|
|
171
|
+
seenIds.add(msgId);
|
|
172
|
+
try {
|
|
173
|
+
const record = await this.fetchMessage(msgId);
|
|
174
|
+
records.push(record);
|
|
175
|
+
} catch (err) {
|
|
176
|
+
errors.push({ id: msgId, error: errorMessage(err) });
|
|
177
|
+
}
|
|
178
|
+
if (limit && records.length >= limit) {
|
|
179
|
+
return { records, cursor: latestHistoryId, hasMore: true, errors };
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
pageToken = res.data.nextPageToken ?? void 0;
|
|
184
|
+
} while (pageToken);
|
|
185
|
+
return { records, cursor: latestHistoryId, hasMore: false, errors };
|
|
186
|
+
}
|
|
187
|
+
/** Full sync — list messages and fetch each one. */
|
|
188
|
+
async syncFull(options) {
|
|
189
|
+
const records = [];
|
|
190
|
+
const errors = [];
|
|
191
|
+
const maxResults = options?.limit ?? 100;
|
|
192
|
+
let query = "";
|
|
193
|
+
if (options?.since) {
|
|
194
|
+
const epoch = Math.floor(new Date(options.since).getTime() / 1e3);
|
|
195
|
+
query = `after:${epoch}`;
|
|
196
|
+
}
|
|
197
|
+
if (options?.filters?.q) {
|
|
198
|
+
query = query ? `${query} ${options.filters.q}` : String(options.filters.q);
|
|
199
|
+
}
|
|
200
|
+
let pageToken;
|
|
201
|
+
let collected = 0;
|
|
202
|
+
do {
|
|
203
|
+
const res = await this.gmail.users.messages.list({
|
|
204
|
+
userId: "me",
|
|
205
|
+
maxResults: Math.min(maxResults - collected, 100),
|
|
206
|
+
...query ? { q: query } : {},
|
|
207
|
+
...pageToken ? { pageToken } : {}
|
|
208
|
+
});
|
|
209
|
+
const messages = res.data.messages ?? [];
|
|
210
|
+
for (const msg of messages) {
|
|
211
|
+
try {
|
|
212
|
+
const record = await this.fetchMessage(msg.id);
|
|
213
|
+
records.push(record);
|
|
214
|
+
} catch (err) {
|
|
215
|
+
errors.push({ id: msg.id, error: errorMessage(err) });
|
|
216
|
+
}
|
|
217
|
+
collected++;
|
|
218
|
+
if (collected >= maxResults) break;
|
|
219
|
+
}
|
|
220
|
+
pageToken = res.data.nextPageToken ?? void 0;
|
|
221
|
+
} while (pageToken && collected < maxResults);
|
|
222
|
+
const profile = await this.gmail.users.getProfile({ userId: "me" });
|
|
223
|
+
const cursor = profile.data.historyId ?? void 0;
|
|
224
|
+
return {
|
|
225
|
+
records,
|
|
226
|
+
cursor,
|
|
227
|
+
hasMore: !!pageToken,
|
|
228
|
+
errors
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
// ── Push (send email) ─────────────────────────────────────────
|
|
232
|
+
async push(payload) {
|
|
233
|
+
this.ensureConnected();
|
|
234
|
+
try {
|
|
235
|
+
const toHeader = payload.to.map(formatAddress).join(", ");
|
|
236
|
+
const ccHeader = payload.cc.length ? `Cc: ${payload.cc.map(formatAddress).join(", ")}\r
|
|
237
|
+
` : "";
|
|
238
|
+
const bccHeader = payload.bcc.length ? `Bcc: ${payload.bcc.map(formatAddress).join(", ")}\r
|
|
239
|
+
` : "";
|
|
240
|
+
const mime = [
|
|
241
|
+
`To: ${toHeader}\r
|
|
242
|
+
`,
|
|
243
|
+
ccHeader,
|
|
244
|
+
bccHeader,
|
|
245
|
+
`Subject: ${payload.subject}\r
|
|
246
|
+
`,
|
|
247
|
+
`Content-Type: text/plain; charset="UTF-8"\r
|
|
248
|
+
`,
|
|
249
|
+
`\r
|
|
250
|
+
`,
|
|
251
|
+
payload.body ?? ""
|
|
252
|
+
].join("");
|
|
253
|
+
const encoded = Buffer.from(mime).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
254
|
+
const res = await this.gmail.users.messages.send({
|
|
255
|
+
userId: "me",
|
|
256
|
+
requestBody: { raw: encoded }
|
|
257
|
+
});
|
|
258
|
+
return { success: true, externalId: res.data.id };
|
|
259
|
+
} catch (err) {
|
|
260
|
+
return { success: false, error: errorMessage(err) };
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
// ── Internals ─────────────────────────────────────────────────
|
|
264
|
+
ensureConnected() {
|
|
265
|
+
if (!this.gmail || !this.config) {
|
|
266
|
+
throw new Error("GoogleGmailConnector is not connected. Call connect() first.");
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
/** Fetch a single message by ID and parse into an EmailRecord. */
|
|
270
|
+
async fetchMessage(messageId) {
|
|
271
|
+
const res = await this.gmail.users.messages.get({
|
|
272
|
+
userId: "me",
|
|
273
|
+
id: messageId,
|
|
274
|
+
format: "metadata",
|
|
275
|
+
metadataHeaders: ["From", "To", "Cc", "Bcc", "Subject", "Date"]
|
|
276
|
+
});
|
|
277
|
+
const msg = res.data;
|
|
278
|
+
const headers = msg.payload?.headers ?? [];
|
|
279
|
+
const getHeader = (name) => headers.find((h) => h.name.toLowerCase() === name.toLowerCase())?.value ?? "";
|
|
280
|
+
return {
|
|
281
|
+
gmailId: msg.id,
|
|
282
|
+
threadId: msg.threadId,
|
|
283
|
+
account: this.config.account,
|
|
284
|
+
subject: getHeader("Subject"),
|
|
285
|
+
from: parseAddress(getHeader("From")),
|
|
286
|
+
to: parseAddressList(getHeader("To")),
|
|
287
|
+
cc: parseAddressList(getHeader("Cc")),
|
|
288
|
+
bcc: parseAddressList(getHeader("Bcc")),
|
|
289
|
+
date: new Date(getHeader("Date")).toISOString(),
|
|
290
|
+
snippet: msg.snippet ?? "",
|
|
291
|
+
labels: msg.labelIds ?? [],
|
|
292
|
+
isRead: !(msg.labelIds ?? []).includes("UNREAD")
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
function parseAddress(raw) {
|
|
297
|
+
const match = raw.match(/^(.+?)\s*<([^>]+)>$/);
|
|
298
|
+
if (match) {
|
|
299
|
+
return { name: match[1].replace(/^["']|["']$/g, "").trim(), email: match[2] };
|
|
300
|
+
}
|
|
301
|
+
return { email: raw.trim() };
|
|
302
|
+
}
|
|
303
|
+
function parseAddressList(raw) {
|
|
304
|
+
if (!raw.trim()) return [];
|
|
305
|
+
const results = [];
|
|
306
|
+
const parts = raw.split(/,(?=(?:[^<]*<[^>]*>)*[^>]*$)/);
|
|
307
|
+
for (const part of parts) {
|
|
308
|
+
const trimmed = part.trim();
|
|
309
|
+
if (trimmed) results.push(parseAddress(trimmed));
|
|
310
|
+
}
|
|
311
|
+
return results;
|
|
312
|
+
}
|
|
313
|
+
function formatAddress(addr) {
|
|
314
|
+
return addr.name ? `${addr.name} <${addr.email}>` : addr.email;
|
|
315
|
+
}
|
|
316
|
+
function errorMessage(err) {
|
|
317
|
+
return err instanceof Error ? err.message : String(err);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export {
|
|
321
|
+
createOAuth2Client,
|
|
322
|
+
getAuthUrl,
|
|
323
|
+
exchangeCode,
|
|
324
|
+
loadTokens,
|
|
325
|
+
saveTokens,
|
|
326
|
+
refreshIfNeeded,
|
|
327
|
+
GoogleGmailConnector
|
|
328
|
+
};
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import * as readline from "readline";
|
|
5
|
+
import * as fs from "fs";
|
|
6
|
+
async function main(args) {
|
|
7
|
+
const [command, subcommand, ...rest] = args;
|
|
8
|
+
if (command === "auth" && subcommand === "google") {
|
|
9
|
+
await authGoogle(rest);
|
|
10
|
+
} else {
|
|
11
|
+
console.log("Usage:");
|
|
12
|
+
console.log(" botinabox auth google <account-email> [options]");
|
|
13
|
+
console.log("");
|
|
14
|
+
console.log("Options:");
|
|
15
|
+
console.log(" --client-id=<id> Google OAuth client ID");
|
|
16
|
+
console.log(" --client-secret=<secret> Google OAuth client secret");
|
|
17
|
+
console.log(" --credentials=<path> Path to Google credentials JSON");
|
|
18
|
+
console.log(" --db=<path> Path to SQLite database (default: ./data/lattice.db)");
|
|
19
|
+
console.log(" --scopes=<scopes> Comma-separated OAuth scopes");
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
async function authGoogle(args) {
|
|
24
|
+
const flags = /* @__PURE__ */ new Map();
|
|
25
|
+
let account;
|
|
26
|
+
for (const arg of args) {
|
|
27
|
+
if (arg.startsWith("--")) {
|
|
28
|
+
const [key, ...valParts] = arg.slice(2).split("=");
|
|
29
|
+
flags.set(key, valParts.join("="));
|
|
30
|
+
} else if (!account) {
|
|
31
|
+
account = arg;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (!account) {
|
|
35
|
+
console.error("Error: account email is required.");
|
|
36
|
+
console.error(" botinabox auth google <account-email> --client-id=... --client-secret=...");
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
let clientId = flags.get("client-id") ?? process.env.GOOGLE_CLIENT_ID;
|
|
40
|
+
let clientSecret = flags.get("client-secret") ?? process.env.GOOGLE_CLIENT_SECRET;
|
|
41
|
+
const credPath = flags.get("credentials");
|
|
42
|
+
if ((!clientId || !clientSecret) && credPath) {
|
|
43
|
+
if (fs.existsSync(credPath)) {
|
|
44
|
+
const creds = JSON.parse(fs.readFileSync(credPath, "utf-8"));
|
|
45
|
+
const installed = creds.installed ?? creds.web;
|
|
46
|
+
clientId = clientId ?? installed?.client_id;
|
|
47
|
+
clientSecret = clientSecret ?? installed?.client_secret;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (!clientId || !clientSecret) {
|
|
51
|
+
console.error(
|
|
52
|
+
"Error: Google OAuth credentials required.\n Set --client-id and --client-secret, or --credentials=path/to/credentials.json,\n or GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET environment variables."
|
|
53
|
+
);
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
const defaultScopes = [
|
|
57
|
+
"https://www.googleapis.com/auth/gmail.readonly",
|
|
58
|
+
"https://www.googleapis.com/auth/gmail.send",
|
|
59
|
+
"https://www.googleapis.com/auth/calendar.readonly"
|
|
60
|
+
];
|
|
61
|
+
const scopes = flags.has("scopes") ? flags.get("scopes").split(",") : defaultScopes;
|
|
62
|
+
const dbPath = flags.get("db") ?? process.env.DATABASE_PATH ?? "./data/lattice.db";
|
|
63
|
+
const { DataStore, defineCoreTables } = await import("./index.js");
|
|
64
|
+
const db = new DataStore({ dbPath });
|
|
65
|
+
defineCoreTables(db);
|
|
66
|
+
await db.init();
|
|
67
|
+
const tokenLoader = async (key) => {
|
|
68
|
+
const rows = await db.query("secrets", { where: { name: key } });
|
|
69
|
+
const row = rows.find((r) => r["deleted_at"] == null);
|
|
70
|
+
return row?.["value"] ?? null;
|
|
71
|
+
};
|
|
72
|
+
const tokenSaver = async (key, value) => {
|
|
73
|
+
const rows = await db.query("secrets", { where: { name: key } });
|
|
74
|
+
const existing = rows.find((r) => r["deleted_at"] == null);
|
|
75
|
+
if (existing) {
|
|
76
|
+
await db.update("secrets", { id: existing["id"] }, {
|
|
77
|
+
value,
|
|
78
|
+
type: "oauth2",
|
|
79
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
80
|
+
});
|
|
81
|
+
} else {
|
|
82
|
+
await db.insert("secrets", {
|
|
83
|
+
name: key,
|
|
84
|
+
type: "oauth2",
|
|
85
|
+
value,
|
|
86
|
+
description: `Google OAuth tokens for ${account}`
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
const { GoogleGmailConnector } = await import("./gmail-connector-2FVYTQJH.js");
|
|
91
|
+
const connector = new GoogleGmailConnector({ tokenLoader, tokenSaver });
|
|
92
|
+
connector.config = {
|
|
93
|
+
account,
|
|
94
|
+
oauth: { clientId, clientSecret, redirectUri: "urn:ietf:wg:oauth:2.0:oob" },
|
|
95
|
+
scopes
|
|
96
|
+
};
|
|
97
|
+
const codeProvider = async (authUrl) => {
|
|
98
|
+
console.log(`
|
|
99
|
+
Authorize account: ${account}`);
|
|
100
|
+
console.log(`
|
|
101
|
+
Open this URL in your browser:
|
|
102
|
+
|
|
103
|
+
${authUrl}
|
|
104
|
+
`);
|
|
105
|
+
const rl = readline.createInterface({
|
|
106
|
+
input: process.stdin,
|
|
107
|
+
output: process.stdout
|
|
108
|
+
});
|
|
109
|
+
return new Promise((resolve) => {
|
|
110
|
+
rl.question("Enter the authorization code: ", (answer) => {
|
|
111
|
+
rl.close();
|
|
112
|
+
resolve(answer.trim());
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
};
|
|
116
|
+
const result = await connector.authenticate(codeProvider);
|
|
117
|
+
if (result.success) {
|
|
118
|
+
console.log(`
|
|
119
|
+
Tokens saved for ${account}. Connector is ready.`);
|
|
120
|
+
} else {
|
|
121
|
+
console.error(`
|
|
122
|
+
Authentication failed: ${result.error}`);
|
|
123
|
+
process.exit(1);
|
|
124
|
+
}
|
|
125
|
+
db.close();
|
|
126
|
+
}
|
|
127
|
+
export {
|
|
128
|
+
main
|
|
129
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/** Connector types — generic external service integrations. */
|
|
2
|
+
interface ConnectorMeta {
|
|
3
|
+
displayName: string;
|
|
4
|
+
/** Provider identifier, e.g. "google", "trello", "jira", "salesforce" */
|
|
5
|
+
provider: string;
|
|
6
|
+
/** Data type this connector handles, e.g. "email", "calendar", "board", "crm" */
|
|
7
|
+
dataType: string;
|
|
8
|
+
}
|
|
9
|
+
interface SyncOptions {
|
|
10
|
+
/** Only sync records after this ISO 8601 timestamp */
|
|
11
|
+
since?: string;
|
|
12
|
+
/** Provider-specific incremental sync token */
|
|
13
|
+
cursor?: string;
|
|
14
|
+
/** Maximum number of records to fetch */
|
|
15
|
+
limit?: number;
|
|
16
|
+
/** Provider-specific query filters */
|
|
17
|
+
filters?: Record<string, unknown>;
|
|
18
|
+
}
|
|
19
|
+
interface SyncResult<T = Record<string, unknown>> {
|
|
20
|
+
/** Typed records produced by the connector — consumer decides where to store */
|
|
21
|
+
records: T[];
|
|
22
|
+
/** Next incremental sync token (persist for future calls) */
|
|
23
|
+
cursor?: string;
|
|
24
|
+
/** Whether more records are available (pagination) */
|
|
25
|
+
hasMore: boolean;
|
|
26
|
+
/** Errors encountered during sync (non-fatal per-record failures) */
|
|
27
|
+
errors: Array<{
|
|
28
|
+
id?: string;
|
|
29
|
+
error: string;
|
|
30
|
+
}>;
|
|
31
|
+
}
|
|
32
|
+
interface PushResult {
|
|
33
|
+
success: boolean;
|
|
34
|
+
externalId?: string;
|
|
35
|
+
error?: string;
|
|
36
|
+
}
|
|
37
|
+
interface AuthResult {
|
|
38
|
+
success: boolean;
|
|
39
|
+
account?: string;
|
|
40
|
+
/** URL the user must visit to authorize (for OAuth flows) */
|
|
41
|
+
authUrl?: string;
|
|
42
|
+
error?: string;
|
|
43
|
+
}
|
|
44
|
+
type ConnectorConfig = Record<string, unknown>;
|
|
45
|
+
/**
|
|
46
|
+
* Generic connector interface for external service integrations.
|
|
47
|
+
*
|
|
48
|
+
* Connectors pull and optionally push data to/from external services
|
|
49
|
+
* (Gmail, Calendar, Trello, Jira, Salesforce, etc.). They produce
|
|
50
|
+
* typed records — the consuming application decides where to store them.
|
|
51
|
+
*
|
|
52
|
+
* @typeParam T - The record type this connector produces/consumes.
|
|
53
|
+
*/
|
|
54
|
+
interface Connector<T = Record<string, unknown>> {
|
|
55
|
+
readonly id: string;
|
|
56
|
+
readonly meta: ConnectorMeta;
|
|
57
|
+
connect(config: ConnectorConfig): Promise<void>;
|
|
58
|
+
disconnect(): Promise<void>;
|
|
59
|
+
healthCheck(): Promise<{
|
|
60
|
+
ok: boolean;
|
|
61
|
+
account?: string;
|
|
62
|
+
error?: string;
|
|
63
|
+
}>;
|
|
64
|
+
/** Pull records from external source */
|
|
65
|
+
sync(options?: SyncOptions): Promise<SyncResult<T>>;
|
|
66
|
+
/** Push a record to external source (optional) */
|
|
67
|
+
push?(payload: T): Promise<PushResult>;
|
|
68
|
+
/**
|
|
69
|
+
* Run the authentication/authorization flow for this connector.
|
|
70
|
+
* For OAuth connectors, this generates the auth URL and exchanges the code for tokens.
|
|
71
|
+
*
|
|
72
|
+
* @param codeProvider - called with the auth URL; must return the authorization code.
|
|
73
|
+
* For CLI flows, this prints the URL and reads from stdin.
|
|
74
|
+
* For programmatic flows, the caller handles the redirect.
|
|
75
|
+
*/
|
|
76
|
+
authenticate?(codeProvider: (authUrl: string) => Promise<string>): Promise<AuthResult>;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export type { AuthResult as A, ConnectorConfig as C, PushResult as P, SyncOptions as S, Connector as a, ConnectorMeta as b, SyncResult as c };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as ConnectorConfig, a as Connector, b as ConnectorMeta, S as SyncOptions, c as SyncResult, P as PushResult } from '../../connector-
|
|
1
|
+
import { C as ConnectorConfig, a as Connector, b as ConnectorMeta, A as AuthResult, S as SyncOptions, c as SyncResult, P as PushResult } from '../../connector-B4Mj0P1b.js';
|
|
2
2
|
|
|
3
3
|
/** Google connector types. */
|
|
4
4
|
|
|
@@ -137,6 +137,7 @@ declare class GoogleGmailConnector implements Connector<EmailRecord> {
|
|
|
137
137
|
account?: string;
|
|
138
138
|
error?: string;
|
|
139
139
|
}>;
|
|
140
|
+
authenticate(codeProvider: (authUrl: string) => Promise<string>): Promise<AuthResult>;
|
|
140
141
|
sync(options?: SyncOptions): Promise<SyncResult<EmailRecord>>;
|
|
141
142
|
/** Incremental sync using Gmail history API. */
|
|
142
143
|
private syncIncremental;
|
|
@@ -178,6 +179,7 @@ declare class GoogleCalendarConnector implements Connector<CalendarEventRecord>
|
|
|
178
179
|
account?: string;
|
|
179
180
|
error?: string;
|
|
180
181
|
}>;
|
|
182
|
+
authenticate(codeProvider: (authUrl: string) => Promise<string>): Promise<AuthResult>;
|
|
181
183
|
sync(options?: SyncOptions): Promise<SyncResult<CalendarEventRecord>>;
|
|
182
184
|
/** Incremental sync using Calendar syncToken. */
|
|
183
185
|
private syncIncremental;
|
|
@@ -1,296 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
"googleapis is required for Google connectors. Install it: npm install googleapis"
|
|
11
|
-
);
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
return _google;
|
|
15
|
-
}
|
|
16
|
-
async function createOAuth2Client(config) {
|
|
17
|
-
const google = await getGoogle();
|
|
18
|
-
return new google.auth.OAuth2(
|
|
19
|
-
config.clientId,
|
|
20
|
-
config.clientSecret,
|
|
21
|
-
config.redirectUri
|
|
22
|
-
);
|
|
23
|
-
}
|
|
24
|
-
function getAuthUrl(client, scopes) {
|
|
25
|
-
return client.generateAuthUrl({
|
|
26
|
-
access_type: "offline",
|
|
27
|
-
prompt: "consent",
|
|
28
|
-
scope: scopes
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
async function exchangeCode(client, code) {
|
|
32
|
-
const { tokens } = await client.getToken(code);
|
|
33
|
-
return tokens;
|
|
34
|
-
}
|
|
35
|
-
async function loadTokens(getter, accountKey) {
|
|
36
|
-
const raw = await getter(`google_tokens:${accountKey}`);
|
|
37
|
-
if (!raw) return null;
|
|
38
|
-
try {
|
|
39
|
-
return JSON.parse(raw);
|
|
40
|
-
} catch {
|
|
41
|
-
return null;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
async function saveTokens(setter, accountKey, tokens) {
|
|
45
|
-
await setter(`google_tokens:${accountKey}`, JSON.stringify(tokens));
|
|
46
|
-
}
|
|
47
|
-
async function refreshIfNeeded(client, tokens, saver) {
|
|
48
|
-
const buffer = 6e4;
|
|
49
|
-
const isExpired = tokens.expiry_date != null && Date.now() >= tokens.expiry_date - buffer;
|
|
50
|
-
if (!isExpired) return tokens;
|
|
51
|
-
client.setCredentials(tokens);
|
|
52
|
-
const { credentials } = await client.refreshAccessToken();
|
|
53
|
-
const refreshed = {
|
|
54
|
-
access_token: credentials.access_token,
|
|
55
|
-
refresh_token: credentials.refresh_token ?? tokens.refresh_token,
|
|
56
|
-
expiry_date: credentials.expiry_date ?? void 0,
|
|
57
|
-
token_type: credentials.token_type ?? "Bearer"
|
|
58
|
-
};
|
|
59
|
-
if (saver) {
|
|
60
|
-
await saver(refreshed);
|
|
61
|
-
}
|
|
62
|
-
return refreshed;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// src/connectors/google/gmail-connector.ts
|
|
66
|
-
var GoogleGmailConnector = class {
|
|
67
|
-
id = "google-gmail";
|
|
68
|
-
meta = {
|
|
69
|
-
displayName: "Google Gmail",
|
|
70
|
-
provider: "google",
|
|
71
|
-
dataType: "email"
|
|
72
|
-
};
|
|
73
|
-
tokenLoader;
|
|
74
|
-
tokenSaver;
|
|
75
|
-
client = null;
|
|
76
|
-
config = null;
|
|
77
|
-
tokens = null;
|
|
78
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
79
|
-
gmail = null;
|
|
80
|
-
constructor(opts) {
|
|
81
|
-
this.tokenLoader = opts.tokenLoader;
|
|
82
|
-
this.tokenSaver = opts.tokenSaver;
|
|
83
|
-
}
|
|
84
|
-
// ── Lifecycle ──────────────────────────────────────────────────
|
|
85
|
-
async connect(config) {
|
|
86
|
-
this.config = config;
|
|
87
|
-
this.client = await createOAuth2Client(config.oauth);
|
|
88
|
-
this.tokens = await loadTokens(this.tokenLoader, config.account);
|
|
89
|
-
if (!this.tokens) {
|
|
90
|
-
throw new Error(
|
|
91
|
-
`No stored tokens for account ${config.account}. Complete the OAuth flow first.`
|
|
92
|
-
);
|
|
93
|
-
}
|
|
94
|
-
this.tokens = await refreshIfNeeded(
|
|
95
|
-
this.client,
|
|
96
|
-
this.tokens,
|
|
97
|
-
async (t) => saveTokens(this.tokenSaver, config.account, t)
|
|
98
|
-
);
|
|
99
|
-
this.client.setCredentials(this.tokens);
|
|
100
|
-
const { google } = await import("googleapis");
|
|
101
|
-
this.gmail = google.gmail({ version: "v1", auth: this.client });
|
|
102
|
-
}
|
|
103
|
-
async disconnect() {
|
|
104
|
-
this.client = null;
|
|
105
|
-
this.gmail = null;
|
|
106
|
-
this.tokens = null;
|
|
107
|
-
this.config = null;
|
|
108
|
-
}
|
|
109
|
-
async healthCheck() {
|
|
110
|
-
try {
|
|
111
|
-
this.ensureConnected();
|
|
112
|
-
const res = await this.gmail.users.getProfile({ userId: "me" });
|
|
113
|
-
return { ok: true, account: res.data.emailAddress };
|
|
114
|
-
} catch (err) {
|
|
115
|
-
return { ok: false, error: errorMessage(err) };
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
// ── Sync ───────────────────────────────────────────────────────
|
|
119
|
-
async sync(options) {
|
|
120
|
-
this.ensureConnected();
|
|
121
|
-
if (options?.cursor) {
|
|
122
|
-
return this.syncIncremental(options.cursor, options.limit);
|
|
123
|
-
}
|
|
124
|
-
return this.syncFull(options);
|
|
125
|
-
}
|
|
126
|
-
/** Incremental sync using Gmail history API. */
|
|
127
|
-
async syncIncremental(startHistoryId, limit) {
|
|
128
|
-
const records = [];
|
|
129
|
-
const errors = [];
|
|
130
|
-
const seenIds = /* @__PURE__ */ new Set();
|
|
131
|
-
let pageToken;
|
|
132
|
-
let latestHistoryId = startHistoryId;
|
|
133
|
-
do {
|
|
134
|
-
const res = await this.gmail.users.history.list({
|
|
135
|
-
userId: "me",
|
|
136
|
-
startHistoryId,
|
|
137
|
-
historyTypes: ["messageAdded"],
|
|
138
|
-
...pageToken ? { pageToken } : {}
|
|
139
|
-
});
|
|
140
|
-
latestHistoryId = res.data.historyId ?? latestHistoryId;
|
|
141
|
-
const histories = res.data.history ?? [];
|
|
142
|
-
for (const h of histories) {
|
|
143
|
-
for (const added of h.messagesAdded ?? []) {
|
|
144
|
-
const msgId = added.message?.id;
|
|
145
|
-
if (!msgId || seenIds.has(msgId)) continue;
|
|
146
|
-
seenIds.add(msgId);
|
|
147
|
-
try {
|
|
148
|
-
const record = await this.fetchMessage(msgId);
|
|
149
|
-
records.push(record);
|
|
150
|
-
} catch (err) {
|
|
151
|
-
errors.push({ id: msgId, error: errorMessage(err) });
|
|
152
|
-
}
|
|
153
|
-
if (limit && records.length >= limit) {
|
|
154
|
-
return { records, cursor: latestHistoryId, hasMore: true, errors };
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
pageToken = res.data.nextPageToken ?? void 0;
|
|
159
|
-
} while (pageToken);
|
|
160
|
-
return { records, cursor: latestHistoryId, hasMore: false, errors };
|
|
161
|
-
}
|
|
162
|
-
/** Full sync — list messages and fetch each one. */
|
|
163
|
-
async syncFull(options) {
|
|
164
|
-
const records = [];
|
|
165
|
-
const errors = [];
|
|
166
|
-
const maxResults = options?.limit ?? 100;
|
|
167
|
-
let query = "";
|
|
168
|
-
if (options?.since) {
|
|
169
|
-
const epoch = Math.floor(new Date(options.since).getTime() / 1e3);
|
|
170
|
-
query = `after:${epoch}`;
|
|
171
|
-
}
|
|
172
|
-
if (options?.filters?.q) {
|
|
173
|
-
query = query ? `${query} ${options.filters.q}` : String(options.filters.q);
|
|
174
|
-
}
|
|
175
|
-
let pageToken;
|
|
176
|
-
let collected = 0;
|
|
177
|
-
do {
|
|
178
|
-
const res = await this.gmail.users.messages.list({
|
|
179
|
-
userId: "me",
|
|
180
|
-
maxResults: Math.min(maxResults - collected, 100),
|
|
181
|
-
...query ? { q: query } : {},
|
|
182
|
-
...pageToken ? { pageToken } : {}
|
|
183
|
-
});
|
|
184
|
-
const messages = res.data.messages ?? [];
|
|
185
|
-
for (const msg of messages) {
|
|
186
|
-
try {
|
|
187
|
-
const record = await this.fetchMessage(msg.id);
|
|
188
|
-
records.push(record);
|
|
189
|
-
} catch (err) {
|
|
190
|
-
errors.push({ id: msg.id, error: errorMessage(err) });
|
|
191
|
-
}
|
|
192
|
-
collected++;
|
|
193
|
-
if (collected >= maxResults) break;
|
|
194
|
-
}
|
|
195
|
-
pageToken = res.data.nextPageToken ?? void 0;
|
|
196
|
-
} while (pageToken && collected < maxResults);
|
|
197
|
-
const profile = await this.gmail.users.getProfile({ userId: "me" });
|
|
198
|
-
const cursor = profile.data.historyId ?? void 0;
|
|
199
|
-
return {
|
|
200
|
-
records,
|
|
201
|
-
cursor,
|
|
202
|
-
hasMore: !!pageToken,
|
|
203
|
-
errors
|
|
204
|
-
};
|
|
205
|
-
}
|
|
206
|
-
// ── Push (send email) ─────────────────────────────────────────
|
|
207
|
-
async push(payload) {
|
|
208
|
-
this.ensureConnected();
|
|
209
|
-
try {
|
|
210
|
-
const toHeader = payload.to.map(formatAddress).join(", ");
|
|
211
|
-
const ccHeader = payload.cc.length ? `Cc: ${payload.cc.map(formatAddress).join(", ")}\r
|
|
212
|
-
` : "";
|
|
213
|
-
const bccHeader = payload.bcc.length ? `Bcc: ${payload.bcc.map(formatAddress).join(", ")}\r
|
|
214
|
-
` : "";
|
|
215
|
-
const mime = [
|
|
216
|
-
`To: ${toHeader}\r
|
|
217
|
-
`,
|
|
218
|
-
ccHeader,
|
|
219
|
-
bccHeader,
|
|
220
|
-
`Subject: ${payload.subject}\r
|
|
221
|
-
`,
|
|
222
|
-
`Content-Type: text/plain; charset="UTF-8"\r
|
|
223
|
-
`,
|
|
224
|
-
`\r
|
|
225
|
-
`,
|
|
226
|
-
payload.body ?? ""
|
|
227
|
-
].join("");
|
|
228
|
-
const encoded = Buffer.from(mime).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
229
|
-
const res = await this.gmail.users.messages.send({
|
|
230
|
-
userId: "me",
|
|
231
|
-
requestBody: { raw: encoded }
|
|
232
|
-
});
|
|
233
|
-
return { success: true, externalId: res.data.id };
|
|
234
|
-
} catch (err) {
|
|
235
|
-
return { success: false, error: errorMessage(err) };
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
// ── Internals ─────────────────────────────────────────────────
|
|
239
|
-
ensureConnected() {
|
|
240
|
-
if (!this.gmail || !this.config) {
|
|
241
|
-
throw new Error("GoogleGmailConnector is not connected. Call connect() first.");
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
/** Fetch a single message by ID and parse into an EmailRecord. */
|
|
245
|
-
async fetchMessage(messageId) {
|
|
246
|
-
const res = await this.gmail.users.messages.get({
|
|
247
|
-
userId: "me",
|
|
248
|
-
id: messageId,
|
|
249
|
-
format: "metadata",
|
|
250
|
-
metadataHeaders: ["From", "To", "Cc", "Bcc", "Subject", "Date"]
|
|
251
|
-
});
|
|
252
|
-
const msg = res.data;
|
|
253
|
-
const headers = msg.payload?.headers ?? [];
|
|
254
|
-
const getHeader = (name) => headers.find((h) => h.name.toLowerCase() === name.toLowerCase())?.value ?? "";
|
|
255
|
-
return {
|
|
256
|
-
gmailId: msg.id,
|
|
257
|
-
threadId: msg.threadId,
|
|
258
|
-
account: this.config.account,
|
|
259
|
-
subject: getHeader("Subject"),
|
|
260
|
-
from: parseAddress(getHeader("From")),
|
|
261
|
-
to: parseAddressList(getHeader("To")),
|
|
262
|
-
cc: parseAddressList(getHeader("Cc")),
|
|
263
|
-
bcc: parseAddressList(getHeader("Bcc")),
|
|
264
|
-
date: new Date(getHeader("Date")).toISOString(),
|
|
265
|
-
snippet: msg.snippet ?? "",
|
|
266
|
-
labels: msg.labelIds ?? [],
|
|
267
|
-
isRead: !(msg.labelIds ?? []).includes("UNREAD")
|
|
268
|
-
};
|
|
269
|
-
}
|
|
270
|
-
};
|
|
271
|
-
function parseAddress(raw) {
|
|
272
|
-
const match = raw.match(/^(.+?)\s*<([^>]+)>$/);
|
|
273
|
-
if (match) {
|
|
274
|
-
return { name: match[1].replace(/^["']|["']$/g, "").trim(), email: match[2] };
|
|
275
|
-
}
|
|
276
|
-
return { email: raw.trim() };
|
|
277
|
-
}
|
|
278
|
-
function parseAddressList(raw) {
|
|
279
|
-
if (!raw.trim()) return [];
|
|
280
|
-
const results = [];
|
|
281
|
-
const parts = raw.split(/,(?=(?:[^<]*<[^>]*>)*[^>]*$)/);
|
|
282
|
-
for (const part of parts) {
|
|
283
|
-
const trimmed = part.trim();
|
|
284
|
-
if (trimmed) results.push(parseAddress(trimmed));
|
|
285
|
-
}
|
|
286
|
-
return results;
|
|
287
|
-
}
|
|
288
|
-
function formatAddress(addr) {
|
|
289
|
-
return addr.name ? `${addr.name} <${addr.email}>` : addr.email;
|
|
290
|
-
}
|
|
291
|
-
function errorMessage(err) {
|
|
292
|
-
return err instanceof Error ? err.message : String(err);
|
|
293
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
GoogleGmailConnector,
|
|
3
|
+
createOAuth2Client,
|
|
4
|
+
exchangeCode,
|
|
5
|
+
getAuthUrl,
|
|
6
|
+
loadTokens,
|
|
7
|
+
refreshIfNeeded,
|
|
8
|
+
saveTokens
|
|
9
|
+
} from "../../chunk-DSNJKNEW.js";
|
|
294
10
|
|
|
295
11
|
// src/connectors/google/calendar-connector.ts
|
|
296
12
|
var GoogleCalendarConnector = class {
|
|
@@ -346,7 +62,31 @@ var GoogleCalendarConnector = class {
|
|
|
346
62
|
);
|
|
347
63
|
return { ok: true, account: primary?.id ?? this.config.account };
|
|
348
64
|
} catch (err) {
|
|
349
|
-
return { ok: false, error:
|
|
65
|
+
return { ok: false, error: errorMessage(err) };
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// ── Auth ───────────────────────────────────────────────────────
|
|
69
|
+
async authenticate(codeProvider) {
|
|
70
|
+
if (!this.config) {
|
|
71
|
+
return { success: false, error: "Call connect() first to set config, or pass config and call authenticate() before connect()." };
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
const client = await createOAuth2Client(this.config.oauth);
|
|
75
|
+
const scopes = this.config.scopes ?? [
|
|
76
|
+
"https://www.googleapis.com/auth/calendar.readonly"
|
|
77
|
+
];
|
|
78
|
+
const authUrl = getAuthUrl(client, scopes);
|
|
79
|
+
const code = await codeProvider(authUrl);
|
|
80
|
+
const tokens = await exchangeCode(client, code);
|
|
81
|
+
await saveTokens(this.tokenSaver, this.config.account, tokens);
|
|
82
|
+
this.tokens = tokens;
|
|
83
|
+
this.client = client;
|
|
84
|
+
this.client.setCredentials(tokens);
|
|
85
|
+
const { google } = await import("googleapis");
|
|
86
|
+
this.calendar = google.calendar({ version: "v3", auth: this.client });
|
|
87
|
+
return { success: true, account: this.config.account };
|
|
88
|
+
} catch (err) {
|
|
89
|
+
return { success: false, error: errorMessage(err) };
|
|
350
90
|
}
|
|
351
91
|
}
|
|
352
92
|
// ── Sync ───────────────────────────────────────────────────────
|
|
@@ -376,7 +116,7 @@ var GoogleCalendarConnector = class {
|
|
|
376
116
|
try {
|
|
377
117
|
records.push(this.mapEvent(event, calendarId));
|
|
378
118
|
} catch (err) {
|
|
379
|
-
errors.push({ id: event.id, error:
|
|
119
|
+
errors.push({ id: event.id, error: errorMessage(err) });
|
|
380
120
|
}
|
|
381
121
|
if (options?.limit && records.length >= options.limit) break;
|
|
382
122
|
}
|
|
@@ -418,7 +158,7 @@ var GoogleCalendarConnector = class {
|
|
|
418
158
|
try {
|
|
419
159
|
records.push(this.mapEvent(event, calendarId));
|
|
420
160
|
} catch (err) {
|
|
421
|
-
errors.push({ id: event.id, error:
|
|
161
|
+
errors.push({ id: event.id, error: errorMessage(err) });
|
|
422
162
|
}
|
|
423
163
|
if (records.length >= maxResults) break;
|
|
424
164
|
}
|
|
@@ -472,7 +212,7 @@ var GoogleCalendarConnector = class {
|
|
|
472
212
|
};
|
|
473
213
|
}
|
|
474
214
|
};
|
|
475
|
-
function
|
|
215
|
+
function errorMessage(err) {
|
|
476
216
|
return err instanceof Error ? err.message : String(err);
|
|
477
217
|
}
|
|
478
218
|
export {
|
package/dist/index.d.ts
CHANGED
|
@@ -2,8 +2,8 @@ import { C as ChannelAdapter, H as HealthStatus, I as InboundMessage } from './c
|
|
|
2
2
|
export { A as Attachment, a as ChannelCapabilities, b as ChannelConfig, c as ChannelMeta, d as ChatType, F as FormattingMode, O as OutboundPayload, S as SendResult } from './channel-06G0vbIn.js';
|
|
3
3
|
import { T as TokenUsage, L as LLMProvider, M as ModelInfo, R as ResolvedModel, C as ChatMessage } from './provider-qqJYv9nv.js';
|
|
4
4
|
export { a as ChatParams, b as ChatResult, c as ContentBlock, d as ToolDefinition, e as ToolUse } from './provider-qqJYv9nv.js';
|
|
5
|
-
import { C as ConnectorConfig } from './connector-
|
|
6
|
-
export { a as Connector, b as ConnectorMeta, P as PushResult, S as SyncOptions, c as SyncResult } from './connector-
|
|
5
|
+
import { C as ConnectorConfig } from './connector-B4Mj0P1b.js';
|
|
6
|
+
export { A as AuthResult, a as Connector, b as ConnectorMeta, P as PushResult, S as SyncOptions, c as SyncResult } from './connector-B4Mj0P1b.js';
|
|
7
7
|
import * as better_sqlite3 from 'better-sqlite3';
|
|
8
8
|
|
|
9
9
|
/** Execution adapter types — Story 1.5 / 3.4 / 3.5 */
|