"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);

// src/connections/index.ts
var connections_exports = {};
__export(connections_exports, {
  ATLASSIAN_SCOPES: () => ATLASSIAN_SCOPES,
  AtlassianProvider: () => AtlassianProvider,
  AtlassianScopes: () => AtlassianScopes,
  ConnectionCrypto: () => ConnectionCrypto,
  ConnectionManager: () => ConnectionManager,
  GITHUB_SCOPES: () => GITHUB_SCOPES,
  GitHubProvider: () => GitHubProvider,
  GitHubScopes: () => GitHubScopes,
  GoogleProvider: () => GoogleProvider,
  GoogleScopes: () => GoogleScopes,
  HUBSPOT_SCOPES: () => HUBSPOT_SCOPES,
  HubSpotProvider: () => HubSpotProvider,
  HubSpotScopes: () => HubSpotScopes,
  LINEAR_SCOPES: () => LINEAR_SCOPES,
  LinearProvider: () => LinearProvider,
  LinearScopes: () => LinearScopes,
  MemoryConnectionStore: () => MemoryConnectionStore,
  MicrosoftGraphProvider: () => MicrosoftGraphProvider,
  MicrosoftGraphScopes: () => MicrosoftGraphScopes,
  NoConnectionError: () => NoConnectionError,
  NotionProvider: () => NotionProvider,
  OAuthExchangeError: () => OAuthExchangeError,
  PrismaConnectionStore: () => PrismaConnectionStore,
  ProviderNotRegisteredError: () => ProviderNotRegisteredError,
  SlackProvider: () => SlackProvider,
  SlackScopes: () => SlackScopes,
  TokenRefreshError: () => TokenRefreshError,
  XERO_SCOPES: () => XERO_SCOPES,
  XeroProvider: () => XeroProvider,
  XeroScopes: () => XeroScopes
});
module.exports = __toCommonJS(connections_exports);

// src/connections/connection-manager.ts
var import_node_crypto = require("crypto");
var NoConnectionError = class extends Error {
  constructor(operatorId, providerId) {
    super(
      `No connection found for operator "${operatorId}" and provider "${providerId}".`
    );
    this.name = "NoConnectionError";
  }
};
var ProviderNotRegisteredError = class extends Error {
  constructor(providerId) {
    super(
      `Provider "${providerId}" is not registered. Add it to ConnectionManagerConfig.providers.`
    );
    this.name = "ProviderNotRegisteredError";
  }
};
var OAuthExchangeError = class extends Error {
  constructor(providerId, cause) {
    super(
      `OAuth code exchange failed for provider "${providerId}": ${String(cause)}`
    );
    this.name = "OAuthExchangeError";
    this.cause = cause;
  }
};
var TokenRefreshError = class extends Error {
  constructor(providerId, cause) {
    super(
      `Token refresh failed for provider "${providerId}": ${String(cause)}`
    );
    this.name = "TokenRefreshError";
    this.cause = cause;
  }
};
var REFRESH_BUFFER_MS = 5 * 60 * 1e3;
var ConnectionManager = class {
  store;
  providers;
  credentials;
  defaultRedirectUri;
  constructor(config) {
    this.store = config.store;
    this.providers = config.providers;
    this.credentials = config.credentials;
    this.defaultRedirectUri = config.defaultRedirectUri;
  }
  // -------------------------------------------------------------------------
  // Public API
  // -------------------------------------------------------------------------
  /**
   * Step 1 of the OAuth flow: build an authorization URL the user must visit.
   *
   * Returns the URL, a CSRF state token, and the providerId. The caller is
   * responsible for storing `state` (e.g. in a session or signed cookie) and
   * verifying it on the callback before calling completeConnection().
   */
  async connect(operatorId, providerId, options = {}) {
    const provider = this.requireProvider(providerId);
    const { clientId } = this.requireCredentials(providerId);
    const state = options.state ?? (0, import_node_crypto.randomUUID)();
    const scopes = options.scopes ?? provider.defaultScopes;
    const redirectUri = options.redirectUri ?? this.defaultRedirectUri;
    const authorizationUrl = provider.buildAuthorizationUrl({
      clientId,
      redirectUri,
      scopes,
      state,
      operatorId
    });
    return { authorizationUrl, state, providerId };
  }
  /**
   * Step 2 of the OAuth flow: exchange the authorization code for tokens and
   * persist the resulting Connection.
   *
   * Call this from your OAuth callback route after verifying the `state`
   * parameter matches what was stored during connect().
   *
   * @throws {OAuthExchangeError} when the vendor rejects the code.
   */
  async completeConnection(operatorId, providerId, code, redirectUri) {
    const provider = this.requireProvider(providerId);
    const { clientId, clientSecret } = this.requireCredentials(providerId);
    const resolvedRedirectUri = redirectUri ?? this.defaultRedirectUri;
    let tokenResponse;
    try {
      tokenResponse = await provider.exchangeCode({
        clientId,
        clientSecret,
        code,
        redirectUri: resolvedRedirectUri
      });
    } catch (err) {
      throw new OAuthExchangeError(providerId, err);
    }
    let metadata;
    if (provider.fetchUserInfo) {
      try {
        metadata = await provider.fetchUserInfo(tokenResponse.accessToken);
      } catch {
      }
    }
    const connection = {
      id: (0, import_node_crypto.randomUUID)(),
      providerId,
      operatorId,
      accessToken: tokenResponse.accessToken,
      refreshToken: tokenResponse.refreshToken,
      expiresAt: tokenResponse.expiresAt,
      scopes: tokenResponse.scopes,
      connectedAt: /* @__PURE__ */ new Date(),
      metadata
    };
    await this.store.save(connection);
    return connection;
  }
  /**
   * Returns a valid access token for the operator's connection to the given
   * provider. Transparently refreshes if the token is within 5 minutes of
   * expiry.
   *
   * @throws {NoConnectionError} when no connection exists.
   * @throws {TokenRefreshError} when refresh fails.
   */
  async getAccessToken(operatorId, providerId) {
    const connection = await this.store.get(operatorId, providerId);
    if (!connection) {
      throw new NoConnectionError(operatorId, providerId);
    }
    const refreshed = await this.refreshIfNeeded(connection);
    return refreshed.accessToken;
  }
  /**
   * Non-throwing status check. Safe to call in middleware or UI layers that
   * need to know whether a connection exists without handling exceptions.
   */
  async getConnectionStatus(operatorId, providerId) {
    const connection = await this.store.get(operatorId, providerId);
    if (!connection) {
      return { connected: false };
    }
    let expiresIn;
    if (connection.expiresAt) {
      expiresIn = Math.max(
        0,
        Math.floor((connection.expiresAt.getTime() - Date.now()) / 1e3)
      );
    }
    return { connected: true, connection, expiresIn };
  }
  /**
   * Revokes the access token at the vendor's end (if the provider supports it)
   * and deletes the Connection record from the store.
   */
  async disconnect(operatorId, providerId) {
    const provider = this.requireProvider(providerId);
    const connection = await this.store.get(operatorId, providerId);
    if (connection && provider.revokeAccess) {
      try {
        await provider.revokeAccess(connection.accessToken);
      } catch {
      }
    }
    await this.store.delete(operatorId, providerId);
  }
  /** Returns all active connections for the given operator. */
  async listConnections(operatorId) {
    return this.store.list(operatorId);
  }
  // -------------------------------------------------------------------------
  // Internal helpers
  // -------------------------------------------------------------------------
  /**
   * Refreshes the connection's access token if it is within REFRESH_BUFFER_MS
   * of expiry. Returns the (possibly refreshed) Connection.
   *
   * No-ops if the connection has no expiry or if it is not yet near expiry.
   *
   * @throws {TokenRefreshError} when the refresh request fails.
   */
  async refreshIfNeeded(connection) {
    if (!connection.expiresAt) {
      return connection;
    }
    const msUntilExpiry = connection.expiresAt.getTime() - Date.now();
    if (msUntilExpiry > REFRESH_BUFFER_MS) {
      return connection;
    }
    if (!connection.refreshToken) {
      return connection;
    }
    const provider = this.requireProvider(connection.providerId);
    const { clientId, clientSecret } = this.requireCredentials(
      connection.providerId
    );
    let tokenResponse;
    try {
      tokenResponse = await provider.refreshAccessToken({
        clientId,
        clientSecret,
        refreshToken: connection.refreshToken
      });
    } catch (err) {
      throw new TokenRefreshError(connection.providerId, err);
    }
    const newRefreshToken = tokenResponse.refreshToken ?? connection.refreshToken;
    await this.store.updateTokens(
      connection.operatorId,
      connection.providerId,
      tokenResponse.accessToken,
      newRefreshToken,
      tokenResponse.expiresAt
    );
    return {
      ...connection,
      accessToken: tokenResponse.accessToken,
      refreshToken: newRefreshToken,
      expiresAt: tokenResponse.expiresAt
    };
  }
  requireProvider(providerId) {
    const provider = this.providers.get(providerId);
    if (!provider) {
      throw new ProviderNotRegisteredError(providerId);
    }
    return provider;
  }
  requireCredentials(providerId) {
    const creds = this.credentials.get(providerId);
    if (!creds) {
      throw new ProviderNotRegisteredError(providerId);
    }
    return creds;
  }
};

// src/connections/connection-store-memory.ts
var MemoryConnectionStore = class {
  // operatorId → providerId → Connection
  store = /* @__PURE__ */ new Map();
  async save(connection) {
    let operatorMap = this.store.get(connection.operatorId);
    if (!operatorMap) {
      operatorMap = /* @__PURE__ */ new Map();
      this.store.set(connection.operatorId, operatorMap);
    }
    operatorMap.set(connection.providerId, connection);
  }
  async get(operatorId, providerId) {
    return this.store.get(operatorId)?.get(providerId) ?? null;
  }
  async list(operatorId) {
    const operatorMap = this.store.get(operatorId);
    if (!operatorMap) return [];
    return Array.from(operatorMap.values());
  }
  async delete(operatorId, providerId) {
    this.store.get(operatorId)?.delete(providerId);
  }
  async updateTokens(operatorId, providerId, accessToken, refreshToken, expiresAt) {
    const existing = this.store.get(operatorId)?.get(providerId);
    if (!existing) return;
    const updated = {
      ...existing,
      accessToken,
      refreshToken: refreshToken ?? existing.refreshToken,
      expiresAt: expiresAt ?? existing.expiresAt
    };
    this.store.get(operatorId).set(providerId, updated);
  }
  /**
   * Returns the total number of stored connections across all operators.
   * Useful for test assertions.
   */
  size() {
    let total = 0;
    for (const map of this.store.values()) {
      total += map.size;
    }
    return total;
  }
  /** Clears all stored connections. Useful for test teardown. */
  clear() {
    this.store.clear();
  }
};

// src/connections/connection-store-prisma.ts
var PrismaConnectionStore = class {
  constructor(prisma, crypto) {
    this.prisma = prisma;
    this.crypto = crypto;
  }
  prisma;
  crypto;
  async save(connection) {
    const encrypted = this.crypto.encryptConnection(connection);
    await this.prisma.connection.upsert({
      where: {
        operatorId_providerId: {
          operatorId: connection.operatorId,
          providerId: connection.providerId
        }
      },
      create: encrypted,
      update: { ...encrypted, updatedAt: /* @__PURE__ */ new Date() }
    });
  }
  async get(operatorId, providerId) {
    const row = await this.prisma.connection.findUnique({
      where: { operatorId_providerId: { operatorId, providerId } }
    });
    if (!row) return null;
    return this.crypto.decryptConnection(row);
  }
  async list(operatorId) {
    const rows = await this.prisma.connection.findMany({
      where: { operatorId }
    });
    return rows.map((row) => this.crypto.decryptConnection(row));
  }
  async delete(operatorId, providerId) {
    try {
      await this.prisma.connection.delete({
        where: { operatorId_providerId: { operatorId, providerId } }
      });
    } catch {
    }
  }
  async updateTokens(operatorId, providerId, accessToken, refreshToken, expiresAt) {
    const accessTokenEnc = this.crypto.encryptToken(accessToken);
    const data = {
      encryptedAccessToken: accessTokenEnc.ciphertext,
      accessTokenIv: accessTokenEnc.iv,
      accessTokenTag: accessTokenEnc.tag,
      updatedAt: /* @__PURE__ */ new Date()
    };
    if (expiresAt !== void 0) {
      data.expiresAt = expiresAt;
    }
    if (refreshToken !== void 0) {
      const refreshTokenEnc = this.crypto.encryptToken(refreshToken);
      data.encryptedRefreshToken = refreshTokenEnc.ciphertext;
      data.refreshTokenIv = refreshTokenEnc.iv;
      data.refreshTokenTag = refreshTokenEnc.tag;
    }
    try {
      await this.prisma.connection.update({
        where: { operatorId_providerId: { operatorId, providerId } },
        data
      });
    } catch {
    }
  }
};

// src/connections/crypto.ts
var import_node_crypto2 = require("crypto");
var ConnectionCrypto = class {
  derivedKey;
  constructor(config) {
    if (!config.encryptionKey || config.encryptionKey.length < 32) {
      throw new Error(
        "ConnectionCrypto requires a 32+ char encryption key. Set CONNECTION_ENCRYPTION_KEY env var to a strong secret."
      );
    }
    this.derivedKey = (0, import_node_crypto2.scryptSync)(
      config.encryptionKey,
      "agent-kit-connections-salt-v1",
      32
    );
  }
  encryptToken(plaintext) {
    const iv = (0, import_node_crypto2.randomBytes)(12);
    const cipher = (0, import_node_crypto2.createCipheriv)("aes-256-gcm", this.derivedKey, iv);
    const ciphertext = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
    const tag = cipher.getAuthTag();
    return {
      ciphertext: ciphertext.toString("base64"),
      iv: iv.toString("base64"),
      tag: tag.toString("base64")
    };
  }
  decryptToken(encrypted) {
    const iv = Buffer.from(encrypted.iv, "base64");
    const ciphertext = Buffer.from(encrypted.ciphertext, "base64");
    const tag = Buffer.from(encrypted.tag, "base64");
    const decipher = (0, import_node_crypto2.createDecipheriv)("aes-256-gcm", this.derivedKey, iv);
    decipher.setAuthTag(tag);
    return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf8");
  }
  encryptConnection(connection) {
    const accessTokenEnc = this.encryptToken(connection.accessToken);
    let encryptedRefreshToken = null;
    let refreshTokenIv = null;
    let refreshTokenTag = null;
    if (connection.refreshToken != null) {
      const enc = this.encryptToken(connection.refreshToken);
      encryptedRefreshToken = enc.ciphertext;
      refreshTokenIv = enc.iv;
      refreshTokenTag = enc.tag;
    }
    return {
      id: connection.id,
      operatorId: connection.operatorId,
      providerId: connection.providerId,
      encryptedAccessToken: accessTokenEnc.ciphertext,
      accessTokenIv: accessTokenEnc.iv,
      accessTokenTag: accessTokenEnc.tag,
      encryptedRefreshToken,
      refreshTokenIv,
      refreshTokenTag,
      expiresAt: connection.expiresAt ?? null,
      scopes: connection.scopes,
      connectedAt: connection.connectedAt,
      lastUsedAt: connection.lastUsedAt ?? null,
      metadata: connection.metadata ?? null,
      updatedAt: /* @__PURE__ */ new Date()
    };
  }
  decryptConnection(row) {
    const accessToken = this.decryptToken({
      ciphertext: row.encryptedAccessToken,
      iv: row.accessTokenIv,
      tag: row.accessTokenTag
    });
    let refreshToken;
    if (row.encryptedRefreshToken != null && row.refreshTokenIv != null && row.refreshTokenTag != null) {
      refreshToken = this.decryptToken({
        ciphertext: row.encryptedRefreshToken,
        iv: row.refreshTokenIv,
        tag: row.refreshTokenTag
      });
    }
    return {
      id: row.id,
      operatorId: row.operatorId,
      providerId: row.providerId,
      accessToken,
      refreshToken,
      expiresAt: row.expiresAt ?? void 0,
      scopes: row.scopes,
      connectedAt: row.connectedAt,
      lastUsedAt: row.lastUsedAt ?? void 0,
      metadata: row.metadata ?? void 0
    };
  }
};

// src/connections/providers/google-provider.ts
var GoogleScopes = {
  CALENDAR_READ: "https://www.googleapis.com/auth/calendar.readonly",
  CALENDAR_FULL: "https://www.googleapis.com/auth/calendar",
  CALENDAR_EVENTS: "https://www.googleapis.com/auth/calendar.events",
  GMAIL_READ: "https://www.googleapis.com/auth/gmail.readonly",
  GMAIL_SEND: "https://www.googleapis.com/auth/gmail.send",
  GMAIL_COMPOSE: "https://www.googleapis.com/auth/gmail.compose",
  DRIVE_READ: "https://www.googleapis.com/auth/drive.readonly",
  DRIVE_FULL: "https://www.googleapis.com/auth/drive",
  SHEETS: "https://www.googleapis.com/auth/spreadsheets",
  DOCS: "https://www.googleapis.com/auth/documents",
  TASKS: "https://www.googleapis.com/auth/tasks"
};
var AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
var TOKEN_URL = "https://oauth2.googleapis.com/token";
var REVOKE_URL = "https://oauth2.googleapis.com/revoke";
var USERINFO_URL = "https://www.googleapis.com/oauth2/v2/userinfo";
var GoogleProvider = class {
  id = "google";
  displayName = "Google Workspace";
  iconUrl = "https://www.google.com/favicon.ico";
  defaultScopes = [
    "https://www.googleapis.com/auth/userinfo.email",
    "https://www.googleapis.com/auth/userinfo.profile"
  ];
  buildAuthorizationUrl(params) {
    const query = new URLSearchParams({
      client_id: params.clientId,
      redirect_uri: params.redirectUri,
      response_type: "code",
      scope: params.scopes.join(" "),
      state: params.state,
      access_type: "offline",
      // Forces re-consent so refresh_token is always returned, even on
      // re-authorization of already-granted scopes.
      prompt: "consent"
    });
    return `${AUTH_URL}?${query.toString()}`;
  }
  async exchangeCode(params) {
    const body = new URLSearchParams({
      client_id: params.clientId,
      client_secret: params.clientSecret,
      code: params.code,
      redirect_uri: params.redirectUri,
      grant_type: "authorization_code"
    });
    const res = await fetch(TOKEN_URL, {
      method: "POST",
      headers: { "Content-Type": "application/x-www-form-urlencoded" },
      body: body.toString()
    });
    const data = await res.json();
    if (!res.ok || data.error) {
      throw new Error(
        data.error_description ?? data.error ?? `HTTP ${res.status}`
      );
    }
    return this.parseTokenResponse(data);
  }
  async refreshAccessToken(params) {
    const body = new URLSearchParams({
      client_id: params.clientId,
      client_secret: params.clientSecret,
      refresh_token: params.refreshToken,
      grant_type: "refresh_token"
    });
    const res = await fetch(TOKEN_URL, {
      method: "POST",
      headers: { "Content-Type": "application/x-www-form-urlencoded" },
      body: body.toString()
    });
    const data = await res.json();
    if (!res.ok || data.error) {
      throw new Error(
        data.error_description ?? data.error ?? `HTTP ${res.status}`
      );
    }
    return this.parseTokenResponse(data);
  }
  async revokeAccess(accessToken) {
    const query = new URLSearchParams({ token: accessToken });
    const res = await fetch(`${REVOKE_URL}?${query.toString()}`, {
      method: "POST"
    });
    if (!res.ok) {
      throw new Error(`Revocation failed: HTTP ${res.status}`);
    }
  }
  async fetchUserInfo(accessToken) {
    const res = await fetch(USERINFO_URL, {
      headers: { Authorization: `Bearer ${accessToken}` }
    });
    if (!res.ok) {
      throw new Error(`Userinfo fetch failed: HTTP ${res.status}`);
    }
    const data = await res.json();
    return data;
  }
  // -------------------------------------------------------------------------
  // Private helpers
  // -------------------------------------------------------------------------
  parseTokenResponse(data) {
    let expiresAt;
    if (data.expires_in != null) {
      expiresAt = new Date(Date.now() + data.expires_in * 1e3);
    }
    const scopes = data.scope ? data.scope.split(" ") : [];
    return {
      accessToken: data.access_token,
      refreshToken: data.refresh_token,
      expiresAt,
      scopes,
      tokenType: data.token_type ?? "Bearer",
      raw: data
    };
  }
};

// src/connections/providers/microsoft-graph-provider.ts
var MicrosoftGraphScopes = {
  USER_READ: "https://graph.microsoft.com/User.Read",
  MAIL_READ: "https://graph.microsoft.com/Mail.Read",
  MAIL_SEND: "https://graph.microsoft.com/Mail.Send",
  MAIL_READWRITE: "https://graph.microsoft.com/Mail.ReadWrite",
  CALENDARS_READ: "https://graph.microsoft.com/Calendars.Read",
  CALENDARS_READWRITE: "https://graph.microsoft.com/Calendars.ReadWrite",
  FILES_READ: "https://graph.microsoft.com/Files.Read",
  FILES_READWRITE: "https://graph.microsoft.com/Files.ReadWrite",
  CONTACTS_READ: "https://graph.microsoft.com/Contacts.Read",
  PRESENCE_READ: "https://graph.microsoft.com/Presence.Read",
  TEAMS_READ: "https://graph.microsoft.com/Team.ReadBasic.All",
  CHAT_READ: "https://graph.microsoft.com/Chat.Read",
  /**
   * Required for refresh tokens. Microsoft does not issue refresh_tokens
   * unless "offline_access" is explicitly included in the scope list.
   * This is a plain string (not a Graph URL) — include it verbatim.
   */
  OFFLINE_ACCESS: "offline_access"
};
var MicrosoftGraphProvider = class {
  id = "microsoft_365";
  displayName = "Microsoft 365";
  iconUrl = "https://outlook.live.com/favicon.ico";
  /**
   * Minimum scopes for identity access. Includes `offline_access` so a
   * refresh_token is always issued. Consumers compose additional scopes
   * from `MicrosoftGraphScopes` as needed.
   */
  defaultScopes = [
    "openid",
    "profile",
    "email",
    "offline_access"
  ];
  tenantId;
  constructor(options = {}) {
    this.tenantId = options.tenantId ?? "common";
  }
  // -------------------------------------------------------------------------
  // Private endpoint helpers
  // -------------------------------------------------------------------------
  get authUrl() {
    return `https://login.microsoftonline.com/${this.tenantId}/oauth2/v2.0/authorize`;
  }
  get tokenUrl() {
    return `https://login.microsoftonline.com/${this.tenantId}/oauth2/v2.0/token`;
  }
  // -------------------------------------------------------------------------
  // Provider interface
  // -------------------------------------------------------------------------
  buildAuthorizationUrl(params) {
    const query = new URLSearchParams({
      client_id: params.clientId,
      redirect_uri: params.redirectUri,
      response_type: "code",
      // response_mode=query puts the code in the URL query string, which is
      // the conventional pattern for server-side apps. Use "form_post" for
      // SPAs that cannot read query strings on the callback page.
      response_mode: "query",
      scope: params.scopes.join(" "),
      state: params.state
    });
    return `${this.authUrl}?${query.toString()}`;
  }
  async exchangeCode(params) {
    const body = new URLSearchParams({
      client_id: params.clientId,
      client_secret: params.clientSecret,
      code: params.code,
      redirect_uri: params.redirectUri,
      grant_type: "authorization_code"
    });
    const res = await fetch(this.tokenUrl, {
      method: "POST",
      headers: { "Content-Type": "application/x-www-form-urlencoded" },
      body: body.toString()
    });
    const data = await res.json();
    if (!res.ok || data.error) {
      throw new Error(
        data.error_description ?? data.error ?? `HTTP ${res.status}`
      );
    }
    return this.parseTokenResponse(data);
  }
  async refreshAccessToken(params) {
    const body = new URLSearchParams({
      client_id: params.clientId,
      client_secret: params.clientSecret,
      refresh_token: params.refreshToken,
      grant_type: "refresh_token"
    });
    const res = await fetch(this.tokenUrl, {
      method: "POST",
      headers: { "Content-Type": "application/x-www-form-urlencoded" },
      body: body.toString()
    });
    const data = await res.json();
    if (!res.ok || data.error) {
      throw new Error(
        data.error_description ?? data.error ?? `HTTP ${res.status}`
      );
    }
    return this.parseTokenResponse(data);
  }
  async revokeAccess(_accessToken) {
    console.warn(
      "[MicrosoftGraphProvider] revokeAccess: Microsoft does not expose a self-service revocation endpoint for delegated tokens. The local connection record will be deleted, and the access token will expire naturally. For immediate revocation, the user must remove app access at https://myapps.microsoft.com."
    );
  }
  async fetchUserInfo(accessToken) {
    const res = await fetch("https://graph.microsoft.com/v1.0/me", {
      headers: { Authorization: `Bearer ${accessToken}` }
    });
    if (!res.ok) {
      throw new Error(`Userinfo fetch failed: HTTP ${res.status}`);
    }
    const data = await res.json();
    return data;
  }
  // -------------------------------------------------------------------------
  // Private helpers
  // -------------------------------------------------------------------------
  parseTokenResponse(data) {
    let expiresAt;
    if (data.expires_in != null) {
      expiresAt = new Date(Date.now() + data.expires_in * 1e3);
    }
    const scopes = data.scope ? data.scope.split(" ") : [];
    return {
      accessToken: data.access_token,
      refreshToken: data.refresh_token,
      expiresAt,
      scopes,
      tokenType: data.token_type ?? "Bearer",
      raw: data
    };
  }
};

// src/connections/providers/slack-provider.ts
var SlackScopes = {
  // Bot scopes — what your app's bot user can do
  CHAT_WRITE: "chat:write",
  CHAT_WRITE_PUBLIC: "chat:write.public",
  CHANNELS_READ: "channels:read",
  CHANNELS_HISTORY: "channels:history",
  GROUPS_READ: "groups:read",
  GROUPS_HISTORY: "groups:history",
  IM_READ: "im:read",
  IM_HISTORY: "im:history",
  USERS_READ: "users:read",
  USERS_READ_EMAIL: "users:read.email",
  TEAM_READ: "team:read",
  FILES_READ: "files:read",
  FILES_WRITE: "files:write"
};
var AUTH_URL2 = "https://slack.com/oauth/v2/authorize";
var TOKEN_URL2 = "https://slack.com/api/oauth.v2.access";
var AUTH_TEST_URL = "https://slack.com/api/auth.test";
var REVOKE_URL2 = "https://slack.com/api/auth.revoke";
var SlackProvider = class {
  id = "slack";
  displayName = "Slack";
  iconUrl = "https://slack.com/favicon.ico";
  /**
   * Slack has no equivalent of Google's identity scopes for the bot flow.
   * Leave empty — callers must explicitly provide the scopes they need.
   * The most minimal working set is typically `["chat:write", "channels:read"]`.
   */
  defaultScopes = [];
  buildAuthorizationUrl(params) {
    const query = new URLSearchParams({
      client_id: params.clientId,
      redirect_uri: params.redirectUri,
      scope: params.scopes.join(" "),
      state: params.state
    });
    return `${AUTH_URL2}?${query.toString()}`;
  }
  async exchangeCode(params) {
    const credentials = Buffer.from(
      `${params.clientId}:${params.clientSecret}`
    ).toString("base64");
    const body = new URLSearchParams({
      code: params.code,
      redirect_uri: params.redirectUri
    });
    const res = await fetch(TOKEN_URL2, {
      method: "POST",
      headers: {
        "Content-Type": "application/x-www-form-urlencoded",
        Authorization: `Basic ${credentials}`
      },
      body: body.toString()
    });
    if (!res.ok) {
      throw new Error(`Slack token exchange failed: HTTP ${res.status}`);
    }
    const data = await res.json();
    if (!data.ok) {
      throw new Error(data.error ?? "Slack oauth.v2.access returned ok: false");
    }
    if (!data.access_token) {
      throw new Error(
        "Slack oauth.v2.access succeeded but returned no access_token. This can happen when only user_scope (not scope) was requested \u2014 check your scope/user_scope configuration."
      );
    }
    return this.parseTokenResponse(data);
  }
  async refreshAccessToken(params) {
    const credentials = Buffer.from(
      `${params.clientId}:${params.clientSecret}`
    ).toString("base64");
    const body = new URLSearchParams({
      grant_type: "refresh_token",
      refresh_token: params.refreshToken
    });
    const res = await fetch(TOKEN_URL2, {
      method: "POST",
      headers: {
        "Content-Type": "application/x-www-form-urlencoded",
        Authorization: `Basic ${credentials}`
      },
      body: body.toString()
    });
    if (!res.ok) {
      throw new Error(`Slack token refresh failed: HTTP ${res.status}`);
    }
    const data = await res.json();
    if (!data.ok) {
      const errorMessage = data.error ?? "Slack token refresh returned ok: false";
      if (data.error === "token_rotation_not_enabled" || data.error === "invalid_refresh_token") {
        throw new Error(
          `Slack token refresh failed: ${errorMessage}. To use token rotation with Slack, enable it in your Slack app's OAuth & Permissions settings at api.slack.com/apps. When rotation is disabled, Slack tokens do not expire and no refresh is needed.`
        );
      }
      throw new Error(`Slack token refresh failed: ${errorMessage}`);
    }
    if (!data.access_token) {
      throw new Error(
        "Slack token refresh succeeded but returned no access_token."
      );
    }
    return this.parseTokenResponse(data);
  }
  async revokeAccess(accessToken) {
    const body = new URLSearchParams({ token: accessToken });
    const res = await fetch(REVOKE_URL2, {
      method: "POST",
      headers: { "Content-Type": "application/x-www-form-urlencoded" },
      body: body.toString()
    });
    if (!res.ok) {
      throw new Error(`Slack revocation request failed: HTTP ${res.status}`);
    }
    const data = await res.json();
    if (!data.ok) {
      throw new Error(
        data.error ?? "Slack auth.revoke returned ok: false"
      );
    }
  }
  async fetchUserInfo(accessToken) {
    const res = await fetch(AUTH_TEST_URL, {
      method: "POST",
      headers: {
        Authorization: `Bearer ${accessToken}`,
        "Content-Type": "application/x-www-form-urlencoded"
      }
    });
    if (!res.ok) {
      throw new Error(`Slack auth.test failed: HTTP ${res.status}`);
    }
    const data = await res.json();
    if (!data.ok) {
      throw new Error(data.error ?? "Slack auth.test returned ok: false");
    }
    return data;
  }
  // -------------------------------------------------------------------------
  // Private helpers
  // -------------------------------------------------------------------------
  parseTokenResponse(data) {
    let expiresAt;
    if (data.expires_in != null) {
      expiresAt = new Date(Date.now() + data.expires_in * 1e3);
    }
    const scopes = data.scope ? data.scope.split(",").map((s) => s.trim()).filter(Boolean) : [];
    return {
      accessToken: data.access_token,
      refreshToken: data.refresh_token,
      expiresAt,
      scopes,
      tokenType: data.token_type ?? "bot",
      raw: data
    };
  }
};

// src/connections/providers/hubspot-provider.ts
var HubSpotScopes = {
  CONTACTS_READ: "crm.objects.contacts.read",
  CONTACTS_WRITE: "crm.objects.contacts.write",
  DEALS_READ: "crm.objects.deals.read",
  DEALS_WRITE: "crm.objects.deals.write",
  COMPANIES_READ: "crm.objects.companies.read",
  COMPANIES_WRITE: "crm.objects.companies.write",
  /**
   * Required for the OAuth authorization flow. Always include this scope.
   * HubSpot uses it to grant the token exchange itself.
   */
  OAUTH: "oauth"
};
var HUBSPOT_SCOPES = {
  /** Read-only access to contacts, deals, and companies. */
  READ_ONLY: [
    HubSpotScopes.CONTACTS_READ,
    HubSpotScopes.DEALS_READ,
    HubSpotScopes.COMPANIES_READ,
    HubSpotScopes.OAUTH
  ],
  /** Read and write access to contacts, deals, and companies. */
  READ_WRITE: [
    HubSpotScopes.CONTACTS_READ,
    HubSpotScopes.CONTACTS_WRITE,
    HubSpotScopes.DEALS_READ,
    HubSpotScopes.DEALS_WRITE,
    HubSpotScopes.COMPANIES_READ,
    HubSpotScopes.COMPANIES_WRITE,
    HubSpotScopes.OAUTH
  ],
  /**
   * Full CRM access — read/write contacts, deals, and companies.
   * Extend with additional HubSpotScopes constants as your app adds more
   * object types (tickets, line items, etc.).
   */
  FULL_CRM: [
    HubSpotScopes.CONTACTS_READ,
    HubSpotScopes.CONTACTS_WRITE,
    HubSpotScopes.DEALS_READ,
    HubSpotScopes.DEALS_WRITE,
    HubSpotScopes.COMPANIES_READ,
    HubSpotScopes.COMPANIES_WRITE,
    HubSpotScopes.OAUTH
  ]
};
var AUTH_URL3 = "https://app.hubspot.com/oauth/authorize";
var TOKEN_URL3 = "https://api.hubapi.com/oauth/v1/token";
var TOKEN_INFO_URL = "https://api.hubapi.com/oauth/v1/access-tokens";
var HubSpotProvider = class {
  id = "hubspot";
  displayName = "HubSpot";
  iconUrl = "https://www.hubspot.com/favicon.ico";
  /**
   * Minimum default scope — includes `oauth` which HubSpot requires for the
   * authorization flow itself. Callers should pass additional CRM scopes via
   * the `scopes` option on `connect()`. Use `HUBSPOT_SCOPES.READ_ONLY` or
   * `HUBSPOT_SCOPES.READ_WRITE` as a starting point.
   */
  defaultScopes = [HubSpotScopes.OAUTH];
  buildAuthorizationUrl(params) {
    const query = new URLSearchParams({
      client_id: params.clientId,
      redirect_uri: params.redirectUri,
      scope: params.scopes.join(" "),
      state: params.state
    });
    return `${AUTH_URL3}?${query.toString()}`;
  }
  async exchangeCode(params) {
    const body = new URLSearchParams({
      grant_type: "authorization_code",
      client_id: params.clientId,
      client_secret: params.clientSecret,
      redirect_uri: params.redirectUri,
      code: params.code
    });
    const res = await fetch(TOKEN_URL3, {
      method: "POST",
      headers: { "Content-Type": "application/x-www-form-urlencoded" },
      body: body.toString()
    });
    const data = await res.json();
    if (!res.ok || data.error) {
      throw new Error(
        data.error_description ?? data.message ?? data.error ?? `HTTP ${res.status}`
      );
    }
    return this.parseTokenResponse(data);
  }
  async refreshAccessToken(params) {
    const body = new URLSearchParams({
      grant_type: "refresh_token",
      client_id: params.clientId,
      client_secret: params.clientSecret,
      refresh_token: params.refreshToken
    });
    const res = await fetch(TOKEN_URL3, {
      method: "POST",
      headers: { "Content-Type": "application/x-www-form-urlencoded" },
      body: body.toString()
    });
    const data = await res.json();
    if (!res.ok || data.error) {
      throw new Error(
        data.error_description ?? data.message ?? data.error ?? `HTTP ${res.status}`
      );
    }
    return this.parseTokenResponse(data);
  }
  async revokeAccess(_accessToken) {
    console.warn(
      "[HubSpotProvider] revokeAccess: HubSpot does not expose a token revocation endpoint. The local connection record will be deleted. For immediate revocation, the portal admin must disconnect the app from Settings \u2192 Integrations \u2192 Connected Apps at app.hubspot.com."
    );
  }
  async fetchUserInfo(accessToken) {
    const res = await fetch(`${TOKEN_INFO_URL}/${accessToken}`);
    if (!res.ok) {
      throw new Error(`HubSpot token info fetch failed: HTTP ${res.status}`);
    }
    const data = await res.json();
    return data;
  }
  // -------------------------------------------------------------------------
  // Private helpers
  // -------------------------------------------------------------------------
  parseTokenResponse(data) {
    let expiresAt;
    if (data.expires_in != null) {
      expiresAt = new Date(Date.now() + data.expires_in * 1e3);
    }
    return {
      accessToken: data.access_token,
      refreshToken: data.refresh_token,
      expiresAt,
      scopes: [],
      tokenType: data.token_type ?? "Bearer",
      raw: data
    };
  }
};

// src/connections/providers/xero-provider.ts
var XeroScopes = {
  OPENID: "openid",
  PROFILE: "profile",
  EMAIL: "email",
  /**
   * Required for refresh tokens. Without this scope, Xero issues a
   * short-lived access token with no ability to refresh.
   */
  OFFLINE_ACCESS: "offline_access",
  /** Read/write access to invoices, credit notes, bank transactions, etc. */
  ACCOUNTING_TRANSACTIONS: "accounting.transactions",
  /** Read/write access to organisation settings, tax rates, accounts. */
  ACCOUNTING_SETTINGS: "accounting.settings",
  /** Read/write access to contacts (customers, suppliers). */
  ACCOUNTING_CONTACTS: "accounting.contacts",
  /** Read/write access to files and attachments. */
  ACCOUNTING_ATTACHMENTS: "accounting.attachments",
  /** Read-only access to financial reports (P&L, balance sheet, etc.). */
  ACCOUNTING_REPORTS_READ: "accounting.reports.read"
};
var XERO_SCOPES = {
  /** Identity only — openid, profile, email. No accounting access. */
  IDENTITY_ONLY: [
    XeroScopes.OPENID,
    XeroScopes.PROFILE,
    XeroScopes.EMAIL
  ],
  /**
   * Read-only accounting — identity + offline access + read-only financial
   * reports. Use when your app only needs to pull report data without
   * creating or modifying accounting records.
   */
  READ_ONLY_ACCOUNTING: [
    XeroScopes.OPENID,
    XeroScopes.PROFILE,
    XeroScopes.EMAIL,
    XeroScopes.OFFLINE_ACCESS,
    XeroScopes.ACCOUNTING_REPORTS_READ
  ],
  /**
   * Full accounting access — identity + offline access + all accounting
   * scopes (transactions, settings, contacts, attachments, reports).
   */
  FULL_ACCOUNTING: [
    XeroScopes.OPENID,
    XeroScopes.PROFILE,
    XeroScopes.EMAIL,
    XeroScopes.OFFLINE_ACCESS,
    XeroScopes.ACCOUNTING_TRANSACTIONS,
    XeroScopes.ACCOUNTING_SETTINGS,
    XeroScopes.ACCOUNTING_CONTACTS,
    XeroScopes.ACCOUNTING_ATTACHMENTS,
    XeroScopes.ACCOUNTING_REPORTS_READ
  ]
};
var AUTH_URL4 = "https://login.xero.com/identity/connect/authorize";
var TOKEN_URL4 = "https://identity.xero.com/connect/token";
var REVOKE_URL3 = "https://identity.xero.com/connect/revocation";
var USERINFO_URL2 = "https://identity.xero.com/connect/userinfo";
var XeroProvider = class {
  id = "xero";
  displayName = "Xero";
  iconUrl = "https://www.xero.com/favicon.ico";
  /**
   * Minimum identity scopes. Does not include `offline_access` — callers
   * that need refresh tokens must explicitly include `XeroScopes.OFFLINE_ACCESS`
   * or use one of the `XERO_SCOPES` groupings that already includes it.
   */
  defaultScopes = [
    XeroScopes.OPENID,
    XeroScopes.PROFILE,
    XeroScopes.EMAIL,
    XeroScopes.OFFLINE_ACCESS
  ];
  buildAuthorizationUrl(params) {
    const query = new URLSearchParams({
      client_id: params.clientId,
      redirect_uri: params.redirectUri,
      response_type: "code",
      scope: params.scopes.join(" "),
      state: params.state
    });
    return `${AUTH_URL4}?${query.toString()}`;
  }
  async exchangeCode(params) {
    const credentials = Buffer.from(
      `${params.clientId}:${params.clientSecret}`
    ).toString("base64");
    const body = new URLSearchParams({
      grant_type: "authorization_code",
      code: params.code,
      redirect_uri: params.redirectUri
    });
    const res = await fetch(TOKEN_URL4, {
      method: "POST",
      headers: {
        "Content-Type": "application/x-www-form-urlencoded",
        Authorization: `Basic ${credentials}`
      },
      body: body.toString()
    });
    const data = await res.json();
    if (!res.ok || data.error) {
      throw new Error(
        data.error_description ?? data.detail ?? data.error ?? `HTTP ${res.status}`
      );
    }
    return this.parseTokenResponse(data);
  }
  async refreshAccessToken(params) {
    const credentials = Buffer.from(
      `${params.clientId}:${params.clientSecret}`
    ).toString("base64");
    const body = new URLSearchParams({
      grant_type: "refresh_token",
      refresh_token: params.refreshToken
    });
    const res = await fetch(TOKEN_URL4, {
      method: "POST",
      headers: {
        "Content-Type": "application/x-www-form-urlencoded",
        Authorization: `Basic ${credentials}`
      },
      body: body.toString()
    });
    const data = await res.json();
    if (!res.ok || data.error) {
      throw new Error(
        data.error_description ?? data.detail ?? data.error ?? `HTTP ${res.status}`
      );
    }
    return this.parseTokenResponse(data);
  }
  async revokeAccess(accessToken) {
    const body = new URLSearchParams({ token: accessToken });
    const res = await fetch(REVOKE_URL3, {
      method: "POST",
      headers: { "Content-Type": "application/x-www-form-urlencoded" },
      body: body.toString()
    });
    if (!res.ok) {
      throw new Error(`Xero token revocation failed: HTTP ${res.status}`);
    }
  }
  async fetchUserInfo(accessToken) {
    const res = await fetch(USERINFO_URL2, {
      headers: { Authorization: `Bearer ${accessToken}` }
    });
    if (!res.ok) {
      throw new Error(`Xero userinfo fetch failed: HTTP ${res.status}`);
    }
    const data = await res.json();
    return data;
  }
  // -------------------------------------------------------------------------
  // Private helpers
  // -------------------------------------------------------------------------
  parseTokenResponse(data) {
    let expiresAt;
    if (data.expires_in != null) {
      expiresAt = new Date(Date.now() + data.expires_in * 1e3);
    }
    const scopes = data.scope ? data.scope.split(" ").filter(Boolean) : [];
    return {
      accessToken: data.access_token,
      refreshToken: data.refresh_token,
      expiresAt,
      scopes,
      tokenType: data.token_type ?? "Bearer",
      raw: data
    };
  }
};

// src/connections/providers/notion-provider.ts
var AUTH_URL5 = "https://api.notion.com/v1/oauth/authorize";
var TOKEN_URL5 = "https://api.notion.com/v1/oauth/token";
var USERINFO_URL3 = "https://api.notion.com/v1/users/me";
var NOTION_API_VERSION = "2022-06-28";
var NotionProvider = class {
  id = "notion";
  displayName = "Notion";
  iconUrl = "https://www.notion.so/favicon.ico";
  /**
   * Notion does not use OAuth scopes — access is workspace-level and
   * controlled by the user during the authorization flow. defaultScopes
   * is empty.
   */
  defaultScopes = [];
  buildAuthorizationUrl(params) {
    const query = new URLSearchParams({
      client_id: params.clientId,
      redirect_uri: params.redirectUri,
      response_type: "code",
      owner: "user",
      state: params.state
    });
    return `${AUTH_URL5}?${query.toString()}`;
  }
  async exchangeCode(params) {
    const credentials = Buffer.from(
      `${params.clientId}:${params.clientSecret}`
    ).toString("base64");
    const res = await fetch(TOKEN_URL5, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: `Basic ${credentials}`
      },
      body: JSON.stringify({
        grant_type: "authorization_code",
        code: params.code,
        redirect_uri: params.redirectUri
      })
    });
    const data = await res.json();
    if (!res.ok || data.error) {
      throw new Error(data.message ?? data.error ?? `HTTP ${res.status}`);
    }
    if (!data.access_token) {
      throw new Error(
        "Notion token exchange succeeded but returned no access_token."
      );
    }
    return this.parseTokenResponse(data);
  }
  async refreshAccessToken(_params) {
    throw new Error(
      "NotionProvider: Notion access tokens do not expire and do not support refresh. This method should never be called for Notion connections \u2014 expiresAt is always undefined. If you see this error, check that your ConnectionManager is not forcing a refresh for a provider that does not need one."
    );
  }
  async revokeAccess(_accessToken) {
    console.warn(
      "[NotionProvider] revokeAccess: Notion does not expose a token revocation endpoint. The local connection record will be deleted. For immediate revocation, the user must disconnect the integration from Notion workspace settings: Settings \u2192 Connections."
    );
  }
  async fetchUserInfo(accessToken) {
    const res = await fetch(USERINFO_URL3, {
      headers: {
        Authorization: `Bearer ${accessToken}`,
        "Notion-Version": NOTION_API_VERSION
      }
    });
    if (!res.ok) {
      throw new Error(`Notion userinfo fetch failed: HTTP ${res.status}`);
    }
    const data = await res.json();
    return data;
  }
  // -------------------------------------------------------------------------
  // Private helpers
  // -------------------------------------------------------------------------
  parseTokenResponse(data) {
    return {
      accessToken: data.access_token,
      // Notion does not issue refresh tokens.
      refreshToken: void 0,
      // Notion tokens are permanent — no expiry.
      expiresAt: void 0,
      // Notion does not use granular scopes; access is workspace-level.
      scopes: [],
      tokenType: data.token_type ?? "bearer",
      raw: data
    };
  }
};

// src/connections/providers/github-provider.ts
var GitHubScopes = {
  /** Full control of private repositories (includes read access to all repos). */
  REPO: "repo",
  /** Read/write access to public repositories only. */
  PUBLIC_REPO: "public_repo",
  /** Read all user profile data. */
  READ_USER: "read:user",
  /** Access user email addresses (read-only). */
  USER_EMAIL: "user:email",
  /** Read-only access to organisation and team membership. */
  READ_ORG: "read:org",
  /** Access notifications. */
  NOTIFICATIONS: "notifications",
  /** Upload packages to GitHub Packages. */
  WRITE_PACKAGES: "write:packages",
  /** Download packages from GitHub Packages. */
  READ_PACKAGES: "read:packages"
};
var GITHUB_SCOPES = {
  /** Identity + read-only access to public repositories. */
  READ_ONLY: [
    GitHubScopes.READ_USER,
    GitHubScopes.USER_EMAIL,
    GitHubScopes.PUBLIC_REPO
  ],
  /** Full repository access + email. Covers most development integrations. */
  READ_WRITE: [
    GitHubScopes.REPO,
    GitHubScopes.USER_EMAIL
  ],
  /**
   * Full development workflow — repository access, org membership, notifications,
   * and GitHub Packages read/write.
   */
  FULL_DEV: [
    GitHubScopes.REPO,
    GitHubScopes.READ_ORG,
    GitHubScopes.USER_EMAIL,
    GitHubScopes.NOTIFICATIONS,
    GitHubScopes.WRITE_PACKAGES,
    GitHubScopes.READ_PACKAGES
  ]
};
var AUTH_URL6 = "https://github.com/login/oauth/authorize";
var TOKEN_URL6 = "https://github.com/login/oauth/access_token";
var USERINFO_URL4 = "https://api.github.com/user";
var GitHubProvider = class {
  id = "github";
  displayName = "GitHub";
  iconUrl = "https://github.com/favicon.ico";
  /**
   * Minimum identity scopes. Consumers that need repository access should pass
   * one of the `GITHUB_SCOPES` groupings or compose a custom scope list.
   */
  defaultScopes = [
    GitHubScopes.READ_USER,
    GitHubScopes.USER_EMAIL
  ];
  buildAuthorizationUrl(params) {
    const query = new URLSearchParams({
      client_id: params.clientId,
      redirect_uri: params.redirectUri,
      scope: params.scopes.join(" "),
      state: params.state
    });
    return `${AUTH_URL6}?${query.toString()}`;
  }
  async exchangeCode(params) {
    const body = new URLSearchParams({
      client_id: params.clientId,
      client_secret: params.clientSecret,
      code: params.code,
      redirect_uri: params.redirectUri
    });
    const res = await fetch(TOKEN_URL6, {
      method: "POST",
      headers: {
        "Content-Type": "application/x-www-form-urlencoded",
        // Request JSON response instead of the default form-encoded body.
        Accept: "application/json"
      },
      body: body.toString()
    });
    const data = await res.json();
    if (!res.ok || data.error) {
      throw new Error(
        data.error_description ?? data.error ?? `HTTP ${res.status}`
      );
    }
    return this.parseTokenResponse(data);
  }
  async refreshAccessToken(params) {
    const body = new URLSearchParams({
      client_id: params.clientId,
      client_secret: params.clientSecret,
      grant_type: "refresh_token",
      refresh_token: params.refreshToken
    });
    const res = await fetch(TOKEN_URL6, {
      method: "POST",
      headers: {
        "Content-Type": "application/x-www-form-urlencoded",
        Accept: "application/json"
      },
      body: body.toString()
    });
    const data = await res.json();
    if (!res.ok || data.error) {
      throw new Error(
        data.error_description ?? data.error ?? `HTTP ${res.status}`
      );
    }
    return this.parseTokenResponse(data);
  }
  async revokeAccess(_accessToken) {
    console.warn(
      "[GitHubProvider] revokeAccess: GitHub token revocation requires OAuth app credentials that are not available in this method. The local connection record will be deleted. For immediate revocation, the user must disconnect the app from github.com/settings/connections/applications."
    );
  }
  async fetchUserInfo(accessToken) {
    const res = await fetch(USERINFO_URL4, {
      headers: {
        Authorization: `Bearer ${accessToken}`,
        Accept: "application/vnd.github+json",
        "X-GitHub-Api-Version": "2022-11-28"
      }
    });
    if (!res.ok) {
      throw new Error(`GitHub user fetch failed: HTTP ${res.status}`);
    }
    const data = await res.json();
    return data;
  }
  // -------------------------------------------------------------------------
  // Private helpers
  // -------------------------------------------------------------------------
  parseTokenResponse(data) {
    let expiresAt;
    if (data.expires_in != null) {
      expiresAt = new Date(Date.now() + data.expires_in * 1e3);
    }
    const scopes = data.scope ? data.scope.split(",").map((s) => s.trim()).filter(Boolean) : [];
    return {
      accessToken: data.access_token,
      refreshToken: data.refresh_token,
      expiresAt,
      scopes,
      tokenType: data.token_type ?? "bearer",
      raw: data
    };
  }
};

// src/connections/providers/linear-provider.ts
var LinearScopes = {
  /** Read-only access to all Linear data (issues, projects, teams, etc.). */
  READ: "read",
  /** Write access to all Linear data. */
  WRITE: "write",
  /** Create issues without full write access. */
  ISSUES_CREATE: "issues:create",
  /** Create comments without full write access. */
  COMMENTS_CREATE: "comments:create",
  /** Full admin access including organisation settings and member management. */
  ADMIN: "admin"
};
var LINEAR_SCOPES = {
  /** Read-only access to all Linear data. */
  READ_ONLY: [LinearScopes.READ],
  /** Read and write access to all Linear data. */
  READ_WRITE: [LinearScopes.READ, LinearScopes.WRITE],
  /**
   * Full admin access — read/write all data plus organisation settings and
   * member management. Use with caution; most integrations only need READ_WRITE.
   */
  ADMIN: [
    LinearScopes.READ,
    LinearScopes.WRITE,
    LinearScopes.ADMIN
  ]
};
var AUTH_URL7 = "https://linear.app/oauth/authorize";
var TOKEN_URL7 = "https://api.linear.app/oauth/token";
var REVOKE_URL4 = "https://api.linear.app/oauth/revoke";
var GRAPHQL_URL = "https://api.linear.app/graphql";
var LinearProvider = class {
  id = "linear";
  displayName = "Linear";
  iconUrl = "https://linear.app/favicon.ico";
  /** Minimum read-only scope. Use `LINEAR_SCOPES.READ_WRITE` for most integrations. */
  defaultScopes = [LinearScopes.READ];
  buildAuthorizationUrl(params) {
    const query = new URLSearchParams({
      client_id: params.clientId,
      redirect_uri: params.redirectUri,
      response_type: "code",
      scope: params.scopes.join(","),
      state: params.state
    });
    return `${AUTH_URL7}?${query.toString()}`;
  }
  async exchangeCode(params) {
    const body = new URLSearchParams({
      grant_type: "authorization_code",
      client_id: params.clientId,
      client_secret: params.clientSecret,
      code: params.code,
      redirect_uri: params.redirectUri
    });
    const res = await fetch(TOKEN_URL7, {
      method: "POST",
      headers: { "Content-Type": "application/x-www-form-urlencoded" },
      body: body.toString()
    });
    const data = await res.json();
    if (!res.ok || data.error) {
      throw new Error(
        data.error_description ?? data.error ?? `HTTP ${res.status}`
      );
    }
    return this.parseTokenResponse(data);
  }
  async refreshAccessToken(params) {
    const body = new URLSearchParams({
      grant_type: "refresh_token",
      client_id: params.clientId,
      client_secret: params.clientSecret,
      refresh_token: params.refreshToken
    });
    const res = await fetch(TOKEN_URL7, {
      method: "POST",
      headers: { "Content-Type": "application/x-www-form-urlencoded" },
      body: body.toString()
    });
    const data = await res.json();
    if (!res.ok || data.error) {
      throw new Error(
        data.error_description ?? data.error ?? `HTTP ${res.status}`
      );
    }
    return this.parseTokenResponse(data);
  }
  async revokeAccess(accessToken) {
    const body = new URLSearchParams({ access_token: accessToken });
    const res = await fetch(REVOKE_URL4, {
      method: "POST",
      headers: { "Content-Type": "application/x-www-form-urlencoded" },
      body: body.toString()
    });
    if (!res.ok) {
      throw new Error(`Linear token revocation failed: HTTP ${res.status}`);
    }
  }
  async fetchUserInfo(accessToken) {
    const res = await fetch(GRAPHQL_URL, {
      method: "POST",
      headers: {
        Authorization: `Bearer ${accessToken}`,
        "Content-Type": "application/json"
      },
      body: JSON.stringify({
        query: "{ viewer { id name email displayName avatarUrl } }"
      })
    });
    if (!res.ok) {
      throw new Error(`Linear userinfo fetch failed: HTTP ${res.status}`);
    }
    const data = await res.json();
    if (data.errors?.length) {
      throw new Error(
        `Linear userinfo query failed: ${data.errors[0]?.message ?? "unknown error"}`
      );
    }
    const viewer = data.data?.viewer ?? {};
    return viewer;
  }
  // -------------------------------------------------------------------------
  // Private helpers
  // -------------------------------------------------------------------------
  parseTokenResponse(data) {
    let expiresAt;
    if (data.expires_in != null) {
      expiresAt = new Date(Date.now() + data.expires_in * 1e3);
    }
    const scopes = data.scope ? data.scope.split(",").map((s) => s.trim()).filter(Boolean) : [];
    return {
      accessToken: data.access_token,
      refreshToken: data.refresh_token,
      expiresAt,
      scopes,
      tokenType: data.token_type ?? "Bearer",
      raw: data
    };
  }
};

// src/connections/providers/atlassian-provider.ts
var AtlassianScopes = {
  /**
   * Read the authenticated user's identity via the /me endpoint. Required for
   * `fetchUserInfo` to work.
   */
  READ_ME: "read:me",
  /** Read Jira issues, projects, boards, sprints, and work items. */
  READ_JIRA_WORK: "read:jira-work",
  /** Create and update Jira issues, comments, and work items. */
  WRITE_JIRA_WORK: "write:jira-work",
  /** Read all Confluence pages, blog posts, and space content. */
  READ_CONFLUENCE_CONTENT: "read:confluence-content.all",
  /** Create and update Confluence pages and blog posts. */
  WRITE_CONFLUENCE_CONTENT: "write:confluence-content",
  /**
   * Required for refresh tokens. Without this scope, Atlassian issues a
   * short-lived access token with no ability to refresh.
   */
  OFFLINE_ACCESS: "offline_access"
};
var ATLASSIAN_SCOPES = {
  /**
   * Read-only Jira access — identity, read Jira work items, and token refresh.
   */
  JIRA_READ_ONLY: [
    AtlassianScopes.READ_ME,
    AtlassianScopes.READ_JIRA_WORK,
    AtlassianScopes.OFFLINE_ACCESS
  ],
  /**
   * Full Jira access — identity, read/write Jira work items, and token refresh.
   */
  JIRA_FULL: [
    AtlassianScopes.READ_ME,
    AtlassianScopes.READ_JIRA_WORK,
    AtlassianScopes.WRITE_JIRA_WORK,
    AtlassianScopes.OFFLINE_ACCESS
  ],
  /**
   * Full Confluence access — identity, read/write all Confluence content, and
   * token refresh.
   */
  CONFLUENCE_FULL: [
    AtlassianScopes.READ_ME,
    AtlassianScopes.READ_CONFLUENCE_CONTENT,
    AtlassianScopes.WRITE_CONFLUENCE_CONTENT,
    AtlassianScopes.OFFLINE_ACCESS
  ],
  /**
   * Combined Jira + Confluence full access — all scopes above. Use when your
   * integration needs to work across both products.
   */
  FULL_ALL: [
    AtlassianScopes.READ_ME,
    AtlassianScopes.READ_JIRA_WORK,
    AtlassianScopes.WRITE_JIRA_WORK,
    AtlassianScopes.READ_CONFLUENCE_CONTENT,
    AtlassianScopes.WRITE_CONFLUENCE_CONTENT,
    AtlassianScopes.OFFLINE_ACCESS
  ]
};
var AUTH_URL8 = "https://auth.atlassian.com/authorize";
var TOKEN_URL8 = "https://auth.atlassian.com/oauth/token";
var USERINFO_URL5 = "https://api.atlassian.com/me";
var AtlassianProvider = class {
  id = "atlassian";
  displayName = "Atlassian";
  iconUrl = "https://www.atlassian.com/favicon.ico";
  /**
   * Minimum identity scopes including offline_access for token refresh.
   * Callers that need Jira or Confluence access should pass one of the
   * `ATLASSIAN_SCOPES` groupings.
   */
  defaultScopes = [
    AtlassianScopes.READ_ME,
    AtlassianScopes.OFFLINE_ACCESS
  ];
  buildAuthorizationUrl(params) {
    const query = new URLSearchParams({
      client_id: params.clientId,
      redirect_uri: params.redirectUri,
      response_type: "code",
      scope: params.scopes.join(" "),
      state: params.state,
      // Required by Atlassian's 3LO flow — identifies the API audience.
      audience: "api.atlassian.com",
      // Forces re-consent on re-authorization so a refresh_token is always
      // returned, even when the user has previously granted the same scopes.
      prompt: "consent"
    });
    return `${AUTH_URL8}?${query.toString()}`;
  }
  async exchangeCode(params) {
    const body = new URLSearchParams({
      grant_type: "authorization_code",
      client_id: params.clientId,
      client_secret: params.clientSecret,
      code: params.code,
      redirect_uri: params.redirectUri
    });
    const res = await fetch(TOKEN_URL8, {
      method: "POST",
      headers: { "Content-Type": "application/x-www-form-urlencoded" },
      body: body.toString()
    });
    const data = await res.json();
    if (!res.ok || data.error) {
      throw new Error(
        data.error_description ?? data.message ?? data.error ?? `HTTP ${res.status}`
      );
    }
    return this.parseTokenResponse(data);
  }
  async refreshAccessToken(params) {
    const body = new URLSearchParams({
      grant_type: "refresh_token",
      client_id: params.clientId,
      client_secret: params.clientSecret,
      refresh_token: params.refreshToken
    });
    const res = await fetch(TOKEN_URL8, {
      method: "POST",
      headers: { "Content-Type": "application/x-www-form-urlencoded" },
      body: body.toString()
    });
    const data = await res.json();
    if (!res.ok || data.error) {
      throw new Error(
        data.error_description ?? data.message ?? data.error ?? `HTTP ${res.status}`
      );
    }
    return this.parseTokenResponse(data);
  }
  async revokeAccess(_accessToken) {
    console.warn(
      "[AtlassianProvider] revokeAccess: Atlassian does not expose a token revocation endpoint accessible with only the access token. The local connection record will be deleted. For immediate revocation, the user must remove the app from id.atlassian.com/manage-profile/apps."
    );
  }
  async fetchUserInfo(accessToken) {
    const res = await fetch(USERINFO_URL5, {
      headers: { Authorization: `Bearer ${accessToken}` }
    });
    if (!res.ok) {
      throw new Error(`Atlassian userinfo fetch failed: HTTP ${res.status}`);
    }
    const data = await res.json();
    return data;
  }
  // -------------------------------------------------------------------------
  // Private helpers
  // -------------------------------------------------------------------------
  parseTokenResponse(data) {
    let expiresAt;
    if (data.expires_in != null) {
      expiresAt = new Date(Date.now() + data.expires_in * 1e3);
    }
    const scopes = data.scope ? data.scope.split(" ").filter(Boolean) : [];
    return {
      accessToken: data.access_token,
      refreshToken: data.refresh_token,
      expiresAt,
      scopes,
      tokenType: data.token_type ?? "Bearer",
      raw: data
    };
  }
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
  ATLASSIAN_SCOPES,
  AtlassianProvider,
  AtlassianScopes,
  ConnectionCrypto,
  ConnectionManager,
  GITHUB_SCOPES,
  GitHubProvider,
  GitHubScopes,
  GoogleProvider,
  GoogleScopes,
  HUBSPOT_SCOPES,
  HubSpotProvider,
  HubSpotScopes,
  LINEAR_SCOPES,
  LinearProvider,
  LinearScopes,
  MemoryConnectionStore,
  MicrosoftGraphProvider,
  MicrosoftGraphScopes,
  NoConnectionError,
  NotionProvider,
  OAuthExchangeError,
  PrismaConnectionStore,
  ProviderNotRegisteredError,
  SlackProvider,
  SlackScopes,
  TokenRefreshError,
  XERO_SCOPES,
  XeroProvider,
  XeroScopes
});
//# sourceMappingURL=index.js.map