import {
  buildIntegrationCoverageConnectors,
  listIntegrationCoverageSpecs
} from "./chunk-7P2LN4VT.js";

// src/specs/types.ts
function specAuthToConnectorAuth(auth) {
  if (auth.mode === "api_key") return "api_key";
  if (auth.mode === "oauth2") return "oauth2";
  if (auth.mode === "none") return "none";
  return "custom";
}

// src/specs/families.ts
var INTEGRATION_FAMILIES = {
  google: {
    id: "google",
    title: "Google OAuth",
    authMode: "oauth2",
    consoleUrl: "https://console.cloud.google.com/apis/credentials",
    authorizationUrl: "https://accounts.google.com/o/oauth2/v2/auth",
    tokenUrl: "https://oauth2.googleapis.com/token",
    redirectUriTemplate: "https://{host}/api/integrations/oauth/google/callback",
    credentialFields: [
      { label: "Client ID", env: "GOOGLE_OAUTH_CLIENT_ID", description: "Google OAuth client ID.", example: "1234567890-abc.apps.googleusercontent.com", regex: "^[0-9]+-[a-zA-Z0-9_-]+\\.apps\\.googleusercontent\\.com$", secret: false },
      { label: "Client Secret", env: "GOOGLE_OAUTH_CLIENT_SECRET", description: "Google OAuth client secret.", example: "GOCSPX-...", secret: true }
    ],
    consoleSteps: [
      { id: "project", title: "Select project", detail: "Open Google Cloud Console and select the project that owns the OAuth client." },
      { id: "consent", title: "Configure consent screen", detail: "Configure OAuth consent, app name, support email, and publishing status appropriate for the deployment." },
      { id: "client", title: "Create web client", detail: "Create an OAuth client of type Web application." },
      { id: "redirect", title: "Add redirect URI", detail: "Add {redirectUri} as an authorized redirect URI.", copyValue: "{redirectUri}" },
      { id: "scopes", title: "Add scopes", detail: "Add the provider scopes listed in this spec." }
    ],
    knownQuirks: [
      { id: "offline-access", severity: "warning", message: "Use access_type=offline and prompt=consent when refresh tokens are required." },
      { id: "verification", severity: "warning", message: "Sensitive or restricted scopes may require Google verification before broad external use." }
    ],
    lifecycle: { supportsRefresh: true, supportsRevoke: true, supportsIncrementalAuth: true, recommendedHealthcheckIntervalHours: 24 }
  },
  "microsoft-graph": {
    id: "microsoft-graph",
    title: "Microsoft Graph OAuth",
    authMode: "oauth2",
    consoleUrl: "https://entra.microsoft.com/#view/Microsoft_AAD_RegisteredApps/ApplicationsListBlade",
    authorizationUrl: "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
    tokenUrl: "https://login.microsoftonline.com/common/oauth2/v2.0/token",
    redirectUriTemplate: "https://{host}/api/integrations/oauth/microsoft/callback",
    credentialFields: [
      { label: "Client ID", env: "MS_OAUTH_CLIENT_ID", description: "Microsoft Entra application client ID.", example: "00000000-0000-0000-0000-000000000000", regex: "^[0-9a-fA-F-]{36}$", secret: false },
      { label: "Client Secret", env: "MS_OAUTH_CLIENT_SECRET", description: "Microsoft Entra client secret value.", secret: true }
    ],
    consoleSteps: [
      { id: "app", title: "Register app", detail: "Create or open an app registration in Microsoft Entra." },
      { id: "redirect", title: "Add redirect URI", detail: "Add {redirectUri} as a Web redirect URI.", copyValue: "{redirectUri}" },
      { id: "secret", title: "Create secret", detail: "Create a client secret and store the secret value, not the secret ID." },
      { id: "permissions", title: "Add Graph permissions", detail: "Add the delegated Graph scopes listed in this spec and grant admin consent where required." }
    ],
    knownQuirks: [
      { id: "tenant-common", severity: "info", message: "The common tenant supports multi-tenant OAuth; single-tenant deployments should override the tenant segment." },
      { id: "admin-consent", severity: "warning", message: "Some Graph scopes require tenant admin consent." }
    ],
    lifecycle: { supportsRefresh: true, supportsRevoke: true, supportsIncrementalAuth: true, recommendedHealthcheckIntervalHours: 24 }
  },
  atlassian: {
    id: "atlassian",
    title: "Atlassian OAuth",
    authMode: "oauth2",
    consoleUrl: "https://developer.atlassian.com/console/myapps/",
    authorizationUrl: "https://auth.atlassian.com/authorize",
    tokenUrl: "https://auth.atlassian.com/oauth/token",
    redirectUriTemplate: "https://{host}/api/integrations/oauth/atlassian/callback",
    credentialFields: [
      { label: "Client ID", description: "Atlassian OAuth client ID.", secret: false },
      { label: "Client Secret", description: "Atlassian OAuth client secret.", secret: true }
    ],
    consoleSteps: [
      { id: "app", title: "Create OAuth app", detail: "Create an OAuth 2.0 app in the Atlassian developer console." },
      { id: "redirect", title: "Add callback URL", detail: "Add {redirectUri} as the callback URL.", copyValue: "{redirectUri}" },
      { id: "apis", title: "Enable APIs", detail: "Enable the Jira or Confluence APIs required by this connector." }
    ],
    lifecycle: { supportsRefresh: true, supportsRevoke: false, supportsIncrementalAuth: false, recommendedHealthcheckIntervalHours: 24 }
  },
  salesforce: {
    id: "salesforce",
    title: "Salesforce OAuth",
    authMode: "oauth2",
    consoleUrl: "https://login.salesforce.com",
    authorizationUrl: "https://login.salesforce.com/services/oauth2/authorize",
    tokenUrl: "https://login.salesforce.com/services/oauth2/token",
    redirectUriTemplate: "https://{host}/api/integrations/oauth/salesforce/callback",
    credentialFields: [
      { label: "Client ID", env: "SALESFORCE_OAUTH_CLIENT_ID", description: "Salesforce connected app consumer key.", secret: false },
      { label: "Client Secret", env: "SALESFORCE_OAUTH_CLIENT_SECRET", description: "Salesforce connected app consumer secret.", secret: true }
    ],
    consoleSteps: [
      { id: "connected-app", title: "Create connected app", detail: "Create a Salesforce connected app with OAuth enabled." },
      { id: "callback", title: "Add callback URL", detail: "Add {redirectUri} as the callback URL.", copyValue: "{redirectUri}" },
      { id: "scopes", title: "Select scopes", detail: "Add api and refresh_token/offline_access, plus any connector-specific scopes." }
    ],
    knownQuirks: [
      { id: "instance-url", severity: "critical", message: "Runtime calls must use the instance_url returned by the token response." }
    ],
    lifecycle: { supportsRefresh: true, supportsRevoke: true, supportsIncrementalAuth: false, recommendedHealthcheckIntervalHours: 24 }
  },
  hubspot: {
    id: "hubspot",
    title: "HubSpot OAuth",
    authMode: "oauth2",
    consoleUrl: "https://developers.hubspot.com/",
    authorizationUrl: "https://app.hubspot.com/oauth/authorize",
    tokenUrl: "https://api.hubapi.com/oauth/v1/token",
    redirectUriTemplate: "https://{host}/api/integrations/oauth/hubspot/callback",
    credentialFields: [
      { label: "Client ID", env: "HUBSPOT_OAUTH_CLIENT_ID", description: "HubSpot app client ID.", secret: false },
      { label: "Client Secret", env: "HUBSPOT_OAUTH_CLIENT_SECRET", description: "HubSpot app client secret.", secret: true }
    ],
    consoleSteps: [
      { id: "app", title: "Create private/public app", detail: "Create a HubSpot app and configure OAuth." },
      { id: "redirect", title: "Add redirect URL", detail: "Add {redirectUri} to the app redirect URLs.", copyValue: "{redirectUri}" },
      { id: "scopes", title: "Add CRM scopes", detail: "Add the CRM object scopes listed in this spec." }
    ],
    lifecycle: { supportsRefresh: true, supportsRevoke: true, supportsIncrementalAuth: false, recommendedHealthcheckIntervalHours: 24 }
  },
  slack: {
    id: "slack",
    title: "Slack OAuth",
    authMode: "oauth2",
    consoleUrl: "https://api.slack.com/apps",
    authorizationUrl: "https://slack.com/oauth/v2/authorize",
    tokenUrl: "https://slack.com/api/oauth.v2.access",
    redirectUriTemplate: "https://{host}/api/integrations/oauth/slack/callback",
    credentialFields: [
      { label: "Client ID", env: "SLACK_OAUTH_CLIENT_ID", description: "Slack app client ID.", secret: false },
      { label: "Client Secret", env: "SLACK_OAUTH_CLIENT_SECRET", description: "Slack app client secret.", secret: true }
    ],
    consoleSteps: [
      { id: "app", title: "Create Slack app", detail: "Create or open a Slack app." },
      { id: "redirect", title: "Add redirect URL", detail: "Add {redirectUri} under OAuth & Permissions.", copyValue: "{redirectUri}" },
      { id: "scopes", title: "Add bot scopes", detail: "Add the bot token scopes listed in this spec and reinstall the app." }
    ],
    knownQuirks: [
      { id: "bot-token", severity: "info", message: "Slack usually returns a bot access token; refresh tokens require token rotation." }
    ],
    lifecycle: { supportsRefresh: false, supportsRevoke: true, supportsIncrementalAuth: false, recommendedHealthcheckIntervalHours: 24 }
  },
  notion: {
    id: "notion",
    title: "Notion OAuth",
    authMode: "oauth2",
    consoleUrl: "https://www.notion.so/my-integrations",
    authorizationUrl: "https://api.notion.com/v1/oauth/authorize",
    tokenUrl: "https://api.notion.com/v1/oauth/token",
    redirectUriTemplate: "https://{host}/api/integrations/oauth/notion/callback",
    credentialFields: [
      { label: "Client ID", env: "NOTION_OAUTH_CLIENT_ID", description: "Notion integration OAuth client ID.", secret: false },
      { label: "Client Secret", env: "NOTION_OAUTH_CLIENT_SECRET", description: "Notion integration OAuth client secret.", secret: true }
    ],
    consoleSteps: [
      { id: "integration", title: "Create integration", detail: "Create a Notion public integration." },
      { id: "redirect", title: "Add redirect URI", detail: "Add {redirectUri} as the redirect URI.", copyValue: "{redirectUri}" },
      { id: "capabilities", title: "Select capabilities", detail: "Enable read/update/insert capabilities matching this connector." }
    ],
    lifecycle: { supportsRefresh: true, supportsRevoke: true, supportsIncrementalAuth: false, recommendedHealthcheckIntervalHours: 24 }
  },
  "standard-oauth2": {
    id: "standard-oauth2",
    title: "Standard OAuth 2.0",
    authMode: "oauth2",
    redirectUriTemplate: "https://{host}/api/integrations/oauth/{kind}/callback",
    credentialFields: [
      { label: "Client ID", description: "OAuth client ID.", secret: false },
      { label: "Client Secret", description: "OAuth client secret.", secret: true }
    ],
    consoleSteps: [
      { id: "app", title: "Create OAuth app", detail: "Create an OAuth app in the provider console." },
      { id: "redirect", title: "Add redirect URI", detail: "Add {redirectUri} as an allowed redirect URI.", copyValue: "{redirectUri}" },
      { id: "scopes", title: "Add scopes", detail: "Add the scopes listed in this spec." }
    ],
    lifecycle: { supportsRefresh: true, supportsRevoke: false, supportsIncrementalAuth: false, recommendedHealthcheckIntervalHours: 24 }
  },
  "api-key": {
    id: "api-key",
    title: "API key",
    authMode: "api_key",
    credentialFields: [
      { label: "API Key", description: "Provider API key or token.", example: "sk_...", secret: true }
    ],
    consoleSteps: [
      { id: "token", title: "Create token", detail: "Create an API key/token in the provider console with the minimum required permissions." }
    ],
    lifecycle: { supportsRefresh: false, supportsRevoke: true, supportsIncrementalAuth: false, recommendedHealthcheckIntervalHours: 24 }
  },
  hmac: {
    id: "hmac",
    title: "HMAC secret",
    authMode: "hmac",
    credentialFields: [
      { label: "Signing Secret", description: "Webhook signing secret.", secret: true }
    ],
    consoleSteps: [
      { id: "secret", title: "Configure signing secret", detail: "Configure the shared signing secret in the sender and receiver." }
    ],
    lifecycle: { supportsRefresh: false, supportsRevoke: true, supportsIncrementalAuth: false, recommendedHealthcheckIntervalHours: 24 }
  },
  none: {
    id: "none",
    title: "No authentication",
    authMode: "none",
    credentialFields: [],
    consoleSteps: [
      { id: "configure", title: "Configure endpoint", detail: "No provider credentials are required." }
    ],
    lifecycle: { supportsRefresh: false, supportsRevoke: false, supportsIncrementalAuth: false }
  }
};
function getIntegrationFamily(id) {
  return INTEGRATION_FAMILIES[id];
}

// src/specs/overrides.ts
var INTEGRATION_OVERRIDES = {
  // ── Stripe pack ────────────────────────────────────────────────────
  // Stripe issues two key types: secret keys (sk_*) and restricted keys
  // (rk_*). For voice-agent workloads, restricted keys are the right call
  // — least-privilege scoped to the specific resources the agent can
  // touch. The hint nudges operators toward that path.
  "stripe-pack": {
    consoleUrl: "https://dashboard.stripe.com/apikeys",
    credentialFields: [
      {
        label: "Stripe secret key",
        description: "Restricted key recommended. Dashboard \u2192 Developers \u2192 API keys \u2192 Create restricted key. Grant write access on Customers, Invoices, and Checkout Sessions.",
        example: "sk_live_\u2026 or rk_live_\u2026 (use sk_test_\u2026 / rk_test_\u2026 for staging)",
        regex: "^(sk|rk)_(live|test)_[A-Za-z0-9]+$",
        secret: true
      }
    ],
    consoleSteps: [
      {
        id: "open-keys",
        title: "Open Stripe API keys",
        detail: "Visit https://dashboard.stripe.com/apikeys",
        copyValue: "https://dashboard.stripe.com/apikeys"
      },
      {
        id: "create-restricted",
        title: "Create a restricted key",
        detail: 'Click "Create restricted key". Name it something descriptive (e.g. "ph0ny voice agent \u2014 prod"). Grant WRITE on Customers, Invoices, and Checkout Sessions. Leave everything else NONE.'
      },
      {
        id: "paste",
        title: "Paste the key",
        detail: "Copy the key Stripe shows once (rk_live_\u2026 or sk_live_\u2026). Paste it into ph0ny. The key is sealed before persistence."
      }
    ]
  },
  // ── Twilio SMS ─────────────────────────────────────────────────────
  // Twilio's REST API uses Basic auth with two parts: Account SID
  // (public-ish, AC…) + Auth Token (secret). The default api-key family
  // only exposes one field, which doesn't fit. Providing both fields
  // explicitly lets the consumer's UI render two inputs.
  "twilio-sms": {
    consoleUrl: "https://console.twilio.com/",
    credentialFields: [
      {
        label: "Account SID",
        description: "Your Twilio Account SID. Console \u2192 Account \u2192 API keys & tokens.",
        example: "AC\u2026 (34 hex chars)",
        regex: "^AC[a-f0-9]{32}$",
        secret: false
      },
      {
        label: "Auth Token",
        description: "Your Twilio Auth Token (or Standard API Key secret). Use a non-primary auth token in production so rotating it won't break other Twilio integrations.",
        secret: true
      }
    ],
    consoleSteps: [
      {
        id: "open",
        title: "Open Twilio console",
        detail: "Visit https://console.twilio.com/",
        copyValue: "https://console.twilio.com/"
      },
      {
        id: "find",
        title: "Find your Account SID + Auth Token",
        detail: "Account info is on the dashboard home. For better security, create a Standard API Key (Account \u2192 API keys & tokens \u2192 Create API Key) and use the SID + Secret pair instead of the primary auth token."
      },
      {
        id: "paste",
        title: "Paste both values",
        detail: "Account SID is non-secret; Auth Token is sealed before persistence."
      }
    ],
    knownQuirks: [
      {
        id: "subaccount-tokens",
        severity: "info",
        message: "If you use Twilio subaccounts, paste the SID/Token of the subaccount that owns the phone numbers your agent calls \u2014 not the master account."
      }
    ]
  },
  // ── ph0ny ──────────────────────────────────────────────────────────
  // ph0ny issues a single Bearer API key per developer. The key is a
  // `plabs_` prefix followed by 32 url-safe nanoid chars. It is shown once
  // at creation (POST /v1/keys in the developer portal); only its hash is
  // stored server-side, so it cannot be retrieved later — rotate to replace.
  phony: {
    consoleUrl: "https://api.ph0ny.com",
    credentialFields: [
      {
        label: "ph0ny API key",
        description: "Bearer key issued by ph0ny. Create one in the developer portal (POST /v1/keys). Sent as `Authorization: Bearer <key>`.",
        example: "plabs_V1StGXR8Z5jdHi6BmyTAbCdEfGhIjKlm",
        regex: "^plabs_[A-Za-z0-9_-]{32}$",
        secret: true
      }
    ],
    consoleSteps: [
      {
        id: "open-portal",
        title: "Open the ph0ny developer portal",
        detail: "Visit https://api.ph0ny.com and sign in to the developer portal.",
        copyValue: "https://api.ph0ny.com"
      },
      {
        id: "create-key",
        title: "Create an API key",
        detail: 'In the portal, create a new API key (POST /v1/keys). Give it a descriptive name (e.g. "tangle agent \u2014 prod"). ph0ny returns the full key exactly once.'
      },
      {
        id: "paste",
        title: "Paste the key",
        detail: "Copy the plabs_\u2026 key ph0ny shows and paste it here. The key is sealed before persistence."
      }
    ],
    knownQuirks: [
      {
        id: "key-shown-once",
        severity: "warning",
        message: "ph0ny stores only a hash of the key \u2014 the full plabs_\u2026 value is shown exactly once at creation and cannot be retrieved later. Save it immediately; if lost, rotate to issue a replacement."
      },
      {
        id: "rotate-endpoint",
        severity: "info",
        message: "Rotate a key with POST /v1/keys/:id/rotate. The old key is revoked and a new plabs_\u2026 key is returned once \u2014 update this connection with the new value."
      }
    ]
  }
};
function getIntegrationOverride(kind) {
  return INTEGRATION_OVERRIDES[kind];
}

// src/specs/registry.ts
var EXECUTABLE_KINDS = /* @__PURE__ */ new Set([
  "google-calendar",
  "google-sheets",
  "outlook-calendar",
  "microsoft-calendar",
  "slack",
  "hubspot",
  "notion",
  "notion-database",
  "salesforce",
  "github",
  "gitlab",
  "airtable",
  "asana",
  "stripe",
  "stripe-pack",
  "twilio",
  "twilio-sms",
  "phony",
  "webhook"
]);
var KIND_ALIASES = {
  "outlook-calendar": "microsoft-calendar",
  notion: "notion-database",
  stripe: "stripe-pack",
  twilio: "twilio-sms"
};
function listIntegrationSpecs() {
  const connectors = new Map(buildIntegrationCoverageConnectors({ providerId: "spec" }).map((c) => [c.id, c]));
  return listIntegrationCoverageSpecs().map((coverage) => {
    const connector = connectors.get(coverage.id);
    if (!connector) throw new Error(`missing coverage connector for ${coverage.id}`);
    return specFromCoverage(coverage, connector);
  });
}
function getIntegrationSpec(kind) {
  const canonical = KIND_ALIASES[kind] ?? kind;
  return listIntegrationSpecs().find((spec) => spec.kind === canonical || KIND_ALIASES[spec.kind] === canonical);
}
function resolveConnectorAuthSpec(kind) {
  const spec = getIntegrationSpec(kind);
  if (!spec) return void 0;
  const auth = spec.auth;
  if (auth.mode === "oauth2") {
    return {
      kind: spec.kind,
      authKind: "oauth2",
      requestedScopes: auth.scopes.map((scope) => scope.providerScope).filter(Boolean),
      authorizationUrl: auth.authorizationUrl,
      tokenUrl: auth.tokenUrl,
      pkce: auth.pkce,
      redirectUriTemplate: auth.redirectUriTemplate,
      clientIdEnv: auth.clientIdEnv,
      clientSecretEnv: auth.clientSecretEnv,
      extraAuthParams: auth.extraAuthParams
    };
  }
  if (auth.mode === "api_key") {
    return { kind: spec.kind, authKind: "api_key", requestedScopes: [] };
  }
  if (auth.mode === "none") {
    return { kind: spec.kind, authKind: "none", requestedScopes: [] };
  }
  return { kind: spec.kind, authKind: "custom", requestedScopes: [] };
}
function listExecutableIntegrationSpecs() {
  return listIntegrationSpecs().filter((spec) => spec.status === "executable");
}
function integrationSpecToConnector(spec, providerId = "spec") {
  return {
    id: spec.kind,
    providerId,
    title: spec.title,
    category: spec.category,
    auth: spec.auth.mode === "api_key" ? "api_key" : spec.auth.mode === "oauth2" ? "oauth2" : spec.auth.mode === "none" ? "none" : "custom",
    scopes: spec.permissions.flatMap((permission) => permission.providerScopes),
    actions: spec.actions,
    triggers: spec.triggers,
    metadata: {
      ...spec.metadata ?? {},
      source: "integration-spec",
      status: spec.status,
      family: spec.family,
      plannerHints: spec.plannerHints
    }
  };
}
function specFromCoverage(coverage, connector) {
  const kind = KIND_ALIASES[coverage.id] ?? coverage.id;
  const family = familyFor(coverage);
  const familySpec = getIntegrationFamily(family);
  const permissions = permissionsFor(coverage, connector.actions);
  const auth = authFor(coverage, family, permissions);
  const status = statusFor(kind);
  const override = getIntegrationOverride(kind) ?? getIntegrationOverride(coverage.id);
  const knownQuirks = override?.knownQuirks ? [...familySpec.knownQuirks ?? [], ...override.knownQuirks] : familySpec.knownQuirks;
  return {
    kind,
    title: connector.title,
    category: connector.category,
    status,
    family,
    auth,
    permissions,
    actions: connector.actions,
    triggers: connector.triggers,
    setup: {
      consoleUrl: override?.consoleUrl ?? familySpec.consoleUrl,
      consoleSteps: override?.consoleSteps ?? familySpec.consoleSteps,
      credentialFields: override?.credentialFields ?? credentialFieldsFor(auth),
      redirectUriTemplate: auth.mode === "oauth2" ? auth.redirectUriTemplate : familySpec.redirectUriTemplate,
      knownQuirks,
      postSetup: override?.postSetup,
      healthcheck: override?.healthcheck ?? healthcheckFor(kind, status, auth)
    },
    lifecycle: familySpec.lifecycle,
    plannerHints: plannerHintsFor(coverage, connector.actions),
    metadata: { priority: coverage.priority, domains: coverage.domains }
  };
}
function familyFor(spec) {
  if (hmacKinds.has(spec.id)) return "hmac";
  if (spec.auth === "none") return "none";
  if (spec.id.startsWith("google-") || spec.domains.includes("google")) return "google";
  if (spec.id.startsWith("microsoft-") || ["outlook-mail", "outlook-calendar", "onedrive", "sharepoint"].includes(spec.id)) return "microsoft-graph";
  if (["jira", "confluence", "trello", "bitbucket"].includes(spec.id)) return "atlassian";
  if (spec.id === "salesforce") return "salesforce";
  if (spec.id === "hubspot") return "hubspot";
  if (spec.id === "slack") return "slack";
  if (spec.id === "notion") return "notion";
  if (apiKeyKinds.has(spec.id)) return "api-key";
  return "standard-oauth2";
}
var apiKeyKinds = /* @__PURE__ */ new Set(["github", "gitlab", "airtable", "asana", "stripe", "twilio", "sendgrid", "postmark", "phony"]);
var hmacKinds = /* @__PURE__ */ new Set(["webhook"]);
function authFor(spec, family, permissions) {
  const f = INTEGRATION_FAMILIES[family];
  if (family === "none") return { mode: "none" };
  if (family === "hmac") {
    return { mode: "hmac", credential: f.credentialFields[0], signatureHeader: `${spec.id}-signature` };
  }
  if (family === "api-key") {
    return { mode: "api_key", credential: apiKeyFieldFor(spec.id), placement: apiKeyPlacementFor(spec.id) };
  }
  const scopes = permissions.flatMap(
    (permission) => permission.providerScopes.map((providerScope) => ({
      normalized: permission.normalized,
      providerScope,
      title: permission.title,
      reason: permission.reason,
      risk: permission.risk,
      dataClass: permission.dataClass
    }))
  );
  return {
    mode: "oauth2",
    authorizationUrl: f.authorizationUrl ?? `https://example.invalid/${spec.id}/authorize`,
    tokenUrl: f.tokenUrl ?? `https://example.invalid/${spec.id}/token`,
    clientIdEnv: f.credentialFields.find((field) => !field.secret)?.env,
    clientSecretEnv: f.credentialFields.find((field) => field.secret)?.env,
    scopes,
    extraAuthParams: extraAuthParamsFor(family),
    redirectUriTemplate: (f.redirectUriTemplate ?? "https://{host}/api/integrations/oauth/{kind}/callback").replace("{kind}", spec.id),
    pkce: family === "google" || family === "microsoft-graph" ? "supported" : "unsupported"
  };
}
function credentialFieldsFor(auth) {
  if (auth.mode === "api_key" || auth.mode === "hmac") return [auth.credential];
  if (auth.mode === "oauth2") {
    return [
      { label: "Client ID", env: auth.clientIdEnv, description: "OAuth client ID.", secret: false },
      { label: "Client Secret", env: auth.clientSecretEnv, description: "OAuth client secret.", secret: true }
    ];
  }
  return [];
}
function permissionsFor(spec, actions) {
  const dataClass = dataClassFor(actions);
  const readScope = providerScopeFor(spec, "read");
  const writeScope = providerScopeFor(spec, "write");
  const permissions = [
    {
      normalized: `${spec.actionPack}.read`,
      providerScopes: readScope ? [readScope] : [],
      title: `${spec.title} read`,
      risk: "read",
      dataClass,
      reason: `Read ${spec.title} data for user-authorized agent workflows.`
    }
  ];
  if (actions.some((a) => a.risk !== "read")) {
    permissions.push({
      normalized: `${spec.actionPack}.write`,
      providerScopes: writeScope ? [writeScope] : [],
      title: `${spec.title} write`,
      risk: "write",
      dataClass,
      reason: `Create or update ${spec.title} resources after policy approval.`
    });
  }
  return permissions;
}
function providerScopeFor(spec, mode) {
  const explicit = explicitScopes[spec.id]?.[mode];
  if (explicit) return explicit;
  if (spec.auth === "none") return "";
  return `${spec.id}.${mode}`;
}
var explicitScopes = {
  gmail: { read: "https://www.googleapis.com/auth/gmail.readonly", write: "https://www.googleapis.com/auth/gmail.modify" },
  "google-calendar": { read: "https://www.googleapis.com/auth/calendar.readonly", write: "https://www.googleapis.com/auth/calendar" },
  "google-sheets": { read: "https://www.googleapis.com/auth/spreadsheets.readonly", write: "https://www.googleapis.com/auth/spreadsheets" },
  "google-drive": { read: "https://www.googleapis.com/auth/drive.readonly", write: "https://www.googleapis.com/auth/drive.file" },
  "google-docs": { read: "https://www.googleapis.com/auth/documents.readonly", write: "https://www.googleapis.com/auth/documents" },
  "outlook-mail": { read: "Mail.Read", write: "Mail.Send" },
  "outlook-calendar": { read: "Calendars.Read", write: "Calendars.ReadWrite" },
  "microsoft-teams": { read: "ChannelMessage.Read.All", write: "ChannelMessage.Send" },
  onedrive: { read: "Files.Read", write: "Files.ReadWrite" },
  sharepoint: { read: "Sites.Read.All", write: "Sites.ReadWrite.All" },
  slack: { read: "channels:read", write: "chat:write" },
  hubspot: { read: "crm.objects.contacts.read", write: "crm.objects.contacts.write" },
  salesforce: { read: "api", write: "api" },
  notion: { read: "", write: "" },
  github: { read: "repo:read", write: "repo" },
  gitlab: { read: "read_api", write: "api" },
  airtable: { read: "data.records:read", write: "data.records:write" },
  asana: { read: "default", write: "default" },
  stripe: { read: "read_only", write: "standard" },
  twilio: { read: "api_key", write: "api_key" }
};
function plannerHintsFor(spec, actions) {
  return {
    useFor: spec.domains.map((domain) => domain.replace(/-/g, " ")),
    dataFreshness: ["calendar", "chat", "commerce", "finance", "support"].includes(spec.actionPack) ? "near_realtime" : "eventual",
    writeRisk: actions.some((a) => a.risk === "destructive") ? "high" : actions.some((a) => a.risk === "write") ? "medium" : "low"
  };
}
function healthcheckFor(kind, status, auth) {
  if (status !== "executable") {
    return { id: `${kind}.static`, level: "static", description: "Catalog-only integration; no executable connector healthcheck is available yet." };
  }
  if (auth.mode === "oauth2") {
    return { id: `${kind}.connection`, level: "connection", description: "Validate a user connection by calling the connector test endpoint." };
  }
  if (auth.mode === "api_key") {
    return { id: `${kind}.connection`, level: "connection", description: "Validate API credentials by calling the connector test endpoint." };
  }
  if (auth.mode === "hmac") {
    return { id: `${kind}.webhook`, level: "webhook", description: "Validate webhook signing configuration with a signed test payload." };
  }
  return { id: `${kind}.static`, level: "static", description: "No credentials are required." };
}
function statusFor(kind) {
  return EXECUTABLE_KINDS.has(kind) ? "executable" : "catalog";
}
function dataClassFor(actions) {
  if (actions.some((a) => a.dataClass === "secret")) return "secret";
  if (actions.some((a) => a.dataClass === "sensitive")) return "sensitive";
  if (actions.some((a) => a.dataClass === "private")) return "private";
  if (actions.some((a) => a.dataClass === "internal")) return "internal";
  return "public";
}
function apiKeyFieldFor(kind) {
  return {
    label: `${kind} API key`,
    description: `API key or token for ${kind}.`,
    example: kind === "stripe" ? "sk_live_..." : void 0,
    secret: true
  };
}
function apiKeyPlacementFor(kind) {
  if (kind === "gitlab") return "header";
  return "bearer";
}
function extraAuthParamsFor(family) {
  if (family === "google") return { access_type: "offline", prompt: "consent", include_granted_scopes: "true" };
  if (family === "notion") return { owner: "user" };
  return void 0;
}

// src/specs/renderers.ts
function renderConsoleSteps(spec, options) {
  const redirectUri = renderRedirectUri(spec, options);
  return spec.setup.consoleSteps.map((step) => ({
    ...step,
    detail: renderTemplate(step.detail, spec, options, redirectUri),
    copyValue: step.copyValue ? renderTemplate(step.copyValue, spec, options, redirectUri) : void 0
  }));
}
function renderRunbookMarkdown(spec, options) {
  const steps = renderConsoleSteps(spec, options);
  const lines = [
    `# ${spec.title} Integration Setup`,
    "",
    `- Kind: \`${spec.kind}\``,
    `- Status: \`${spec.status}\``,
    `- Auth: \`${spec.auth.mode}\``,
    `- Family: \`${spec.family}\``
  ];
  if (spec.setup.consoleUrl) lines.push(`- Console: ${spec.setup.consoleUrl}`);
  if (spec.setup.redirectUriTemplate) lines.push(`- Redirect URI: \`${renderRedirectUri(spec, options)}\``);
  lines.push("", "## Credentials", "");
  for (const field of spec.setup.credentialFields) {
    lines.push(`- ${field.secret ? "[secret] " : ""}${field.label}${field.env ? ` (\`${field.env}\`)` : ""}: ${field.description}`);
  }
  lines.push("", "## Permissions", "");
  for (const permission of spec.permissions) {
    lines.push(`- \`${permission.normalized}\`: ${permission.providerScopes.length ? permission.providerScopes.map((scope) => `\`${scope}\``).join(", ") : "no provider scope"} - ${permission.reason}`);
  }
  lines.push("", "## Console Steps", "");
  for (const [i, step] of steps.entries()) {
    lines.push(`${i + 1}. ${step.title}: ${step.detail}`);
  }
  if (spec.setup.knownQuirks?.length) {
    lines.push("", "## Known Quirks", "");
    for (const quirk of spec.setup.knownQuirks) lines.push(`- ${quirk.severity}: ${quirk.message}`);
  }
  return `${lines.join("\n")}
`;
}
function renderAgentToolDescription(spec) {
  const hints = spec.plannerHints;
  const useFor = hints?.useFor?.length ? `Use for ${hints.useFor.join(", ")}.` : `Use for ${spec.title} workflows.`;
  const risk = hints ? `Freshness: ${hints.dataFreshness}. Write risk: ${hints.writeRisk}.` : "";
  return `${spec.title} (${spec.kind}). ${useFor} ${risk}`.trim();
}
function buildHealthcheckPlan(spec) {
  const healthcheck = spec.setup.healthcheck ?? { id: `${spec.kind}.static`, level: "static", description: "No healthcheck defined." };
  const requires = [];
  if (healthcheck.level === "connection") requires.push("connection_credentials");
  if (healthcheck.level === "client_config" && spec.auth.mode === "oauth2") requires.push("client_id", "client_secret");
  if (spec.auth.mode === "api_key") requires.push("api_key");
  if (spec.auth.mode === "hmac") requires.push("hmac_secret");
  return {
    kind: spec.kind,
    healthcheck,
    requires,
    message: healthcheck.description
  };
}
function renderTemplate(template, spec, options, redirectUri) {
  return template.replaceAll("{host}", options.host).replaceAll("{kind}", spec.kind).replaceAll("{redirectUri}", redirectUri ?? renderRedirectUri(spec, options));
}
function renderRedirectUri(spec, options) {
  return (options.callbackPath ?? spec.setup.redirectUriTemplate ?? "").replaceAll("{host}", options.host).replaceAll("{kind}", spec.kind);
}
function consoleStepsToText(steps) {
  return steps.map((step, index) => `${index + 1}. ${step.title}: ${step.detail}`).join("\n");
}

// src/specs/validation.ts
function validateIntegrationSpec(spec) {
  const issues = [];
  if (!spec.kind.trim()) issues.push({ path: "kind", message: "kind is required" });
  if (!spec.title.trim()) issues.push({ path: "title", message: "title is required" });
  if (!spec.actions.length) issues.push({ path: "actions", message: "at least one action is required" });
  if (!spec.permissions.length) issues.push({ path: "permissions", message: "at least one permission is required" });
  if (spec.auth.mode === "oauth2") {
    if (!spec.auth.authorizationUrl) issues.push({ path: "auth.authorizationUrl", message: "authorizationUrl is required" });
    if (!spec.auth.tokenUrl) issues.push({ path: "auth.tokenUrl", message: "tokenUrl is required" });
    if (!spec.auth.redirectUriTemplate) issues.push({ path: "auth.redirectUriTemplate", message: "redirectUriTemplate is required" });
  }
  const actionIds = /* @__PURE__ */ new Set();
  for (const [index, action] of spec.actions.entries()) {
    if (actionIds.has(action.id)) issues.push({ path: `actions[${index}].id`, message: `duplicate action id ${action.id}` });
    actionIds.add(action.id);
  }
  return { ok: issues.length === 0, issues };
}
function assertValidIntegrationSpec(spec) {
  const result = validateIntegrationSpec(spec);
  if (!result.ok) {
    throw new Error(`Invalid integration spec ${spec.kind}: ${result.issues.map((i) => `${i.path}: ${i.message}`).join("; ")}`);
  }
}
function validateCredentialFormat(field, value) {
  if (!value.trim()) return { ok: false, field: field.label, message: `${field.label} is required` };
  if (field.regex && !new RegExp(field.regex).test(value)) {
    return { ok: false, field: field.label, message: `${field.label} does not match expected format` };
  }
  return { ok: true, field: field.label };
}
function validateCredentialSet(spec, values) {
  return spec.setup.credentialFields.map((field) => {
    const key = field.env ?? field.label;
    return validateCredentialFormat(field, values[key] ?? "");
  });
}

export {
  INTEGRATION_FAMILIES,
  getIntegrationFamily,
  listIntegrationSpecs,
  getIntegrationSpec,
  resolveConnectorAuthSpec,
  listExecutableIntegrationSpecs,
  integrationSpecToConnector,
  specAuthToConnectorAuth,
  renderConsoleSteps,
  renderRunbookMarkdown,
  renderAgentToolDescription,
  buildHealthcheckPlan,
  consoleStepsToText,
  validateIntegrationSpec,
  assertValidIntegrationSpec,
  validateCredentialFormat,
  validateCredentialSet
};
//# sourceMappingURL=chunk-53NQJZAT.js.map