@almadar/integrations 2.24.0 → 2.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,17 +1,22 @@
1
1
  import { createLogger } from '@almadar/logger';
2
2
  import { integratorsRegistry } from '@almadar/core/patterns';
3
+ import { randomBytes, createCipheriv, createDecipheriv, createHmac, createHash } from 'crypto';
3
4
  import Stripe from 'stripe';
4
5
  import { google } from 'googleapis';
5
6
  import twilio from 'twilio';
6
7
  import sgMail from '@sendgrid/mail';
7
8
  import { Resend } from 'resend';
8
- import { createHmac } from 'crypto';
9
+ import webpush from 'web-push';
10
+ import { Readable } from 'stream';
9
11
  import { getAvailableProvider, LLMClient, EmbeddingClient } from '@almadar/llm';
10
12
  import { z } from 'zod';
11
13
  import { execSync, spawn } from 'child_process';
12
14
  import { mkdtempSync, writeFileSync, rmSync, promises } from 'fs';
13
15
  import { join } from 'path';
14
16
  import { tmpdir } from 'os';
17
+ import * as oidc from 'openid-client';
18
+ import { S3Client, PutObjectCommand, GetObjectCommand, ListObjectsV2Command, DeleteObjectCommand } from '@aws-sdk/client-s3';
19
+ import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
15
20
  import { Pool } from 'pg';
16
21
 
17
22
  // src/types.ts
@@ -222,56 +227,80 @@ function getRegisteredIntegrations() {
222
227
  }
223
228
 
224
229
  // src/factory.ts
230
+ function instanceKey(name, principal) {
231
+ return principal ? `${name}\0${principal}` : name;
232
+ }
225
233
  var IntegrationFactory = class {
226
234
  constructor() {
227
235
  this.instances = /* @__PURE__ */ new Map();
228
236
  this.configs = /* @__PURE__ */ new Map();
229
237
  }
230
238
  /**
231
- * Configure an integration (doesn't instantiate yet)
239
+ * Configure an integration (doesn't instantiate yet). A `principal` scopes
240
+ * the config to that principal; the app-wide config (no principal) is the
241
+ * fallback for every principal.
232
242
  */
233
- configure(name, config) {
234
- this.configs.set(name, { name, ...config });
243
+ configure(name, config, principal) {
244
+ this.configs.set(instanceKey(name, principal), { name, ...config });
235
245
  }
236
246
  /**
237
- * Get or create an integration instance
247
+ * Get or create an integration instance. Principal-scoped lookups fall
248
+ * back to the app-wide config when no per-principal config exists.
238
249
  */
239
- get(name) {
240
- if (this.instances.has(name)) {
241
- return this.instances.get(name);
250
+ get(name, principal) {
251
+ const key = instanceKey(name, principal);
252
+ const cached = this.instances.get(key);
253
+ if (cached) {
254
+ return cached;
242
255
  }
243
256
  const Constructor = getIntegration(name);
244
257
  if (!Constructor) {
245
258
  throw new Error(`Unknown integration: ${name}. Make sure it's imported.`);
246
259
  }
247
- const config = this.configs.get(name);
260
+ const config = this.configs.get(key) ?? this.configs.get(name);
248
261
  if (!config) {
249
262
  throw new Error(
250
263
  `Integration not configured: ${name}. Call configure() first.`
251
264
  );
252
265
  }
253
266
  const instance = new Constructor(config);
254
- this.instances.set(name, instance);
267
+ this.instances.set(key, instance);
255
268
  return instance;
256
269
  }
257
270
  /**
258
271
  * Execute an action on an integration
259
272
  */
260
- async execute(integration, action, params) {
261
- const instance = this.get(integration);
273
+ async execute(integration, action, params, context) {
274
+ const instance = this.get(integration, context?.principal);
262
275
  return await instance.execute(action, params);
263
276
  }
264
277
  /**
265
278
  * Check if integration is configured
266
279
  */
267
- isConfigured(name) {
268
- return this.configs.has(name);
280
+ isConfigured(name, principal) {
281
+ return this.configs.has(instanceKey(name, principal)) || this.configs.has(name);
269
282
  }
270
283
  /**
271
284
  * Register an integration instance directly (used by mock infrastructure)
272
285
  */
273
- registerInstance(name, instance) {
274
- this.instances.set(name, instance);
286
+ registerInstance(name, instance, principal) {
287
+ this.instances.set(instanceKey(name, principal), instance);
288
+ }
289
+ /**
290
+ * Drop the cached instance(s) for a name so the next `get` rebuilds from
291
+ * the current config — how a credential change goes live without restart.
292
+ * Configs are kept; without a name, every instance is dropped.
293
+ */
294
+ invalidate(name) {
295
+ if (name === void 0) {
296
+ this.instances.clear();
297
+ return;
298
+ }
299
+ for (const key of this.instances.keys()) {
300
+ if (key === name || key.startsWith(`${name}\0`)) {
301
+ this.instances.delete(key);
302
+ }
303
+ }
275
304
  }
276
305
  /**
277
306
  * Clear all instances (useful for testing)
@@ -298,6 +327,157 @@ function resetIntegrationFactory() {
298
327
  _factory?.reset();
299
328
  _factory = null;
300
329
  }
330
+ var CREDENTIAL_ENTITY_TYPE = "AlmadarIntegrationCredential";
331
+ var CREDENTIAL_MASTER_KEY_ENV = "ALMADAR_CREDENTIAL_MASTER_KEY";
332
+ var ENCRYPTION_ALGORITHM = "aes-256-gcm";
333
+ function isNonEmptyString(v) {
334
+ return typeof v === "string" && v.length > 0;
335
+ }
336
+ var CredentialStore = class {
337
+ constructor(adapter, masterKey) {
338
+ this.byEnvVar = /* @__PURE__ */ new Map();
339
+ this.listeners = /* @__PURE__ */ new Set();
340
+ this.warmed = false;
341
+ this.adapter = adapter;
342
+ const key = masterKey ?? process.env[CREDENTIAL_MASTER_KEY_ENV];
343
+ const valid = typeof key === "string" && /^[0-9a-fA-F]{64}$/.test(key);
344
+ if (!valid && process.env.NODE_ENV === "production") {
345
+ throw new Error(
346
+ `${CREDENTIAL_MASTER_KEY_ENV} must be a 64-character hex string (32 bytes) in production \u2014 generate one with: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"`
347
+ );
348
+ }
349
+ this.masterKeyHex = valid ? key : void 0;
350
+ }
351
+ /** True when a valid master key is present (writes and decryption enabled). */
352
+ get enabled() {
353
+ return this.masterKeyHex !== void 0;
354
+ }
355
+ /** Subscribe to credential changes; returns an unsubscribe function. */
356
+ subscribe(listener) {
357
+ this.listeners.add(listener);
358
+ return () => this.listeners.delete(listener);
359
+ }
360
+ notify() {
361
+ for (const listener of this.listeners) listener();
362
+ }
363
+ encrypt(plaintext) {
364
+ if (!this.masterKeyHex) {
365
+ throw new Error(`${CREDENTIAL_MASTER_KEY_ENV} is not configured \u2014 cannot store credentials`);
366
+ }
367
+ const iv = randomBytes(12);
368
+ const cipher = createCipheriv(ENCRYPTION_ALGORITHM, Buffer.from(this.masterKeyHex, "hex"), iv);
369
+ let ciphertext = cipher.update(plaintext, "utf8", "hex");
370
+ ciphertext += cipher.final("hex");
371
+ return { ciphertext, iv: iv.toString("hex"), authTag: cipher.getAuthTag().toString("hex") };
372
+ }
373
+ decrypt(ciphertext, ivHex, authTagHex) {
374
+ if (!this.masterKeyHex) {
375
+ throw new Error(`${CREDENTIAL_MASTER_KEY_ENV} is not configured \u2014 cannot decrypt credentials`);
376
+ }
377
+ const decipher = createDecipheriv(
378
+ ENCRYPTION_ALGORITHM,
379
+ Buffer.from(this.masterKeyHex, "hex"),
380
+ Buffer.from(ivHex, "hex")
381
+ );
382
+ decipher.setAuthTag(Buffer.from(authTagHex, "hex"));
383
+ let plaintext = decipher.update(ciphertext, "hex", "utf8");
384
+ plaintext += decipher.final("utf8");
385
+ return plaintext;
386
+ }
387
+ /**
388
+ * Load and decrypt every stored row into memory. Returns the number of
389
+ * resolvable credentials. Without a master key nothing decrypts (rows are
390
+ * left in place, resolution falls through to env).
391
+ */
392
+ async warm() {
393
+ this.byEnvVar.clear();
394
+ this.warmed = true;
395
+ if (!this.enabled) return 0;
396
+ const rows = await this.adapter.list(CREDENTIAL_ENTITY_TYPE);
397
+ for (const row of rows) {
398
+ const { id, service, envVar, ciphertext, iv, authTag } = row;
399
+ if (!isNonEmptyString(id) || !isNonEmptyString(service) || !isNonEmptyString(envVar) || !isNonEmptyString(ciphertext) || !isNonEmptyString(iv) || !isNonEmptyString(authTag)) {
400
+ continue;
401
+ }
402
+ const value = this.decrypt(ciphertext, iv, authTag);
403
+ this.byEnvVar.set(envVar, {
404
+ id,
405
+ service,
406
+ envVar,
407
+ value,
408
+ last4: value.slice(-4),
409
+ updatedAt: typeof row.updatedAt === "number" ? row.updatedAt : 0
410
+ });
411
+ }
412
+ return this.byEnvVar.size;
413
+ }
414
+ /** Resolve one credential by env-var name (warmed values only). */
415
+ resolve(envVar) {
416
+ return this.byEnvVar.get(envVar)?.value;
417
+ }
418
+ /** All warmed values as an env-shaped map, for merging over the process env. */
419
+ snapshotEnv() {
420
+ const env = {};
421
+ for (const [envVar, entry] of this.byEnvVar) env[envVar] = entry.value;
422
+ return env;
423
+ }
424
+ /** Masked entries for display — never includes plaintext. */
425
+ entries() {
426
+ return Array.from(this.byEnvVar.values()).map(({ service, envVar, last4, updatedAt }) => ({ service, envVar, last4, updatedAt })).sort((a, b) => a.service.localeCompare(b.service) || a.envVar.localeCompare(b.envVar));
427
+ }
428
+ /** Upsert one credential (encrypts, persists, re-warms the entry, notifies). */
429
+ async set(service, envVar, value) {
430
+ if (!this.warmed) await this.warm();
431
+ const { ciphertext, iv, authTag } = this.encrypt(value);
432
+ const updatedAt = Date.now();
433
+ const existing = this.byEnvVar.get(envVar);
434
+ const data = { service, envVar, ciphertext, iv, authTag, updatedAt };
435
+ let id;
436
+ if (existing) {
437
+ id = existing.id;
438
+ await this.adapter.update(CREDENTIAL_ENTITY_TYPE, id, data);
439
+ } else {
440
+ ({ id } = await this.adapter.create(CREDENTIAL_ENTITY_TYPE, data));
441
+ }
442
+ const entry = { id, service, envVar, value, last4: value.slice(-4), updatedAt };
443
+ this.byEnvVar.set(envVar, entry);
444
+ this.notify();
445
+ const { service: s, envVar: e, last4, updatedAt: u } = entry;
446
+ return { service: s, envVar: e, last4, updatedAt: u };
447
+ }
448
+ /** Delete one credential row; resolution falls back to env afterwards. */
449
+ async remove(envVar) {
450
+ if (!this.warmed) await this.warm();
451
+ const existing = this.byEnvVar.get(envVar);
452
+ if (!existing) return false;
453
+ await this.adapter.delete(CREDENTIAL_ENTITY_TYPE, existing.id);
454
+ this.byEnvVar.delete(envVar);
455
+ this.notify();
456
+ return true;
457
+ }
458
+ };
459
+
460
+ // src/credentials/resolver.ts
461
+ var installedStore = null;
462
+ var activeFactory = null;
463
+ function installActiveFactory(factory) {
464
+ activeFactory = factory;
465
+ }
466
+ function getActiveFactory() {
467
+ return activeFactory;
468
+ }
469
+ function installCredentialStore(store) {
470
+ installedStore = store;
471
+ }
472
+ function getInstalledCredentialStore() {
473
+ return installedStore;
474
+ }
475
+ function uninstallCredentialStore() {
476
+ installedStore = null;
477
+ }
478
+ function resolveCredentialRef(ref, env = process.env) {
479
+ return installedStore?.resolve(ref) ?? env[ref];
480
+ }
301
481
  var STRIPE_API_VERSION = "2025-02-24.acacia";
302
482
  function priceToTier(priceId, prices) {
303
483
  if (priceId === prices.solo) return "solo";
@@ -962,44 +1142,933 @@ var EmailIntegration = class extends BaseIntegration {
962
1142
  from || this.fromEmail
963
1143
  );
964
1144
  }
965
- throw new Error(`Unknown email provider: ${this.provider}`);
1145
+ throw new Error(`Unknown email provider: ${this.provider}`);
1146
+ }
1147
+ async sendViaSendGrid(to, subject, body, from) {
1148
+ const msg = {
1149
+ to,
1150
+ from,
1151
+ subject,
1152
+ html: body
1153
+ };
1154
+ const response = await sgMail.send(msg);
1155
+ return {
1156
+ id: response[0].headers["x-message-id"],
1157
+ status: "sent"
1158
+ };
1159
+ }
1160
+ async sendViaResend(to, subject, body, from) {
1161
+ if (!this.resendClient) {
1162
+ throw new Error("Resend client not initialized");
1163
+ }
1164
+ const response = await this.resendClient.emails.send({
1165
+ from,
1166
+ to,
1167
+ subject,
1168
+ html: body
1169
+ });
1170
+ return {
1171
+ id: response.data?.id,
1172
+ status: "sent"
1173
+ };
1174
+ }
1175
+ };
1176
+ registerIntegration("email", EmailIntegration);
1177
+ var WebhookIntegration = class extends BaseIntegration {
1178
+ constructor(config) {
1179
+ super(config);
1180
+ this.signingSecret = config.env.WEBHOOK_SIGNING_SECRET || "";
1181
+ this.timeoutMs = Number(config.env.WEBHOOK_TIMEOUT_MS) || 1e4;
1182
+ this.logger.info("Webhook integration initialized");
1183
+ }
1184
+ async execute(action, params) {
1185
+ const validation = this.validateParams(action, params);
1186
+ if (!validation.valid) {
1187
+ return {
1188
+ success: false,
1189
+ error: {
1190
+ name: "IntegrationError",
1191
+ message: "Validation failed",
1192
+ code: "VALIDATION_ERROR",
1193
+ details: validation.errors
1194
+ },
1195
+ metadata: this.createMetadata(action, 0)
1196
+ };
1197
+ }
1198
+ const startTime = Date.now();
1199
+ let retries = 0;
1200
+ try {
1201
+ let data;
1202
+ switch (action) {
1203
+ case "send":
1204
+ data = await this.executeWithRetry(() => this.send(params));
1205
+ break;
1206
+ default:
1207
+ throw new Error(`Unknown action: ${action}`);
1208
+ }
1209
+ return {
1210
+ success: true,
1211
+ data,
1212
+ metadata: this.createMetadata(action, Date.now() - startTime, retries)
1213
+ };
1214
+ } catch (error) {
1215
+ return this.handleError(action, error);
1216
+ }
1217
+ }
1218
+ async send(params) {
1219
+ const { url, event, payload, secret } = params;
1220
+ const body = JSON.stringify({
1221
+ event,
1222
+ payload: payload ?? {},
1223
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1224
+ });
1225
+ const headers = {
1226
+ "Content-Type": "application/json",
1227
+ "X-Almadar-Event": event
1228
+ };
1229
+ const signingSecret = secret || this.signingSecret;
1230
+ if (signingSecret) {
1231
+ headers["X-Almadar-Signature"] = "sha256=" + createHmac("sha256", signingSecret).update(body).digest("hex");
1232
+ }
1233
+ this.logger.debug("Sending webhook", { url: String(url), event: String(event) });
1234
+ const startTime = Date.now();
1235
+ const response = await fetch(url, {
1236
+ method: "POST",
1237
+ headers,
1238
+ body,
1239
+ signal: AbortSignal.timeout(this.timeoutMs)
1240
+ });
1241
+ if (response.status >= 500) {
1242
+ throw new Error(`Webhook endpoint returned ${response.status}`);
1243
+ }
1244
+ return {
1245
+ status: response.status,
1246
+ ok: response.ok,
1247
+ durationMs: Date.now() - startTime
1248
+ };
1249
+ }
1250
+ };
1251
+ registerIntegration("webhook", WebhookIntegration);
1252
+ function isParamRecord(value) {
1253
+ return value !== null && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date);
1254
+ }
1255
+ function toSubscription(value) {
1256
+ if (isParamRecord(value)) {
1257
+ const { endpoint, keys } = value;
1258
+ if (typeof endpoint === "string" && isParamRecord(keys)) {
1259
+ const { p256dh, auth } = keys;
1260
+ if (typeof p256dh === "string" && typeof auth === "string") {
1261
+ return { endpoint, keys: { p256dh, auth } };
1262
+ }
1263
+ }
1264
+ }
1265
+ throw new Error("push.send: subscription must be { endpoint, keys: { p256dh, auth } }");
1266
+ }
1267
+ var PushIntegration = class extends BaseIntegration {
1268
+ constructor(config) {
1269
+ super(config);
1270
+ this.vapidPublicKey = config.env.VAPID_PUBLIC_KEY || "";
1271
+ this.vapidPrivateKey = config.env.VAPID_PRIVATE_KEY || "";
1272
+ this.vapidSubject = config.env.VAPID_SUBJECT || "";
1273
+ this.logger.info("Push integration initialized");
1274
+ }
1275
+ async execute(action, params) {
1276
+ const validation = this.validateParams(action, params);
1277
+ if (!validation.valid) {
1278
+ return {
1279
+ success: false,
1280
+ error: {
1281
+ name: "IntegrationError",
1282
+ message: "Validation failed",
1283
+ code: "VALIDATION_ERROR",
1284
+ details: validation.errors
1285
+ },
1286
+ metadata: this.createMetadata(action, 0)
1287
+ };
1288
+ }
1289
+ const startTime = Date.now();
1290
+ let retries = 0;
1291
+ try {
1292
+ let data;
1293
+ switch (action) {
1294
+ case "send":
1295
+ data = await this.executeWithRetry(() => this.send(params));
1296
+ break;
1297
+ default:
1298
+ throw new Error(`Unknown action: ${action}`);
1299
+ }
1300
+ return {
1301
+ success: true,
1302
+ data,
1303
+ metadata: this.createMetadata(action, Date.now() - startTime, retries)
1304
+ };
1305
+ } catch (error) {
1306
+ return this.handleError(action, error);
1307
+ }
1308
+ }
1309
+ async send(params) {
1310
+ const { subscription, title, body, url, icon } = params;
1311
+ const sub = toSubscription(subscription);
1312
+ const payload = JSON.stringify({
1313
+ title,
1314
+ body,
1315
+ url: url || void 0,
1316
+ icon: icon || void 0
1317
+ });
1318
+ this.logger.debug("Sending push notification", { endpoint: sub.endpoint });
1319
+ try {
1320
+ const response = await webpush.sendNotification(sub, payload, {
1321
+ vapidDetails: {
1322
+ subject: this.vapidSubject,
1323
+ publicKey: this.vapidPublicKey,
1324
+ privateKey: this.vapidPrivateKey
1325
+ }
1326
+ });
1327
+ return { statusCode: response.statusCode, ok: true, expired: false };
1328
+ } catch (error) {
1329
+ if (error instanceof webpush.WebPushError) {
1330
+ if (error.statusCode >= 500) {
1331
+ throw new Error(`Push endpoint returned ${error.statusCode}`);
1332
+ }
1333
+ return {
1334
+ statusCode: error.statusCode,
1335
+ ok: false,
1336
+ expired: error.statusCode === 404 || error.statusCode === 410
1337
+ };
1338
+ }
1339
+ throw error;
1340
+ }
1341
+ }
1342
+ };
1343
+ registerIntegration("push", PushIntegration);
1344
+
1345
+ // src/integrations/calendar/webhooks.ts
1346
+ function header(headers, name) {
1347
+ const direct = headers[name] ?? headers[name.toLowerCase()];
1348
+ if (direct !== void 0) return direct;
1349
+ const found = Object.keys(headers).find((k) => k.toLowerCase() === name.toLowerCase());
1350
+ return (found !== void 0 ? headers[found] : void 0) ?? "";
1351
+ }
1352
+ function parseCalendarPushNotification(headers, expectedToken) {
1353
+ const channelId = header(headers, "x-goog-channel-id");
1354
+ const resourceId = header(headers, "x-goog-resource-id");
1355
+ const resourceState = header(headers, "x-goog-resource-state");
1356
+ if (!channelId || !resourceId || !resourceState) {
1357
+ return { error: "missing-headers" };
1358
+ }
1359
+ if (expectedToken && header(headers, "x-goog-channel-token") !== expectedToken) {
1360
+ return { error: "bad-token" };
1361
+ }
1362
+ return {
1363
+ type: "calendar.changed",
1364
+ channelId,
1365
+ resourceId,
1366
+ resourceState,
1367
+ messageNumber: Number(header(headers, "x-goog-message-number")) || 0
1368
+ };
1369
+ }
1370
+ function googleCalendarHookProvider(expectedToken) {
1371
+ return (input) => {
1372
+ const parsed = parseCalendarPushNotification(input.headers, expectedToken);
1373
+ if ("error" in parsed) return { error: parsed.error };
1374
+ if (parsed.resourceState === "sync") return { ack: true };
1375
+ return {
1376
+ event: "CAL_REMOTE_CHANGED",
1377
+ payload: {
1378
+ channelId: parsed.channelId,
1379
+ resourceId: parsed.resourceId,
1380
+ resourceState: parsed.resourceState
1381
+ }
1382
+ };
1383
+ };
1384
+ }
1385
+ var CalendarIntegration = class extends BaseIntegration {
1386
+ constructor(config) {
1387
+ super(config);
1388
+ const rawKey = config.env.GOOGLE_CALENDAR_SA_KEY;
1389
+ if (!rawKey) {
1390
+ throw new Error("GOOGLE_CALENDAR_SA_KEY not configured");
1391
+ }
1392
+ const keyJson = rawKey.trim().startsWith("{") ? rawKey : Buffer.from(rawKey, "base64").toString("utf8");
1393
+ const key = JSON.parse(keyJson);
1394
+ const subject = config.env.GOOGLE_CALENDAR_SUBJECT || void 0;
1395
+ const auth = new google.auth.JWT({
1396
+ email: key.client_email,
1397
+ key: key.private_key,
1398
+ scopes: ["https://www.googleapis.com/auth/calendar"],
1399
+ subject
1400
+ });
1401
+ this.client = google.calendar({ version: "v3", auth });
1402
+ this.defaultCalendarId = config.env.GOOGLE_CALENDAR_ID || "primary";
1403
+ this.logger.info("Calendar integration initialized", {
1404
+ delegated: Boolean(subject)
1405
+ });
1406
+ }
1407
+ async execute(action, params) {
1408
+ const validation = this.validateParams(action, params);
1409
+ if (!validation.valid) {
1410
+ return {
1411
+ success: false,
1412
+ error: {
1413
+ name: "IntegrationError",
1414
+ message: "Validation failed",
1415
+ code: "VALIDATION_ERROR",
1416
+ details: validation.errors
1417
+ },
1418
+ metadata: this.createMetadata(action, 0)
1419
+ };
1420
+ }
1421
+ const startTime = Date.now();
1422
+ let retries = 0;
1423
+ try {
1424
+ let data;
1425
+ switch (action) {
1426
+ case "listEvents":
1427
+ data = await this.executeWithRetry(() => this.listEvents(params));
1428
+ break;
1429
+ case "createEvent":
1430
+ data = await this.executeWithRetry(() => this.createEvent(params));
1431
+ break;
1432
+ case "updateEvent":
1433
+ data = await this.executeWithRetry(() => this.updateEvent(params));
1434
+ break;
1435
+ case "deleteEvent":
1436
+ data = await this.executeWithRetry(() => this.deleteEvent(params));
1437
+ break;
1438
+ case "freeBusy":
1439
+ data = await this.executeWithRetry(() => this.freeBusy(params));
1440
+ break;
1441
+ case "watch":
1442
+ data = await this.executeWithRetry(() => this.watch(params));
1443
+ break;
1444
+ case "stopWatch":
1445
+ data = await this.executeWithRetry(() => this.stopWatch(params));
1446
+ break;
1447
+ default:
1448
+ throw new Error(`Unknown action: ${action}`);
1449
+ }
1450
+ return {
1451
+ success: true,
1452
+ data,
1453
+ metadata: this.createMetadata(action, Date.now() - startTime, retries)
1454
+ };
1455
+ } catch (error) {
1456
+ return this.handleError(action, error);
1457
+ }
1458
+ }
1459
+ calendarId(params) {
1460
+ return params.calendarId || this.defaultCalendarId;
1461
+ }
1462
+ toEventTime(value) {
1463
+ return value.length === 10 ? { date: value } : { dateTime: value };
1464
+ }
1465
+ fromEventTime(time) {
1466
+ return time?.dateTime ?? time?.date ?? "";
1467
+ }
1468
+ async listEvents(params) {
1469
+ const { timeMin, timeMax, syncToken, maxResults } = params;
1470
+ const response = await this.client.events.list({
1471
+ calendarId: this.calendarId(params),
1472
+ // Incremental sync: a syncToken supersedes the window params (the API
1473
+ // rejects combining them).
1474
+ ...syncToken ? { syncToken } : {
1475
+ timeMin: timeMin || void 0,
1476
+ timeMax: timeMax || void 0,
1477
+ singleEvents: true,
1478
+ orderBy: "startTime"
1479
+ },
1480
+ maxResults: maxResults || 250
1481
+ });
1482
+ const items = response.data.items ?? [];
1483
+ return {
1484
+ events: items.map((event) => ({
1485
+ id: event.id ?? "",
1486
+ summary: event.summary ?? "",
1487
+ description: event.description ?? "",
1488
+ location: event.location ?? "",
1489
+ start: this.fromEventTime(event.start ?? void 0),
1490
+ end: this.fromEventTime(event.end ?? void 0),
1491
+ status: event.status ?? "",
1492
+ updated: event.updated ?? ""
1493
+ })),
1494
+ nextSyncToken: response.data.nextSyncToken ?? null
1495
+ };
1496
+ }
1497
+ resolveEnd(start, end, durationMinutes) {
1498
+ if (end) return end;
1499
+ if (start.length === 10) {
1500
+ const next = new Date(Date.parse(start) + 24 * 60 * 6e4);
1501
+ return next.toISOString().slice(0, 10);
1502
+ }
1503
+ if (durationMinutes && durationMinutes > 0) {
1504
+ return new Date(Date.parse(start) + durationMinutes * 6e4).toISOString();
1505
+ }
1506
+ throw new Error("createEvent requires `end` or a positive `durationMinutes`");
1507
+ }
1508
+ async createEvent(params) {
1509
+ const { summary, description, location, start, end, durationMinutes } = params;
1510
+ const response = await this.client.events.insert({
1511
+ calendarId: this.calendarId(params),
1512
+ requestBody: {
1513
+ summary,
1514
+ description: description || void 0,
1515
+ location: location || void 0,
1516
+ start: this.toEventTime(start),
1517
+ end: this.toEventTime(
1518
+ this.resolveEnd(start, end || void 0, durationMinutes || void 0)
1519
+ )
1520
+ }
1521
+ });
1522
+ return {
1523
+ id: response.data.id ?? "",
1524
+ status: response.data.status ?? "",
1525
+ htmlLink: response.data.htmlLink ?? ""
1526
+ };
1527
+ }
1528
+ async updateEvent(params) {
1529
+ const { eventId, summary, description, location, start, end } = params;
1530
+ const requestBody = {};
1531
+ if (typeof summary === "string") requestBody.summary = summary;
1532
+ if (typeof description === "string") requestBody.description = description;
1533
+ if (typeof location === "string") requestBody.location = location;
1534
+ if (typeof start === "string" && start) requestBody.start = this.toEventTime(start);
1535
+ if (typeof end === "string" && end) requestBody.end = this.toEventTime(end);
1536
+ const response = await this.client.events.patch({
1537
+ calendarId: this.calendarId(params),
1538
+ eventId,
1539
+ requestBody
1540
+ });
1541
+ return {
1542
+ id: response.data.id ?? "",
1543
+ status: response.data.status ?? ""
1544
+ };
1545
+ }
1546
+ async deleteEvent(params) {
1547
+ const { eventId } = params;
1548
+ await this.client.events.delete({
1549
+ calendarId: this.calendarId(params),
1550
+ eventId
1551
+ });
1552
+ return { id: eventId, deleted: true };
1553
+ }
1554
+ async freeBusy(params) {
1555
+ const { timeMin, timeMax } = params;
1556
+ const id = this.calendarId(params);
1557
+ const response = await this.client.freebusy.query({
1558
+ requestBody: {
1559
+ timeMin,
1560
+ timeMax,
1561
+ items: [{ id }]
1562
+ }
1563
+ });
1564
+ const busy = response.data.calendars?.[id]?.busy ?? [];
1565
+ return {
1566
+ busy: busy.map((slot) => ({ start: slot.start ?? "", end: slot.end ?? "" }))
1567
+ };
1568
+ }
1569
+ async watch(params) {
1570
+ const { channelId, address, ttlSeconds } = params;
1571
+ const response = await this.client.events.watch({
1572
+ calendarId: this.calendarId(params),
1573
+ requestBody: {
1574
+ id: channelId,
1575
+ type: "web_hook",
1576
+ address,
1577
+ params: ttlSeconds ? { ttl: String(ttlSeconds) } : void 0
1578
+ }
1579
+ });
1580
+ return {
1581
+ channelId: response.data.id ?? channelId,
1582
+ resourceId: response.data.resourceId ?? "",
1583
+ expiration: response.data.expiration ?? ""
1584
+ };
1585
+ }
1586
+ async stopWatch(params) {
1587
+ const { channelId, resourceId } = params;
1588
+ await this.client.channels.stop({
1589
+ requestBody: {
1590
+ id: channelId,
1591
+ resourceId
1592
+ }
1593
+ });
1594
+ return { stopped: true };
1595
+ }
1596
+ };
1597
+ registerIntegration("calendar", CalendarIntegration);
1598
+ function decodeContent(content) {
1599
+ const dataUrlMatch = /^data:([^;,]+)?(;base64)?,(.*)$/s.exec(content);
1600
+ if (dataUrlMatch) {
1601
+ const [, mime, isB64, body] = dataUrlMatch;
1602
+ const bytes = isB64 ? Buffer.from(body, "base64") : Buffer.from(decodeURIComponent(body), "utf8");
1603
+ return { bytes, contentType: mime || null };
1604
+ }
1605
+ return { bytes: Buffer.from(content, "utf8"), contentType: null };
1606
+ }
1607
+ var DriveIntegration = class extends BaseIntegration {
1608
+ constructor(config) {
1609
+ super(config);
1610
+ const rawKey = config.env.GOOGLE_DRIVE_SA_KEY;
1611
+ if (!rawKey) {
1612
+ throw new Error("GOOGLE_DRIVE_SA_KEY not configured");
1613
+ }
1614
+ const keyJson = rawKey.trim().startsWith("{") ? rawKey : Buffer.from(rawKey, "base64").toString("utf8");
1615
+ const key = JSON.parse(keyJson);
1616
+ const subject = config.env.GOOGLE_DRIVE_SUBJECT || void 0;
1617
+ const auth = new google.auth.JWT({
1618
+ email: key.client_email,
1619
+ key: key.private_key,
1620
+ scopes: ["https://www.googleapis.com/auth/drive"],
1621
+ subject
1622
+ });
1623
+ this.client = google.drive({ version: "v3", auth });
1624
+ this.logger.info("Drive integration initialized", { delegated: Boolean(subject) });
1625
+ }
1626
+ async execute(action, params) {
1627
+ const validation = this.validateParams(action, params);
1628
+ if (!validation.valid) {
1629
+ return {
1630
+ success: false,
1631
+ error: {
1632
+ name: "IntegrationError",
1633
+ message: "Validation failed",
1634
+ code: "VALIDATION_ERROR",
1635
+ details: validation.errors
1636
+ },
1637
+ metadata: this.createMetadata(action, 0)
1638
+ };
1639
+ }
1640
+ const startTime = Date.now();
1641
+ try {
1642
+ let data;
1643
+ switch (action) {
1644
+ case "listFiles":
1645
+ data = await this.executeWithRetry(() => this.listFiles(params));
1646
+ break;
1647
+ case "getFile":
1648
+ data = await this.executeWithRetry(() => this.getFile(params));
1649
+ break;
1650
+ case "uploadFile":
1651
+ data = await this.executeWithRetry(() => this.uploadFile(params));
1652
+ break;
1653
+ case "createFolder":
1654
+ data = await this.executeWithRetry(() => this.createFolder(params));
1655
+ break;
1656
+ case "shareFile":
1657
+ data = await this.executeWithRetry(() => this.shareFile(params));
1658
+ break;
1659
+ default:
1660
+ throw new Error(`Unknown action: ${action}`);
1661
+ }
1662
+ return {
1663
+ success: true,
1664
+ data,
1665
+ metadata: this.createMetadata(action, Date.now() - startTime)
1666
+ };
1667
+ } catch (error) {
1668
+ return this.handleError(action, error);
1669
+ }
1670
+ }
1671
+ async listFiles(params) {
1672
+ const { folderId, query, maxResults } = params;
1673
+ const clauses = ["trashed = false"];
1674
+ if (folderId) clauses.push(`'${String(folderId).replace(/'/g, "\\'")}' in parents`);
1675
+ if (query) clauses.push(String(query));
1676
+ const response = await this.client.files.list({
1677
+ q: clauses.join(" and "),
1678
+ pageSize: maxResults || 100,
1679
+ fields: "files(id, name, mimeType, size, modifiedTime, webViewLink)"
1680
+ });
1681
+ return {
1682
+ files: (response.data.files ?? []).map((file) => ({
1683
+ id: file.id ?? "",
1684
+ name: file.name ?? "",
1685
+ mimeType: file.mimeType ?? "",
1686
+ size: Number(file.size ?? 0),
1687
+ modifiedTime: file.modifiedTime ?? "",
1688
+ webViewLink: file.webViewLink ?? ""
1689
+ }))
1690
+ };
1691
+ }
1692
+ async getFile(params) {
1693
+ const fileId = params.fileId;
1694
+ const meta = await this.client.files.get({
1695
+ fileId,
1696
+ fields: "id, name, mimeType, size"
1697
+ });
1698
+ const content = await this.client.files.get(
1699
+ { fileId, alt: "media" },
1700
+ { responseType: "arraybuffer" }
1701
+ );
1702
+ const bytes = Buffer.from(content.data);
1703
+ return {
1704
+ id: meta.data.id ?? fileId,
1705
+ name: meta.data.name ?? "",
1706
+ mimeType: meta.data.mimeType ?? "application/octet-stream",
1707
+ content: bytes.toString("base64"),
1708
+ size: bytes.length
1709
+ };
1710
+ }
1711
+ async uploadFile(params) {
1712
+ const { name, content, mimeType, folderId } = params;
1713
+ const { bytes, contentType } = decodeContent(content);
1714
+ const response = await this.client.files.create({
1715
+ requestBody: {
1716
+ name,
1717
+ parents: folderId ? [folderId] : void 0
1718
+ },
1719
+ media: {
1720
+ mimeType: mimeType || contentType || "application/octet-stream",
1721
+ body: Readable.from(bytes)
1722
+ },
1723
+ fields: "id, name, webViewLink"
1724
+ });
1725
+ return {
1726
+ id: response.data.id ?? "",
1727
+ name: response.data.name ?? name,
1728
+ webViewLink: response.data.webViewLink ?? ""
1729
+ };
1730
+ }
1731
+ async createFolder(params) {
1732
+ const { name, parentId } = params;
1733
+ const response = await this.client.files.create({
1734
+ requestBody: {
1735
+ name,
1736
+ mimeType: "application/vnd.google-apps.folder",
1737
+ parents: parentId ? [parentId] : void 0
1738
+ },
1739
+ fields: "id, name"
1740
+ });
1741
+ return { id: response.data.id ?? "", name: response.data.name ?? name };
1742
+ }
1743
+ async shareFile(params) {
1744
+ const { fileId, email, role } = params;
1745
+ const response = await this.client.permissions.create({
1746
+ fileId,
1747
+ requestBody: {
1748
+ type: "user",
1749
+ role: role || "reader",
1750
+ emailAddress: email
1751
+ },
1752
+ fields: "id"
1753
+ });
1754
+ return { shared: true, permissionId: response.data.id ?? "" };
1755
+ }
1756
+ };
1757
+ registerIntegration("drive", DriveIntegration);
1758
+
1759
+ // src/integrations/metaAds/index.ts
1760
+ var GRAPH_BASE = "https://graph.facebook.com/v21.0";
1761
+ var MetaAdsIntegration = class extends BaseIntegration {
1762
+ constructor(config) {
1763
+ super(config);
1764
+ this.accessToken = config.env.META_ACCESS_TOKEN || "";
1765
+ if (!this.accessToken) {
1766
+ throw new Error("META_ACCESS_TOKEN not configured");
1767
+ }
1768
+ this.defaultAccountId = config.env.META_AD_ACCOUNT_ID || "";
1769
+ this.logger.info("Meta Ads integration initialized");
1770
+ }
1771
+ async execute(action, params) {
1772
+ const validation = this.validateParams(action, params);
1773
+ if (!validation.valid) {
1774
+ return {
1775
+ success: false,
1776
+ error: {
1777
+ name: "IntegrationError",
1778
+ message: "Validation failed",
1779
+ code: "VALIDATION_ERROR",
1780
+ details: validation.errors
1781
+ },
1782
+ metadata: this.createMetadata(action, 0)
1783
+ };
1784
+ }
1785
+ const startTime = Date.now();
1786
+ try {
1787
+ let data;
1788
+ switch (action) {
1789
+ case "getSpend":
1790
+ data = await this.executeWithRetry(() => this.getSpend(params));
1791
+ break;
1792
+ case "listCampaigns":
1793
+ data = await this.executeWithRetry(() => this.listCampaigns(params));
1794
+ break;
1795
+ default:
1796
+ throw new Error(`Unknown action: ${action}`);
1797
+ }
1798
+ return {
1799
+ success: true,
1800
+ data,
1801
+ metadata: this.createMetadata(action, Date.now() - startTime)
1802
+ };
1803
+ } catch (error) {
1804
+ return this.handleError(action, error);
1805
+ }
1806
+ }
1807
+ accountId(params) {
1808
+ const id = params.accountId || this.defaultAccountId;
1809
+ if (!id) {
1810
+ throw new Error("No ad account: pass `accountId` or set META_AD_ACCOUNT_ID");
1811
+ }
1812
+ return id.startsWith("act_") ? id : `act_${id}`;
1813
+ }
1814
+ async graphGet(path, query) {
1815
+ const url = new URL(`${GRAPH_BASE}/${path}`);
1816
+ for (const [key, value] of Object.entries(query)) {
1817
+ url.searchParams.set(key, value);
1818
+ }
1819
+ url.searchParams.set("access_token", this.accessToken);
1820
+ const response = await fetch(url, { signal: AbortSignal.timeout(15e3) });
1821
+ if (response.status >= 500) {
1822
+ throw new Error(`Meta Graph API returned ${response.status}`);
1823
+ }
1824
+ const body = await response.json();
1825
+ if (!response.ok) {
1826
+ throw new Error(`Meta Graph API error: ${body.error?.message ?? response.status}`);
1827
+ }
1828
+ return body.data ?? body;
1829
+ }
1830
+ async getSpend(params) {
1831
+ const { since, until } = params;
1832
+ const rows = await this.graphGet(`${this.accountId(params)}/insights`, {
1833
+ fields: "spend,impressions,clicks,account_currency",
1834
+ time_range: JSON.stringify({ since, until }),
1835
+ level: "account"
1836
+ });
1837
+ const row = Array.isArray(rows) ? rows[0] : void 0;
1838
+ return {
1839
+ spend: Number(row?.spend ?? 0),
1840
+ currency: row?.account_currency ?? "",
1841
+ impressions: Number(row?.impressions ?? 0),
1842
+ clicks: Number(row?.clicks ?? 0)
1843
+ };
1844
+ }
1845
+ async listCampaigns(params) {
1846
+ const { status } = params;
1847
+ const rows = await this.graphGet(`${this.accountId(params)}/campaigns`, {
1848
+ fields: "id,name,status,daily_budget",
1849
+ ...status ? { effective_status: JSON.stringify([status]) } : {}
1850
+ });
1851
+ return {
1852
+ campaigns: (Array.isArray(rows) ? rows : []).map((row) => ({
1853
+ id: row.id ?? "",
1854
+ name: row.name ?? "",
1855
+ status: row.status ?? "",
1856
+ // Meta reports budgets in minor units (cents).
1857
+ dailyBudget: Number(row.daily_budget ?? 0) / 100
1858
+ }))
1859
+ };
1860
+ }
1861
+ };
1862
+ registerIntegration("metaAds", MetaAdsIntegration);
1863
+
1864
+ // src/integrations/accounting/index.ts
1865
+ var AccountingIntegration = class extends BaseIntegration {
1866
+ constructor(config) {
1867
+ super(config);
1868
+ this.logger.info("Accounting integration initialized (generic CSV export)");
1869
+ }
1870
+ async execute(action, params) {
1871
+ const validation = this.validateParams(action, params);
1872
+ if (!validation.valid) {
1873
+ return {
1874
+ success: false,
1875
+ error: {
1876
+ name: "IntegrationError",
1877
+ message: "Validation failed",
1878
+ code: "VALIDATION_ERROR",
1879
+ details: validation.errors
1880
+ },
1881
+ metadata: this.createMetadata(action, 0)
1882
+ };
1883
+ }
1884
+ const startTime = Date.now();
1885
+ try {
1886
+ let data;
1887
+ switch (action) {
1888
+ case "exportInvoices":
1889
+ data = this.exportRows(params.invoices, INVOICE_COLUMNS, "invoices");
1890
+ break;
1891
+ case "exportJournal":
1892
+ data = this.exportRows(params.entries, JOURNAL_COLUMNS, "journal");
1893
+ break;
1894
+ default:
1895
+ throw new Error(`Unknown action: ${action}`);
1896
+ }
1897
+ return {
1898
+ success: true,
1899
+ data,
1900
+ metadata: this.createMetadata(action, Date.now() - startTime)
1901
+ };
1902
+ } catch (error) {
1903
+ return this.handleError(action, error);
1904
+ }
1905
+ }
1906
+ exportRows(rowsValue, columns, kind) {
1907
+ if (!Array.isArray(rowsValue)) {
1908
+ throw new Error(`${kind} export requires an array of rows`);
1909
+ }
1910
+ const lines = [columns.join(",")];
1911
+ for (const row of rowsValue) {
1912
+ if (row === null || typeof row !== "object" || Array.isArray(row) || row instanceof Date) {
1913
+ throw new Error(`${kind} export: every row must be an object`);
1914
+ }
1915
+ const record = row;
1916
+ lines.push(columns.map((column) => csvCell(record[column])).join(","));
1917
+ }
1918
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
1919
+ return {
1920
+ content: lines.join("\r\n") + "\r\n",
1921
+ filename: `${kind}-export-${stamp}.csv`,
1922
+ count: rowsValue.length
1923
+ };
1924
+ }
1925
+ };
1926
+ var INVOICE_COLUMNS = ["id", "number", "customer", "issuedAt", "dueAt", "currency", "net", "tax", "gross", "status"];
1927
+ var JOURNAL_COLUMNS = ["date", "account", "description", "debit", "credit", "reference"];
1928
+ function csvCell(value) {
1929
+ if (value === void 0 || value === null) return "";
1930
+ const raw = value instanceof Date ? value.toISOString() : String(value);
1931
+ return /[",\r\n]/.test(raw) ? `"${raw.replace(/"/g, '""')}"` : raw;
1932
+ }
1933
+ registerIntegration("accounting", AccountingIntegration);
1934
+
1935
+ // src/integrations/banking/index.ts
1936
+ var GC_BASE = "https://bankaccountdata.gocardless.com/api/v2";
1937
+ var BankingIntegration = class extends BaseIntegration {
1938
+ constructor(config) {
1939
+ super(config);
1940
+ this.accessToken = null;
1941
+ this.accessTokenExpiresAt = 0;
1942
+ this.secretId = config.env.GOCARDLESS_SECRET_ID || "";
1943
+ this.secretKey = config.env.GOCARDLESS_SECRET_KEY || "";
1944
+ if (!this.secretId || !this.secretKey) {
1945
+ throw new Error("GOCARDLESS_SECRET_ID / GOCARDLESS_SECRET_KEY not configured");
1946
+ }
1947
+ this.logger.info("Banking integration initialized (GoCardless Bank Account Data)");
1948
+ }
1949
+ async execute(action, params) {
1950
+ const validation = this.validateParams(action, params);
1951
+ if (!validation.valid) {
1952
+ return {
1953
+ success: false,
1954
+ error: {
1955
+ name: "IntegrationError",
1956
+ message: "Validation failed",
1957
+ code: "VALIDATION_ERROR",
1958
+ details: validation.errors
1959
+ },
1960
+ metadata: this.createMetadata(action, 0)
1961
+ };
1962
+ }
1963
+ const startTime = Date.now();
1964
+ try {
1965
+ let data;
1966
+ switch (action) {
1967
+ case "createRequisition":
1968
+ data = await this.executeWithRetry(() => this.createRequisition(params));
1969
+ break;
1970
+ case "listAccounts":
1971
+ data = await this.executeWithRetry(() => this.listAccounts(params));
1972
+ break;
1973
+ case "listTransactions":
1974
+ data = await this.executeWithRetry(() => this.listTransactions(params));
1975
+ break;
1976
+ default:
1977
+ throw new Error(`Unknown action: ${action}`);
1978
+ }
1979
+ return {
1980
+ success: true,
1981
+ data,
1982
+ metadata: this.createMetadata(action, Date.now() - startTime)
1983
+ };
1984
+ } catch (error) {
1985
+ return this.handleError(action, error);
1986
+ }
966
1987
  }
967
- async sendViaSendGrid(to, subject, body, from) {
968
- const msg = {
969
- to,
970
- from,
971
- subject,
972
- html: body
973
- };
974
- const response = await sgMail.send(msg);
975
- return {
976
- id: response[0].headers["x-message-id"],
977
- status: "sent"
978
- };
1988
+ async token() {
1989
+ if (this.accessToken && Date.now() < this.accessTokenExpiresAt - 6e4) {
1990
+ return this.accessToken;
1991
+ }
1992
+ const response = await fetch(`${GC_BASE}/token/new/`, {
1993
+ method: "POST",
1994
+ headers: { "Content-Type": "application/json", Accept: "application/json" },
1995
+ body: JSON.stringify({ secret_id: this.secretId, secret_key: this.secretKey }),
1996
+ signal: AbortSignal.timeout(15e3)
1997
+ });
1998
+ if (!response.ok) {
1999
+ throw new Error(`GoCardless token request failed: ${response.status}`);
2000
+ }
2001
+ const body = await response.json();
2002
+ this.accessToken = body.access;
2003
+ this.accessTokenExpiresAt = Date.now() + body.access_expires * 1e3;
2004
+ return this.accessToken;
979
2005
  }
980
- async sendViaResend(to, subject, body, from) {
981
- if (!this.resendClient) {
982
- throw new Error("Resend client not initialized");
2006
+ async gcRequest(path, init2) {
2007
+ const response = await fetch(`${GC_BASE}${path}`, {
2008
+ method: init2?.method ?? "GET",
2009
+ headers: {
2010
+ Accept: "application/json",
2011
+ "Content-Type": "application/json",
2012
+ Authorization: `Bearer ${await this.token()}`
2013
+ },
2014
+ body: init2?.body === void 0 ? void 0 : JSON.stringify(init2.body),
2015
+ signal: AbortSignal.timeout(2e4)
2016
+ });
2017
+ if (response.status >= 500) {
2018
+ throw new Error(`GoCardless returned ${response.status}`);
983
2019
  }
984
- const response = await this.resendClient.emails.send({
985
- from,
986
- to,
987
- subject,
988
- html: body
2020
+ const body = await response.json();
2021
+ if (!response.ok) {
2022
+ throw new Error(`GoCardless error ${response.status}: ${JSON.stringify(body).slice(0, 300)}`);
2023
+ }
2024
+ return body;
2025
+ }
2026
+ async createRequisition(params) {
2027
+ const { institutionId, redirectUrl, reference } = params;
2028
+ const body = await this.gcRequest("/requisitions/", {
2029
+ method: "POST",
2030
+ body: {
2031
+ institution_id: institutionId,
2032
+ redirect: redirectUrl,
2033
+ reference: reference || void 0
2034
+ }
989
2035
  });
2036
+ return { requisitionId: body.id ?? "", link: body.link ?? "" };
2037
+ }
2038
+ async listAccounts(params) {
2039
+ const { requisitionId } = params;
2040
+ const body = await this.gcRequest(`/requisitions/${requisitionId}/`);
2041
+ return { accounts: body.accounts ?? [] };
2042
+ }
2043
+ async listTransactions(params) {
2044
+ const { accountId, dateFrom, dateTo } = params;
2045
+ const query = new URLSearchParams();
2046
+ if (dateFrom) query.set("date_from", dateFrom);
2047
+ if (dateTo) query.set("date_to", dateTo);
2048
+ const suffix = query.size > 0 ? `?${query.toString()}` : "";
2049
+ const body = await this.gcRequest(`/accounts/${accountId}/transactions/${suffix}`);
990
2050
  return {
991
- id: response.data?.id,
992
- status: "sent"
2051
+ transactions: (body.transactions?.booked ?? []).map((tx) => ({
2052
+ id: tx.transactionId ?? tx.internalTransactionId ?? "",
2053
+ amount: Number(tx.transactionAmount?.amount ?? 0),
2054
+ currency: tx.transactionAmount?.currency ?? "",
2055
+ date: tx.bookingDate ?? "",
2056
+ description: tx.remittanceInformationUnstructured ?? "",
2057
+ counterparty: tx.creditorName ?? tx.debtorName ?? ""
2058
+ }))
993
2059
  };
994
2060
  }
995
2061
  };
996
- registerIntegration("email", EmailIntegration);
997
- var WebhookIntegration = class extends BaseIntegration {
2062
+ registerIntegration("banking", BankingIntegration);
2063
+
2064
+ // src/integrations/esign/index.ts
2065
+ var EsignIntegration = class extends BaseIntegration {
998
2066
  constructor(config) {
999
2067
  super(config);
1000
- this.signingSecret = config.env.WEBHOOK_SIGNING_SECRET || "";
1001
- this.timeoutMs = Number(config.env.WEBHOOK_TIMEOUT_MS) || 1e4;
1002
- this.logger.info("Webhook integration initialized");
2068
+ if (!config.env.DOCUSIGN_BASE_URL || !config.env.DOCUSIGN_ACCESS_TOKEN) {
2069
+ throw new Error("DOCUSIGN_BASE_URL / DOCUSIGN_ACCESS_TOKEN not configured");
2070
+ }
2071
+ this.logger.info("E-sign integration initialized (DocuSign)");
1003
2072
  }
1004
2073
  async execute(action, params) {
1005
2074
  const validation = this.validateParams(action, params);
@@ -1016,12 +2085,17 @@ var WebhookIntegration = class extends BaseIntegration {
1016
2085
  };
1017
2086
  }
1018
2087
  const startTime = Date.now();
1019
- let retries = 0;
1020
2088
  try {
1021
2089
  let data;
1022
2090
  switch (action) {
1023
- case "send":
1024
- data = await this.executeWithRetry(() => this.send(params));
2091
+ case "sendEnvelope":
2092
+ data = await this.executeWithRetry(() => this.sendEnvelope(params));
2093
+ break;
2094
+ case "getEnvelopeStatus":
2095
+ data = await this.executeWithRetry(() => this.getEnvelopeStatus(params));
2096
+ break;
2097
+ case "downloadDocument":
2098
+ data = await this.executeWithRetry(() => this.downloadDocument(params));
1025
2099
  break;
1026
2100
  default:
1027
2101
  throw new Error(`Unknown action: ${action}`);
@@ -1029,46 +2103,81 @@ var WebhookIntegration = class extends BaseIntegration {
1029
2103
  return {
1030
2104
  success: true,
1031
2105
  data,
1032
- metadata: this.createMetadata(action, Date.now() - startTime, retries)
2106
+ metadata: this.createMetadata(action, Date.now() - startTime)
1033
2107
  };
1034
2108
  } catch (error) {
1035
2109
  return this.handleError(action, error);
1036
2110
  }
1037
2111
  }
1038
- async send(params) {
1039
- const { url, event, payload, secret } = params;
1040
- const body = JSON.stringify({
1041
- event,
1042
- payload: payload ?? {},
1043
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
2112
+ async dsRequest(path, init2) {
2113
+ const base = this.config.env.DOCUSIGN_BASE_URL.replace(/\/$/, "");
2114
+ const response = await fetch(`${base}${path}`, {
2115
+ method: init2?.method ?? "GET",
2116
+ headers: {
2117
+ Accept: init2?.raw ? "application/pdf" : "application/json",
2118
+ "Content-Type": "application/json",
2119
+ Authorization: `Bearer ${this.config.env.DOCUSIGN_ACCESS_TOKEN}`
2120
+ },
2121
+ body: init2?.body === void 0 ? void 0 : JSON.stringify(init2.body),
2122
+ signal: AbortSignal.timeout(3e4)
1044
2123
  });
1045
- const headers = {
1046
- "Content-Type": "application/json",
1047
- "X-Almadar-Event": event
1048
- };
1049
- const signingSecret = secret || this.signingSecret;
1050
- if (signingSecret) {
1051
- headers["X-Almadar-Signature"] = "sha256=" + createHmac("sha256", signingSecret).update(body).digest("hex");
2124
+ if (response.status >= 500) {
2125
+ throw new Error(`DocuSign returned ${response.status}`);
1052
2126
  }
1053
- this.logger.debug("Sending webhook", { url: String(url), event: String(event) });
1054
- const startTime = Date.now();
1055
- const response = await fetch(url, {
2127
+ if (!response.ok) {
2128
+ const detail = await response.text();
2129
+ throw new Error(`DocuSign error ${response.status}: ${detail.slice(0, 300)}`);
2130
+ }
2131
+ if (init2?.raw) {
2132
+ return Buffer.from(await response.arrayBuffer());
2133
+ }
2134
+ return response.json();
2135
+ }
2136
+ async sendEnvelope(params) {
2137
+ const { recipientEmail, recipientName, documentName, documentContent, emailSubject } = params;
2138
+ const rawContent = documentContent;
2139
+ const base64 = rawContent.startsWith("data:") ? rawContent.slice(rawContent.indexOf(",") + 1) : rawContent;
2140
+ const body = await this.dsRequest("/envelopes", {
1056
2141
  method: "POST",
1057
- headers,
1058
- body,
1059
- signal: AbortSignal.timeout(this.timeoutMs)
2142
+ body: {
2143
+ emailSubject: emailSubject || `Please sign: ${documentName}`,
2144
+ status: "sent",
2145
+ documents: [
2146
+ {
2147
+ documentBase64: base64,
2148
+ name: documentName,
2149
+ fileExtension: String(documentName).split(".").pop() || "pdf",
2150
+ documentId: "1"
2151
+ }
2152
+ ],
2153
+ recipients: {
2154
+ signers: [
2155
+ {
2156
+ email: recipientEmail,
2157
+ name: recipientName,
2158
+ recipientId: "1",
2159
+ routingOrder: "1"
2160
+ }
2161
+ ]
2162
+ }
2163
+ }
1060
2164
  });
1061
- if (response.status >= 500) {
1062
- throw new Error(`Webhook endpoint returned ${response.status}`);
1063
- }
1064
- return {
1065
- status: response.status,
1066
- ok: response.ok,
1067
- durationMs: Date.now() - startTime
1068
- };
2165
+ return { envelopeId: body.envelopeId ?? "", status: body.status ?? "sent" };
2166
+ }
2167
+ async getEnvelopeStatus(params) {
2168
+ const { envelopeId } = params;
2169
+ const body = await this.dsRequest(`/envelopes/${envelopeId}`);
2170
+ return { status: body.status ?? "", completedAt: body.completedDateTime ?? "" };
2171
+ }
2172
+ async downloadDocument(params) {
2173
+ const { envelopeId } = params;
2174
+ const bytes = await this.dsRequest(`/envelopes/${envelopeId}/documents/combined`, {
2175
+ raw: true
2176
+ });
2177
+ return { content: bytes.toString("base64"), documentName: `envelope-${envelopeId}.pdf` };
1069
2178
  }
1070
2179
  };
1071
- registerIntegration("webhook", WebhookIntegration);
2180
+ registerIntegration("esign", EsignIntegration);
1072
2181
  var LLMIntegration = class extends BaseIntegration {
1073
2182
  constructor(config) {
1074
2183
  super(config);
@@ -2639,25 +3748,35 @@ var OtelIntegration = class extends BaseIntegration {
2639
3748
  }
2640
3749
  };
2641
3750
  registerIntegration("otel", OtelIntegration);
2642
-
2643
- // src/integrations/oauth/index.ts
2644
3751
  var PROVIDER_AUTH_URLS = {
2645
3752
  google: "https://accounts.google.com/o/oauth2/v2/auth",
2646
3753
  github: "https://github.com/login/oauth/authorize",
2647
3754
  auth0: "https://auth.example.com/authorize"
2648
3755
  };
3756
+ var PROVIDER_ISSUERS = {
3757
+ google: "https://accounts.google.com"
3758
+ };
2649
3759
  var OAuthIntegration = class extends BaseIntegration {
2650
3760
  constructor(config) {
2651
3761
  super(config);
2652
- /** Maps state token -> provider for pending authorization flows */
3762
+ /** Maps state token -> provider for pending MOCK authorization flows */
2653
3763
  this.states = /* @__PURE__ */ new Map();
2654
- /** Maps access token -> token set */
3764
+ /** Maps access token -> token set (mock backend) */
2655
3765
  this.tokens = /* @__PURE__ */ new Map();
2656
- /** Maps refresh token -> access token for refresh lookups */
3766
+ /** Maps refresh token -> access token for refresh lookups (mock backend) */
2657
3767
  this.refreshIndex = /* @__PURE__ */ new Map();
2658
3768
  /** Maps access token -> mock user session */
2659
3769
  this.sessions = /* @__PURE__ */ new Map();
2660
- this.logger.info("OAuth integration initialized (mock backend)");
3770
+ /** Maps state -> pending OIDC authorization (real backend) */
3771
+ this.pending = /* @__PURE__ */ new Map();
3772
+ /** Maps access token -> ID-token subject, for userinfo subject checks */
3773
+ this.subjects = /* @__PURE__ */ new Map();
3774
+ /** Discovered issuer configurations, keyed by issuer URL */
3775
+ this.discovered = /* @__PURE__ */ new Map();
3776
+ this.real = config.env.OAUTH_MODE !== "mock" && Boolean(config.env.OAUTH_CLIENT_ID) && Boolean(config.env.OAUTH_CLIENT_SECRET);
3777
+ this.logger.info(
3778
+ this.real ? "OAuth integration initialized (OIDC backend via openid-client)" : "OAuth integration initialized (mock backend)"
3779
+ );
2661
3780
  }
2662
3781
  async execute(action, params) {
2663
3782
  const validation = this.validateParams(action, params);
@@ -2678,19 +3797,19 @@ var OAuthIntegration = class extends BaseIntegration {
2678
3797
  let data;
2679
3798
  switch (action) {
2680
3799
  case "authorize":
2681
- data = await this.executeWithRetry(() => this.authorize(params));
3800
+ data = await this.executeWithRetry(() => this.real ? this.oidcAuthorize(params) : this.authorize(params));
2682
3801
  break;
2683
3802
  case "token":
2684
- data = await this.executeWithRetry(() => this.token(params));
3803
+ data = await this.executeWithRetry(() => this.real ? this.oidcToken(params) : this.token(params));
2685
3804
  break;
2686
3805
  case "refresh":
2687
- data = await this.executeWithRetry(() => this.refresh(params));
3806
+ data = await this.executeWithRetry(() => this.real ? this.oidcRefresh(params) : this.refresh(params));
2688
3807
  break;
2689
3808
  case "revoke":
2690
- data = await this.executeWithRetry(() => this.revoke(params));
3809
+ data = await this.executeWithRetry(() => this.real ? this.oidcRevoke(params) : this.revoke(params));
2691
3810
  break;
2692
3811
  case "userinfo":
2693
- data = await this.executeWithRetry(() => this.userinfo(params));
3812
+ data = await this.executeWithRetry(() => this.real ? this.oidcUserinfo(params) : this.userinfo(params));
2694
3813
  break;
2695
3814
  default:
2696
3815
  throw new Error(`Unknown action: ${action}`);
@@ -2705,7 +3824,119 @@ var OAuthIntegration = class extends BaseIntegration {
2705
3824
  }
2706
3825
  }
2707
3826
  // ---------------------------------------------------------------------------
2708
- // Helpers
3827
+ // OIDC backend (openid-client)
3828
+ // ---------------------------------------------------------------------------
3829
+ issuerFor(provider) {
3830
+ const configured = this.config.env.OIDC_ISSUER_URL;
3831
+ if (configured) return configured;
3832
+ const issuer = PROVIDER_ISSUERS[provider];
3833
+ if (!issuer) {
3834
+ throw new Error(
3835
+ `Provider "${provider}" has no OIDC issuer \u2014 set OIDC_ISSUER_URL to an OIDC-compliant issuer, or use OAUTH_MODE=mock`
3836
+ );
3837
+ }
3838
+ return issuer;
3839
+ }
3840
+ async configurationFor(provider) {
3841
+ const issuer = this.issuerFor(provider);
3842
+ const cached = this.discovered.get(issuer);
3843
+ if (cached) return cached;
3844
+ const configuration = await oidc.discovery(
3845
+ new URL(issuer),
3846
+ this.config.env.OAUTH_CLIENT_ID,
3847
+ this.config.env.OAUTH_CLIENT_SECRET
3848
+ );
3849
+ this.discovered.set(issuer, configuration);
3850
+ return configuration;
3851
+ }
3852
+ async oidcAuthorize(params) {
3853
+ const provider = params.provider;
3854
+ const scopes = params.scopes;
3855
+ const redirectUri = params.redirectUri || this.config.env.OAUTH_REDIRECT_URI;
3856
+ const configuration = await this.configurationFor(provider);
3857
+ const state = oidc.randomState();
3858
+ const pkceVerifier = oidc.randomPKCECodeVerifier();
3859
+ const codeChallenge = await oidc.calculatePKCECodeChallenge(pkceVerifier);
3860
+ const parameters = {
3861
+ redirect_uri: redirectUri,
3862
+ scope: scopes.join(" "),
3863
+ state,
3864
+ code_challenge: codeChallenge,
3865
+ code_challenge_method: "S256"
3866
+ };
3867
+ if (provider === "google") {
3868
+ parameters.access_type = "offline";
3869
+ parameters.prompt = "consent";
3870
+ }
3871
+ const authUrl = oidc.buildAuthorizationUrl(configuration, parameters);
3872
+ this.pending.set(state, { provider, redirectUri, pkceVerifier });
3873
+ return { authUrl: authUrl.toString(), state };
3874
+ }
3875
+ async oidcToken(params) {
3876
+ const code = params.code;
3877
+ const state = params.state;
3878
+ const pendingAuth = this.pending.get(state);
3879
+ if (!pendingAuth) {
3880
+ throw new Error(`Invalid or expired state token: ${state}`);
3881
+ }
3882
+ this.pending.delete(state);
3883
+ const configuration = await this.configurationFor(pendingAuth.provider);
3884
+ const callbackUrl = new URL(pendingAuth.redirectUri);
3885
+ callbackUrl.searchParams.set("code", code);
3886
+ callbackUrl.searchParams.set("state", state);
3887
+ const tokens = await oidc.authorizationCodeGrant(configuration, callbackUrl, {
3888
+ expectedState: state,
3889
+ pkceCodeVerifier: pendingAuth.pkceVerifier
3890
+ });
3891
+ const claims = tokens.claims();
3892
+ if (claims?.sub) {
3893
+ this.subjects.set(tokens.access_token, claims.sub);
3894
+ }
3895
+ return {
3896
+ accessToken: tokens.access_token,
3897
+ refreshToken: tokens.refresh_token ?? "",
3898
+ expiresIn: tokens.expires_in ?? 3600,
3899
+ tokenType: "bearer"
3900
+ };
3901
+ }
3902
+ async oidcRefresh(params) {
3903
+ const refreshToken = params.refreshToken;
3904
+ const configuration = await this.configurationFor("google");
3905
+ const tokens = await oidc.refreshTokenGrant(configuration, refreshToken);
3906
+ const claims = tokens.claims();
3907
+ if (claims?.sub) {
3908
+ this.subjects.set(tokens.access_token, claims.sub);
3909
+ }
3910
+ return {
3911
+ accessToken: tokens.access_token,
3912
+ expiresIn: tokens.expires_in ?? 3600
3913
+ };
3914
+ }
3915
+ async oidcRevoke(params) {
3916
+ const token = params.token;
3917
+ const configuration = await this.configurationFor("google");
3918
+ await oidc.tokenRevocation(configuration, token);
3919
+ this.subjects.delete(token);
3920
+ return { revoked: true };
3921
+ }
3922
+ async oidcUserinfo(params) {
3923
+ const accessToken = params.accessToken;
3924
+ const configuration = await this.configurationFor("google");
3925
+ const subject = this.subjects.get(accessToken);
3926
+ const info = await oidc.fetchUserInfo(
3927
+ configuration,
3928
+ accessToken,
3929
+ subject ?? oidc.skipSubjectCheck
3930
+ );
3931
+ return {
3932
+ sub: info.sub,
3933
+ email: typeof info.email === "string" ? info.email : "",
3934
+ name: typeof info.name === "string" ? info.name : "",
3935
+ picture: typeof info.picture === "string" ? info.picture : ""
3936
+ };
3937
+ }
3938
+ // ---------------------------------------------------------------------------
3939
+ // Mock backend helpers
2709
3940
  // ---------------------------------------------------------------------------
2710
3941
  /** Generate a random hex token of the given byte length. */
2711
3942
  generateToken(bytes = 32) {
@@ -2726,7 +3957,7 @@ var OAuthIntegration = class extends BaseIntegration {
2726
3957
  };
2727
3958
  }
2728
3959
  // ---------------------------------------------------------------------------
2729
- // Actions
3960
+ // Mock backend actions
2730
3961
  // ---------------------------------------------------------------------------
2731
3962
  async authorize(params) {
2732
3963
  const provider = params.provider;
@@ -2835,19 +4066,333 @@ var OAuthIntegration = class extends BaseIntegration {
2835
4066
  };
2836
4067
  registerIntegration("oauth", OAuthIntegration);
2837
4068
 
2838
- // src/integrations/storage/index.ts
4069
+ // src/contracts.ts
4070
+ var serviceCredentials = {
4071
+ stripe: [
4072
+ { envVar: "STRIPE_SECRET_KEY", required: true, description: "Stripe secret API key" },
4073
+ { envVar: "STRIPE_WEBHOOK_SECRET", required: false, description: "Webhook endpoint signing secret" }
4074
+ ],
4075
+ youtube: [
4076
+ { envVar: "YOUTUBE_API_KEY", required: true, description: "YouTube Data API key" }
4077
+ ],
4078
+ twilio: [
4079
+ { envVar: "TWILIO_ACCOUNT_SID", required: true, description: "Twilio account SID" },
4080
+ { envVar: "TWILIO_AUTH_TOKEN", required: true, description: "Twilio auth token" },
4081
+ { envVar: "TWILIO_PHONE_NUMBER", required: false, description: "Default sender phone number" }
4082
+ ],
4083
+ email: [
4084
+ { envVar: "SENDGRID_API_KEY", required: false, description: "SendGrid API key (use this OR RESEND_API_KEY)" },
4085
+ { envVar: "RESEND_API_KEY", required: false, description: "Resend API key (use this OR SENDGRID_API_KEY)" },
4086
+ { envVar: "FROM_EMAIL", required: false, description: "Default sender email address" }
4087
+ ],
4088
+ webhook: [
4089
+ { envVar: "WEBHOOK_SIGNING_SECRET", required: false, description: "Default HMAC-SHA256 signing secret (per-call secret overrides)" },
4090
+ { envVar: "WEBHOOK_TIMEOUT_MS", required: false, description: "Per-request timeout (ms, default 10000)" }
4091
+ ],
4092
+ push: [
4093
+ { envVar: "VAPID_PUBLIC_KEY", required: true, description: "VAPID public key (web-push generate-vapid-keys)" },
4094
+ { envVar: "VAPID_PRIVATE_KEY", required: true, description: "VAPID private key" },
4095
+ { envVar: "VAPID_SUBJECT", required: true, description: "VAPID subject (mailto: or https: contact URI)" }
4096
+ ],
4097
+ calendar: [
4098
+ { envVar: "GOOGLE_CALENDAR_SA_KEY", required: true, description: "Google service-account key JSON (raw or base64) with calendar scope; store in Secret Manager, bind as env" },
4099
+ { envVar: "GOOGLE_CALENDAR_SUBJECT", required: false, description: "Workspace user to impersonate (domain-wide delegation); empty = act as the service account" },
4100
+ { envVar: "GOOGLE_CALENDAR_ID", required: false, description: "Default calendar id (default: primary)" }
4101
+ ],
4102
+ drive: [
4103
+ { envVar: "GOOGLE_DRIVE_SA_KEY", required: true, description: "Google service-account key JSON (raw or base64) with drive scope; store in Secret Manager, bind as env" },
4104
+ { envVar: "GOOGLE_DRIVE_SUBJECT", required: false, description: "Workspace user to impersonate (domain-wide delegation); empty = act as the service account" }
4105
+ ],
4106
+ metaAds: [
4107
+ { envVar: "META_ACCESS_TOKEN", required: true, description: "Meta Graph API access token (Marketing API, ads_read)" },
4108
+ { envVar: "META_AD_ACCOUNT_ID", required: false, description: "Default ad account id (act_\u2026); per-call accountId overrides" }
4109
+ ],
4110
+ accounting: [],
4111
+ banking: [
4112
+ { envVar: "GOCARDLESS_SECRET_ID", required: true, description: "GoCardless Bank Account Data secret id" },
4113
+ { envVar: "GOCARDLESS_SECRET_KEY", required: true, description: "GoCardless Bank Account Data secret key" }
4114
+ ],
4115
+ esign: [
4116
+ { envVar: "DOCUSIGN_BASE_URL", required: true, description: "DocuSign REST base (e.g. https://demo.docusign.net/restapi/v2.1/accounts/<accountId>)" },
4117
+ { envVar: "DOCUSIGN_ACCESS_TOKEN", required: true, description: "DocuSign OAuth access token (JWT grant rotation is the deployment concern)" }
4118
+ ],
4119
+ llm: [
4120
+ { envVar: "ANTHROPIC_API_KEY", required: false, description: "Anthropic API key (use this OR OPENAI_API_KEY)" },
4121
+ { envVar: "OPENAI_API_KEY", required: false, description: "OpenAI API key (use this OR ANTHROPIC_API_KEY)" }
4122
+ ],
4123
+ "llm-integration": [
4124
+ { envVar: "ANTHROPIC_API_KEY", required: false, description: "Anthropic API key" },
4125
+ { envVar: "OPENAI_API_KEY", required: false, description: "OpenAI API key" }
4126
+ ],
4127
+ ml: [
4128
+ { envVar: "MASAR_URL", required: true, description: "Base URL of the deployed model-serving orbital" },
4129
+ { envVar: "MASAR_ML_TRAIT", required: true, description: "Kebab-case trait name the serving orbital exposes its /events route under" }
4130
+ ],
4131
+ deepagent: [
4132
+ { envVar: "DEEPAGENT_API_URL", required: true, description: "DeepAgent server URL" },
4133
+ { envVar: "DEEPAGENT_API_KEY", required: false, description: "DeepAgent API key" }
4134
+ ],
4135
+ github: [
4136
+ { envVar: "GITHUB_TOKEN", required: true, description: "GitHub personal access token" },
4137
+ { envVar: "GITHUB_OWNER", required: false, description: "Default repository owner" },
4138
+ { envVar: "GITHUB_REPO", required: false, description: "Default repository name" }
4139
+ ],
4140
+ docker: [
4141
+ { envVar: "DOCKER_HOST", required: false, description: "Docker daemon host URL" }
4142
+ ],
4143
+ storage: [
4144
+ { envVar: "STORAGE_ACCESS_KEY_ID", required: true, description: "S3-compatible access key id" },
4145
+ { envVar: "STORAGE_SECRET_ACCESS_KEY", required: true, description: "S3-compatible secret access key" },
4146
+ { envVar: "STORAGE_BUCKET", required: true, description: "Default bucket name" },
4147
+ { envVar: "STORAGE_REGION", required: false, description: "Region (default us-east-1)" },
4148
+ { envVar: "STORAGE_ENDPOINT", required: false, description: "Custom S3-compatible endpoint (R2/MinIO/\u2026); empty = AWS S3" },
4149
+ { envVar: "STORAGE_PUBLIC_URL_BASE", required: false, description: "Base URL for public-acl object links; empty = endpoint-derived" }
4150
+ ],
4151
+ queue: [
4152
+ { envVar: "QUEUE_URL", required: true, description: "Message queue connection URL" }
4153
+ ],
4154
+ redis: [
4155
+ { envVar: "REDIS_URL", required: true, description: "Redis connection URL" }
4156
+ ],
4157
+ oauth: [
4158
+ { envVar: "OAUTH_CLIENT_ID", required: true, description: "OIDC client ID" },
4159
+ { envVar: "OAUTH_CLIENT_SECRET", required: true, description: "OIDC client secret" },
4160
+ { envVar: "OAUTH_REDIRECT_URI", required: false, description: "OIDC redirect URI (default per-call param)" },
4161
+ { envVar: "OIDC_ISSUER_URL", required: false, description: "OIDC issuer URL (default https://accounts.google.com)" }
4162
+ ],
4163
+ credentials: [
4164
+ { envVar: "ALMADAR_CREDENTIAL_MASTER_KEY", required: false, description: "AES-256 master key (64-char hex) enabling the hosted credential store \u2014 hold it alone in the platform secret store" }
4165
+ ],
4166
+ otel: [
4167
+ { envVar: "OTEL_EXPORTER_OTLP_ENDPOINT", required: true, description: "OpenTelemetry collector endpoint" },
4168
+ { envVar: "OTEL_SERVICE_NAME", required: false, description: "Service name for traces" }
4169
+ ],
4170
+ cli: [],
4171
+ // No fixed env vars — connection strings are resolved per-query from the
4172
+ // caller-supplied connectionRef, so credentials cannot be declared statically.
4173
+ database: [],
4174
+ wikimedia: [
4175
+ { envVar: "WIKIMEDIA_USER_AGENT", required: false, description: "Descriptive User-Agent for the Wikipedia API" },
4176
+ { envVar: "WIKIMEDIA_TIMEOUT_MS", required: false, description: "Per-request timeout (ms)" }
4177
+ ],
4178
+ iconify: [
4179
+ { envVar: "ICONIFY_USER_AGENT", required: false, description: "User-Agent for the Iconify API" },
4180
+ { envVar: "ICONIFY_TIMEOUT_MS", required: false, description: "Per-request timeout (ms)" }
4181
+ ],
4182
+ arxiv: [
4183
+ { envVar: "ARXIV_TIMEOUT_MS", required: false, description: "Per-request timeout (ms)" }
4184
+ ]
4185
+ };
4186
+ var serviceProbes = {
4187
+ calendar: { action: "listEvents", params: { maxResults: 1 } },
4188
+ drive: { action: "listFiles", params: { maxResults: 1 } },
4189
+ metaAds: { action: "listCampaigns", params: {} }
4190
+ };
4191
+
4192
+ // src/integrations/credentials/index.ts
4193
+ var ENV_VAR_NAME = /^[A-Z][A-Z0-9_]*$/;
4194
+ function isProbeService(service) {
4195
+ return service in serviceProbes;
4196
+ }
4197
+ var CredentialsIntegration = class extends BaseIntegration {
4198
+ constructor(config) {
4199
+ super(config);
4200
+ this.logger.info("Credentials integration initialized (tenant credential store surface)");
4201
+ }
4202
+ async execute(action, params) {
4203
+ const validation = this.validateParams(action, params);
4204
+ if (!validation.valid) {
4205
+ return {
4206
+ success: false,
4207
+ error: {
4208
+ name: "IntegrationError",
4209
+ message: "Validation failed",
4210
+ code: "VALIDATION_ERROR",
4211
+ details: validation.errors
4212
+ },
4213
+ metadata: this.createMetadata(action, 0)
4214
+ };
4215
+ }
4216
+ const startTime = Date.now();
4217
+ try {
4218
+ let data;
4219
+ switch (action) {
4220
+ case "list":
4221
+ data = this.list(typeof params.service === "string" ? params.service : void 0);
4222
+ break;
4223
+ case "set":
4224
+ data = await this.set(params.service, params.envVar, params.value);
4225
+ break;
4226
+ case "remove":
4227
+ data = await this.remove(params.service, params.envVar);
4228
+ break;
4229
+ case "test":
4230
+ data = await this.test(params.service);
4231
+ break;
4232
+ default:
4233
+ throw new Error(`Unknown action: ${action}`);
4234
+ }
4235
+ return {
4236
+ success: true,
4237
+ data,
4238
+ metadata: this.createMetadata(action, Date.now() - startTime)
4239
+ };
4240
+ } catch (error) {
4241
+ return this.handleError(action, error);
4242
+ }
4243
+ }
4244
+ declaredFor(service) {
4245
+ return serviceCredentials[service] ?? [];
4246
+ }
4247
+ assertSettable(service, envVar) {
4248
+ if (service === "database") {
4249
+ if (!ENV_VAR_NAME.test(envVar)) {
4250
+ throw new Error(`"${envVar}" is not a well-formed connection reference (expected an env-var name)`);
4251
+ }
4252
+ return;
4253
+ }
4254
+ const declared = this.declaredFor(service);
4255
+ if (declared.length === 0) {
4256
+ throw new Error(`Service "${service}" declares no credentials`);
4257
+ }
4258
+ if (!declared.some((c) => c.envVar === envVar)) {
4259
+ const valid = declared.map((c) => c.envVar).join(", ");
4260
+ throw new Error(`"${envVar}" is not a declared credential of "${service}" (declared: ${valid})`);
4261
+ }
4262
+ }
4263
+ list(serviceFilter) {
4264
+ const store = getInstalledCredentialStore();
4265
+ const stored = new Map((store?.entries() ?? []).map((e) => [`${e.service}\0${e.envVar}`, e]));
4266
+ const entries = [];
4267
+ for (const [service, declared] of Object.entries(serviceCredentials)) {
4268
+ if (serviceFilter && service !== serviceFilter) continue;
4269
+ for (const { envVar, required, description } of declared) {
4270
+ const fromStore = stored.get(`${service}\0${envVar}`);
4271
+ stored.delete(`${service}\0${envVar}`);
4272
+ const fromEnv = process.env[envVar];
4273
+ const source = fromStore ? "store" : fromEnv ? "env" : "none";
4274
+ entries.push({
4275
+ service,
4276
+ envVar,
4277
+ required,
4278
+ description,
4279
+ configured: source !== "none",
4280
+ source,
4281
+ last4: fromStore ? fromStore.last4 : fromEnv ? fromEnv.slice(-4) : ""
4282
+ });
4283
+ }
4284
+ }
4285
+ for (const e of stored.values()) {
4286
+ if (serviceFilter && e.service !== serviceFilter) continue;
4287
+ entries.push({
4288
+ service: e.service,
4289
+ envVar: e.envVar,
4290
+ required: false,
4291
+ description: "Stored connection reference",
4292
+ configured: true,
4293
+ source: "store",
4294
+ last4: e.last4
4295
+ });
4296
+ }
4297
+ return { enabled: store?.enabled ?? false, entries };
4298
+ }
4299
+ async set(service, envVar, value) {
4300
+ const store = getInstalledCredentialStore();
4301
+ if (!store) {
4302
+ throw new Error("No credential store is installed on this host \u2014 set credentials via the environment");
4303
+ }
4304
+ this.assertSettable(service, envVar);
4305
+ const entry = await store.set(service, envVar, value);
4306
+ return { saved: true, service, envVar, last4: entry.last4 };
4307
+ }
4308
+ async remove(service, envVar) {
4309
+ const store = getInstalledCredentialStore();
4310
+ if (!store) {
4311
+ throw new Error("No credential store is installed on this host");
4312
+ }
4313
+ this.assertSettable(service, envVar);
4314
+ return { removed: await store.remove(envVar) };
4315
+ }
4316
+ async test(service) {
4317
+ const declared = this.declaredFor(service);
4318
+ const missing = declared.filter((c) => c.required && !resolveCredentialRef(c.envVar)).map((c) => c.envVar);
4319
+ const configured = missing.length === 0;
4320
+ if (!configured) {
4321
+ return { service, configured, missing, probed: false, ok: false, message: `Missing required credentials: ${missing.join(", ")}` };
4322
+ }
4323
+ const factory = getActiveFactory();
4324
+ const probe = isProbeService(service) ? serviceProbes[service] : void 0;
4325
+ if (!factory || !probe) {
4326
+ return { service, configured, missing, probed: false, ok: true, message: "Credentials present (no live probe declared for this service)" };
4327
+ }
4328
+ if (!factory.isConfigured(service)) {
4329
+ return { service, configured, missing, probed: false, ok: false, message: "Credentials present but the service is not configured on this host \u2014 restart or re-save a credential" };
4330
+ }
4331
+ const result = await factory.execute(service, probe.action, probe.params);
4332
+ if (!result.success) {
4333
+ return { service, configured, missing, probed: true, ok: false, message: result.error?.message ?? `Probe ${probe.action} failed` };
4334
+ }
4335
+ const echoed = result.data !== null && typeof result.data === "object" && "_mock" in result.data;
4336
+ if (echoed) {
4337
+ return { service, configured, missing, probed: false, ok: false, message: "Probe was mock-echoed \u2014 the service is not actually configured" };
4338
+ }
4339
+ return { service, configured, missing, probed: true, ok: true, message: `Probe ${probe.action} succeeded` };
4340
+ }
4341
+ };
4342
+ registerIntegration("credentials", CredentialsIntegration);
4343
+ function isParamRecord2(value) {
4344
+ return value !== null && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date);
4345
+ }
4346
+ function toFilePayload(value) {
4347
+ if (!isParamRecord2(value)) return null;
4348
+ const { name, size, type, content } = value;
4349
+ if (typeof name !== "string") return null;
4350
+ return {
4351
+ name,
4352
+ size: typeof size === "number" ? size : 0,
4353
+ type: typeof type === "string" ? type : "application/octet-stream",
4354
+ content: typeof content === "string" ? content : void 0
4355
+ };
4356
+ }
4357
+ function decodeContent2(content) {
4358
+ const dataUrlMatch = /^data:([^;,]+)?(;base64)?,(.*)$/s.exec(content);
4359
+ if (dataUrlMatch) {
4360
+ const [, mime, isB64, body] = dataUrlMatch;
4361
+ const bytes = isB64 ? Buffer.from(body, "base64") : Buffer.from(decodeURIComponent(body), "utf8");
4362
+ return { bytes, contentType: mime || null };
4363
+ }
4364
+ return { bytes: Buffer.from(content, "utf8"), contentType: null };
4365
+ }
2839
4366
  var StorageIntegration = class extends BaseIntegration {
2840
4367
  constructor(config) {
2841
4368
  super(config);
2842
4369
  this.objects = /* @__PURE__ */ new Map();
2843
- const storageUrl = config.env.STORAGE_URL;
2844
- if (storageUrl) {
2845
- this.logger.warn(
2846
- "STORAGE_URL is configured but real storage client is not yet implemented. Falling back to in-memory store.",
2847
- { storageUrl }
2848
- );
4370
+ this.s3 = null;
4371
+ this.defaultBucket = config.env.STORAGE_BUCKET || "";
4372
+ this.publicUrlBase = config.env.STORAGE_PUBLIC_URL_BASE || "";
4373
+ const accessKeyId = config.env.STORAGE_ACCESS_KEY_ID || "";
4374
+ const secretAccessKey = config.env.STORAGE_SECRET_ACCESS_KEY || "";
4375
+ if (accessKeyId && secretAccessKey) {
4376
+ const endpoint = config.env.STORAGE_ENDPOINT || void 0;
4377
+ this.s3 = new S3Client({
4378
+ region: config.env.STORAGE_REGION || "us-east-1",
4379
+ endpoint,
4380
+ // Path-style is what MinIO/R2-style endpoints expect.
4381
+ forcePathStyle: Boolean(endpoint),
4382
+ credentials: { accessKeyId, secretAccessKey }
4383
+ });
4384
+ this.logger.info("Storage integration initialized (S3 backend)", {
4385
+ endpoint: endpoint ?? "aws",
4386
+ bucket: this.defaultBucket
4387
+ });
4388
+ } else {
4389
+ if (process.env.NODE_ENV === "production") {
4390
+ throw new Error(
4391
+ "Storage credentials missing in production (STORAGE_ACCESS_KEY_ID / STORAGE_SECRET_ACCESS_KEY) \u2014 refusing the in-memory fallback. See SECRETS.md."
4392
+ );
4393
+ }
4394
+ this.logger.warn("Storage integration initialized (in-memory backend \u2014 dev only, nothing persists)");
2849
4395
  }
2850
- this.logger.info("Storage integration initialized (in-memory backend)");
2851
4396
  }
2852
4397
  async execute(action, params) {
2853
4398
  const validation = this.validateParams(action, params);
@@ -2897,107 +4442,182 @@ var StorageIntegration = class extends BaseIntegration {
2897
4442
  // ---------------------------------------------------------------------------
2898
4443
  // Helpers
2899
4444
  // ---------------------------------------------------------------------------
2900
- /** Build a composite key from bucket and object key. */
4445
+ bucketOf(params) {
4446
+ return params.bucket || this.defaultBucket;
4447
+ }
2901
4448
  compositeKey(bucket, key) {
2902
4449
  return `${bucket}/${key}`;
2903
4450
  }
2904
- /** Generate a deterministic etag from content. */
2905
- generateEtag(content) {
2906
- const raw = typeof content === "string" ? content : JSON.stringify(content);
2907
- let hash = 0;
2908
- for (let i = 0; i < raw.length; i++) {
2909
- const ch = raw.charCodeAt(i);
2910
- hash = (hash << 5) - hash + ch | 0;
4451
+ generateEtag(bytes) {
4452
+ return `"${createHash("md5").update(bytes).digest("hex")}"`;
4453
+ }
4454
+ /** Resolve the upload inputs from either admitted shape. */
4455
+ resolveUpload(params) {
4456
+ const file = toFilePayload(params.file);
4457
+ if (file) {
4458
+ const maxSize = typeof params.maxSize === "number" ? params.maxSize : 0;
4459
+ if (maxSize > 0 && file.size > maxSize) {
4460
+ throw new Error(`Upload rejected: ${file.name} is ${file.size} bytes (max ${maxSize})`);
4461
+ }
4462
+ if (!file.content) {
4463
+ throw new Error(
4464
+ `Upload rejected: file payload for '${file.name}' carries no content \u2014 the uploader must include the base64 data URL`
4465
+ );
4466
+ }
4467
+ const { bytes: bytes2, contentType: contentType2 } = decodeContent2(file.content);
4468
+ const safeName = file.name.replace(/[^A-Za-z0-9._-]/g, "_");
4469
+ return {
4470
+ key: `${Date.now()}-${safeName}`,
4471
+ bytes: bytes2,
4472
+ contentType: contentType2 ?? file.type,
4473
+ acl: params.acl === "public" ? "public-read" : void 0
4474
+ };
4475
+ }
4476
+ const key = params.key;
4477
+ const content = params.content;
4478
+ if (!key || content === void 0 || content === null) {
4479
+ throw new Error("upload requires either `file` (with content) or the `key` + `content` pair");
2911
4480
  }
2912
- return `"${Math.abs(hash).toString(16).padStart(8, "0")}"`;
4481
+ const raw = typeof content === "string" ? content : JSON.stringify(content);
4482
+ const { bytes, contentType } = decodeContent2(raw);
4483
+ return {
4484
+ key,
4485
+ bytes,
4486
+ contentType: params.contentType || contentType || "application/octet-stream",
4487
+ acl: params.acl === "public" ? "public-read" : void 0
4488
+ };
2913
4489
  }
2914
- /** Compute the byte size of content. */
2915
- computeSize(content) {
2916
- if (typeof content === "string") {
2917
- return new TextEncoder().encode(content).byteLength;
4490
+ publicUrl(bucket, key) {
4491
+ if (this.publicUrlBase) {
4492
+ return `${this.publicUrlBase.replace(/\/$/, "")}/${key}`;
4493
+ }
4494
+ const endpoint = this.config.env.STORAGE_ENDPOINT;
4495
+ if (endpoint) {
4496
+ return `${endpoint.replace(/\/$/, "")}/${bucket}/${key}`;
2918
4497
  }
2919
- return JSON.stringify(content).length;
4498
+ const region = this.config.env.STORAGE_REGION || "us-east-1";
4499
+ return `https://${bucket}.s3.${region}.amazonaws.com/${key}`;
2920
4500
  }
2921
4501
  // ---------------------------------------------------------------------------
2922
4502
  // Actions
2923
4503
  // ---------------------------------------------------------------------------
2924
4504
  async upload(params) {
2925
- const bucket = params.bucket;
2926
- const key = params.key;
2927
- const content = params.content;
2928
- const contentType = params.contentType ?? "application/octet-stream";
2929
- const metadata = params.metadata ?? {};
2930
- this.logger.debug("Storage UPLOAD", { bucket, key, contentType });
2931
- const size = this.computeSize(content);
2932
- const etag = this.generateEtag(content);
2933
- const obj = {
2934
- content,
2935
- contentType,
2936
- size,
2937
- metadata,
2938
- lastModified: Date.now(),
2939
- etag
2940
- };
2941
- this.objects.set(this.compositeKey(bucket, key), obj);
2942
- return { key, bucket, size, etag };
4505
+ const bucket = this.bucketOf(params);
4506
+ const { key, bytes, contentType, acl } = this.resolveUpload(params);
4507
+ const etag = this.generateEtag(bytes);
4508
+ this.logger.debug("Storage UPLOAD", { bucket, key, contentType, size: bytes.length });
4509
+ if (this.s3) {
4510
+ await this.s3.send(
4511
+ new PutObjectCommand({
4512
+ Bucket: bucket,
4513
+ Key: key,
4514
+ Body: bytes,
4515
+ ContentType: contentType,
4516
+ ACL: acl
4517
+ })
4518
+ );
4519
+ } else {
4520
+ this.objects.set(this.compositeKey(bucket, key), {
4521
+ content: bytes.toString("base64"),
4522
+ contentType,
4523
+ size: bytes.length,
4524
+ metadata: params.metadata ?? {},
4525
+ lastModified: Date.now(),
4526
+ etag
4527
+ });
4528
+ }
4529
+ const url = acl === "public-read" ? this.publicUrl(bucket, key) : (await this.signUrl(bucket, key, "get", 3600)).url;
4530
+ return { key, bucket, size: bytes.length, etag, id: key, url };
2943
4531
  }
2944
4532
  async download(params) {
2945
- const bucket = params.bucket;
4533
+ const bucket = this.bucketOf(params);
2946
4534
  const key = params.key;
2947
4535
  this.logger.debug("Storage DOWNLOAD", { bucket, key });
4536
+ if (this.s3) {
4537
+ const response = await this.s3.send(new GetObjectCommand({ Bucket: bucket, Key: key }));
4538
+ const bytes = Buffer.from(await response.Body.transformToByteArray());
4539
+ return {
4540
+ content: bytes.toString("base64"),
4541
+ contentType: response.ContentType ?? "application/octet-stream",
4542
+ size: bytes.length,
4543
+ metadata: {}
4544
+ };
4545
+ }
2948
4546
  const obj = this.objects.get(this.compositeKey(bucket, key));
2949
4547
  if (!obj) {
2950
4548
  throw new Error(`Object not found: ${bucket}/${key}`);
2951
4549
  }
2952
4550
  return {
2953
- content: obj.content,
4551
+ content: String(obj.content),
2954
4552
  contentType: obj.contentType,
2955
4553
  size: obj.size,
2956
4554
  metadata: obj.metadata
2957
4555
  };
2958
4556
  }
2959
4557
  async list(params) {
2960
- const bucket = params.bucket;
4558
+ const bucket = this.bucketOf(params);
2961
4559
  const prefix = params.prefix ?? "";
2962
4560
  const maxKeys = params.maxKeys ?? 1e3;
2963
4561
  this.logger.debug("Storage LIST", { bucket, prefix, maxKeys });
4562
+ if (this.s3) {
4563
+ const response = await this.s3.send(
4564
+ new ListObjectsV2Command({ Bucket: bucket, Prefix: prefix || void 0, MaxKeys: maxKeys })
4565
+ );
4566
+ return {
4567
+ keys: (response.Contents ?? []).map((entry) => ({
4568
+ key: entry.Key ?? "",
4569
+ size: entry.Size ?? 0,
4570
+ lastModified: entry.LastModified?.getTime() ?? 0
4571
+ })),
4572
+ // Continuation tokens are unrepresentable in the result type (ledger
4573
+ // I-11) — surface the clamp honestly.
4574
+ truncated: Boolean(response.IsTruncated)
4575
+ };
4576
+ }
2964
4577
  const bucketPrefix = `${bucket}/`;
2965
4578
  const fullPrefix = `${bucket}/${prefix}`;
2966
4579
  const results = [];
2967
4580
  for (const [compositeKey, obj] of this.objects) {
2968
4581
  if (!compositeKey.startsWith(fullPrefix)) continue;
2969
- const objectKey = compositeKey.slice(bucketPrefix.length);
2970
4582
  results.push({
2971
- key: objectKey,
4583
+ key: compositeKey.slice(bucketPrefix.length),
2972
4584
  size: obj.size,
2973
4585
  lastModified: obj.lastModified
2974
4586
  });
2975
4587
  }
2976
4588
  results.sort((a, b) => a.key.localeCompare(b.key));
2977
- const truncated = results.length > maxKeys;
2978
- return {
2979
- keys: results.slice(0, maxKeys),
2980
- truncated
2981
- };
4589
+ return { keys: results.slice(0, maxKeys), truncated: results.length > maxKeys };
2982
4590
  }
2983
4591
  async deleteObject(params) {
2984
- const bucket = params.bucket;
4592
+ const bucket = this.bucketOf(params);
2985
4593
  const key = params.key;
2986
4594
  this.logger.debug("Storage DELETE", { bucket, key });
4595
+ if (this.s3) {
4596
+ await this.s3.send(new DeleteObjectCommand({ Bucket: bucket, Key: key }));
4597
+ return { deleted: true };
4598
+ }
2987
4599
  const existed = this.objects.has(this.compositeKey(bucket, key));
2988
4600
  this.objects.delete(this.compositeKey(bucket, key));
2989
4601
  return { deleted: existed };
2990
4602
  }
4603
+ async signUrl(bucket, key, operation, expiresIn) {
4604
+ const expiresAt = Date.now() + expiresIn * 1e3;
4605
+ if (this.s3) {
4606
+ const command = operation === "put" ? new PutObjectCommand({ Bucket: bucket, Key: key }) : new GetObjectCommand({ Bucket: bucket, Key: key });
4607
+ const url2 = await getSignedUrl(this.s3, command, { expiresIn });
4608
+ return { url: url2, expiresAt };
4609
+ }
4610
+ const token = createHash("sha256").update(`${bucket}/${key}/${expiresAt}`).digest("hex").slice(0, 16);
4611
+ const url = `https://storage.mock.local/${bucket}/${key}?X-Amz-Algorithm=MOCK-HMAC-SHA256&X-Amz-Expires=${expiresIn}&X-Amz-SignedHeaders=host&X-Amz-Signature=${token}&operation=${operation}`;
4612
+ return { url, expiresAt };
4613
+ }
2991
4614
  async getSignedUrl(params) {
2992
- const bucket = params.bucket;
4615
+ const bucket = this.bucketOf(params);
2993
4616
  const key = params.key;
2994
4617
  const expiresIn = params.expiresIn ?? 3600;
2995
4618
  const operation = params.operation ?? "get";
2996
4619
  this.logger.debug("Storage GET_SIGNED_URL", { bucket, key, expiresIn, operation });
2997
- const expiresAt = Date.now() + expiresIn * 1e3;
2998
- const token = Math.random().toString(36).slice(2, 18);
2999
- const url = `https://storage.mock.local/${bucket}/${key}?X-Amz-Algorithm=MOCK-HMAC-SHA256&X-Amz-Expires=${expiresIn}&X-Amz-SignedHeaders=host&X-Amz-Signature=${token}&operation=${operation}`;
3000
- return { url, expiresAt };
4620
+ return this.signUrl(bucket, key, operation, expiresIn);
3001
4621
  }
3002
4622
  };
3003
4623
  registerIntegration("storage", StorageIntegration);
@@ -3504,10 +5124,10 @@ var DatabaseIntegration = class extends BaseIntegration {
3504
5124
  }
3505
5125
  /** Resolve (and cache) the driver for a connection reference. */
3506
5126
  driverFor(connectionRef) {
3507
- const connectionString = process.env[connectionRef];
5127
+ const connectionString = resolveCredentialRef(connectionRef);
3508
5128
  if (!connectionString) {
3509
5129
  throw new IntegrationError(
3510
- `Connection reference "${connectionRef}" is not set in the environment`,
5130
+ `Connection reference "${connectionRef}" is not set in the credential store or environment`,
3511
5131
  "AUTH_ERROR"
3512
5132
  );
3513
5133
  }
@@ -3759,6 +5379,6 @@ var ArxivIntegration = class extends BaseIntegration {
3759
5379
  };
3760
5380
  registerIntegration("arxiv", ArxivIntegration);
3761
5381
 
3762
- export { ArxivIntegration, BaseIntegration, CLIIntegration, ConsoleLogger, DatabaseIntegration, DeepAgentIntegration, DockerIntegration, EmailIntegration, GitHubIntegration, IconifyIntegration, IntegrationError, IntegrationFactory, LLMIntegration, MLIntegration, OAuthIntegration, OtelIntegration, QueueIntegration, RedisIntegration, StorageIntegration, StripeIntegration, TwilioIntegration, WebhookIntegration, WikimediaIntegration, YouTubeIntegration, assertReadOnlySelect, getIntegration, getIntegrationFactory, getRegisteredIntegrations, isKnownIntegration, registerIntegration, resetIntegrationFactory, validateParams, verifyAndParseStripeEvent, withRetry };
5382
+ export { AccountingIntegration, ArxivIntegration, BankingIntegration, BaseIntegration, CLIIntegration, CREDENTIAL_ENTITY_TYPE, CREDENTIAL_MASTER_KEY_ENV, CalendarIntegration, ConsoleLogger, CredentialStore, CredentialsIntegration, DatabaseIntegration, DeepAgentIntegration, DockerIntegration, DriveIntegration, EmailIntegration, EsignIntegration, GitHubIntegration, IconifyIntegration, IntegrationError, IntegrationFactory, LLMIntegration, MLIntegration, MetaAdsIntegration, OAuthIntegration, OtelIntegration, PushIntegration, QueueIntegration, RedisIntegration, StorageIntegration, StripeIntegration, TwilioIntegration, WebhookIntegration, WikimediaIntegration, YouTubeIntegration, assertReadOnlySelect, getActiveFactory, getInstalledCredentialStore, getIntegration, getIntegrationFactory, getRegisteredIntegrations, googleCalendarHookProvider, installActiveFactory, installCredentialStore, isKnownIntegration, parseCalendarPushNotification, registerIntegration, resetIntegrationFactory, resolveCredentialRef, uninstallCredentialStore, validateParams, verifyAndParseStripeEvent, withRetry };
3763
5383
  //# sourceMappingURL=index.js.map
3764
5384
  //# sourceMappingURL=index.js.map