@integrity-labs/agt-cli 0.28.778 → 0.28.779

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -39067,6 +39067,459 @@ var ASSET_TYPES = {
39067
39067
  var DEFAULT_CHUNK_BUDGET_BYTES = 180 * 1024;
39068
39068
  var textEncoder = new TextEncoder();
39069
39069
 
39070
+ // ../core/dist/integrations/oauth-providers.js
39071
+ var OAUTH_PROVIDERS = {
39072
+ "google-workspace": {
39073
+ definitionId: "google-workspace",
39074
+ authorizeUrl: "https://accounts.google.com/o/oauth2/v2/auth",
39075
+ tokenUrl: "https://oauth2.googleapis.com/token",
39076
+ revokeUrl: "https://oauth2.googleapis.com/revoke",
39077
+ defaultScopes: [
39078
+ "https://www.googleapis.com/auth/gmail.modify",
39079
+ "https://www.googleapis.com/auth/calendar",
39080
+ "https://www.googleapis.com/auth/drive",
39081
+ "https://www.googleapis.com/auth/spreadsheets",
39082
+ "https://www.googleapis.com/auth/documents",
39083
+ "https://www.googleapis.com/auth/chat.messages",
39084
+ "https://www.googleapis.com/auth/chat.spaces.readonly"
39085
+ ],
39086
+ supportsRefresh: true,
39087
+ extraAuthorizeParams: {
39088
+ access_type: "offline",
39089
+ prompt: "consent"
39090
+ },
39091
+ clientAuthMethod: "body",
39092
+ userInfoUrl: "https://www.googleapis.com/oauth2/v2/userinfo"
39093
+ },
39094
+ "github": {
39095
+ definitionId: "github",
39096
+ authorizeUrl: "https://github.com/login/oauth/authorize",
39097
+ tokenUrl: "https://github.com/login/oauth/access_token",
39098
+ // ENG-6187: revoke the existing grant on reconnect so GitHub re-prompts and
39099
+ // the four scopes below are actually granted. Without this, a stale narrow
39100
+ // grant (e.g. an old read:user-only authorization) is silently re-issued and
39101
+ // the missing-scopes banner loops forever.
39102
+ grantRevokeUrl: "https://api.github.com/applications/{client_id}/grant",
39103
+ defaultScopes: ["repo", "read:org", "gist", "workflow"],
39104
+ supportsRefresh: true,
39105
+ extraAuthorizeParams: {},
39106
+ clientAuthMethod: "body",
39107
+ userInfoUrl: "https://api.github.com/user"
39108
+ },
39109
+ "granola": {
39110
+ // Granola MCP — remote streamable-HTTP at https://mcp.granola.ai/mcp.
39111
+ // The AS is at mcp-auth.granola.ai and exposes RFC 8414 metadata at
39112
+ // /.well-known/oauth-authorization-server. Auth is OAuth 2.0 with
39113
+ // mandatory PKCE (S256) and a public client (no client_secret) issued
39114
+ // via Dynamic Client Registration (RFC 7591). The bootstrap script
39115
+ // (`packages/api/scripts/dcr-register.ts`) registers a client once at
39116
+ // deploy time; OAUTH_GRANOLA_CLIENT_ID is set from its output.
39117
+ definitionId: "granola",
39118
+ authorizeUrl: "https://mcp-auth.granola.ai/oauth2/authorize",
39119
+ tokenUrl: "https://mcp-auth.granola.ai/oauth2/token",
39120
+ // Minimal scope set: `offline_access` earns the refresh_token so the
39121
+ // refresh cron can rotate the bearer without operator action; `openid`
39122
+ // is required for the OIDC code flow even when we don't request an
39123
+ // id_token. Profile/email are intentionally omitted — we have no
39124
+ // userInfoUrl wired up here, so requesting them would over-ask consent
39125
+ // for fields the callback can't read.
39126
+ defaultScopes: ["openid", "offline_access"],
39127
+ supportsRefresh: true,
39128
+ extraAuthorizeParams: {},
39129
+ clientAuthMethod: "body",
39130
+ pkce: "S256",
39131
+ publicClient: true,
39132
+ mcpUrl: "https://mcp.granola.ai/mcp",
39133
+ // Curated surface (matches the catalog seed's defined_scopes[].tools).
39134
+ toolAllowlist: ["search-meetings", "read-transcript", "read-summary", "list-folders"]
39135
+ },
39136
+ "brand-ninja": {
39137
+ // ENG-6820: Brand Ninja External-Content MCP, remote streamable-HTTP at
39138
+ // https://ext-api.app.brandninja.ai/v1/mcp. Same shape as Granola: the
39139
+ // server exposes RFC 8414 authorization-server metadata at
39140
+ // /.well-known/oauth-authorization-server (values below are taken verbatim
39141
+ // from that document, not inferred). Auth is OAuth 2.0 authorization-code
39142
+ // with mandatory PKCE (S256) and a public client (token_endpoint_auth_method
39143
+ // 'none') issued via Dynamic Client Registration (RFC 7591). The bootstrap
39144
+ // script (packages/api/scripts/dcr-register.ts) registers a client once at
39145
+ // deploy time against the registration_endpoint
39146
+ // (https://ext-api.app.brandninja.ai/v1/oauth/register); OAUTH_BRAND_NINJA_CLIENT_ID
39147
+ // is set from its output. The AS advertises the refresh_token grant, so the
39148
+ // shared oauth-refresh cron rotates the bearer without operator action.
39149
+ definitionId: "brand-ninja",
39150
+ authorizeUrl: "https://prod-brandninja.auth.ap-southeast-2.amazoncognito.com/oauth2/authorize",
39151
+ tokenUrl: "https://prod-brandninja.auth.ap-southeast-2.amazoncognito.com/oauth2/token",
39152
+ // The resource server (ext-api.app.brandninja.ai) advertises exactly two
39153
+ // scopes: external-api/content.write (the default content surface) and
39154
+ // external-api/admin (read-only credential metadata, granted per account
39155
+ // admin). Default install is least-privilege: content.write only; an
39156
+ // operator can widen to admin out of band. No openid/offline_access in the
39157
+ // advertised scope set, so Cognito issues the refresh_token for the code grant
39158
+ // regardless, so requesting only the resource scope keeps consent minimal.
39159
+ defaultScopes: ["external-api/content.write"],
39160
+ supportsRefresh: true,
39161
+ extraAuthorizeParams: {},
39162
+ clientAuthMethod: "body",
39163
+ pkce: "S256",
39164
+ publicClient: true,
39165
+ mcpUrl: "https://ext-api.app.brandninja.ai/v1/mcp",
39166
+ // Curated surface (matches the catalog seed's defined_scopes[].tools).
39167
+ toolAllowlist: [
39168
+ "submit_content_request",
39169
+ "get_content_status",
39170
+ "list_content_requests",
39171
+ "list_channels",
39172
+ "list_brands",
39173
+ "list_topics",
39174
+ "list_timeline_templates",
39175
+ "list_content_types",
39176
+ "list_source_skills",
39177
+ "list_output_skills",
39178
+ "list_conversion_skills",
39179
+ "list_credentials",
39180
+ "search_transcripts",
39181
+ "create_source_clips",
39182
+ "create_timeline_ranking",
39183
+ "create_topic",
39184
+ "create_timeline_template",
39185
+ "create_knowledge",
39186
+ "link_knowledge"
39187
+ ]
39188
+ },
39189
+ "kajabi": {
39190
+ // Kajabi MCP — remote streamable-HTTP at https://mcp.kajabi.com/mcp.
39191
+ // Same Granola/Brand-Ninja shape: OAuth 2.0 authorization-code with
39192
+ // mandatory PKCE (S256) and a public client (token_endpoint_auth_method
39193
+ // 'none') issued via Dynamic Client Registration (RFC 7591). Values below
39194
+ // are taken verbatim from Kajabi's RFC 8414 metadata at
39195
+ // https://mcp.kajabi.com/.well-known/oauth-authorization-server (a Rails
39196
+ // Doorkeeper AS), not inferred. NOTE the authorize host differs from the
39197
+ // token host: authorize is on app.kajabi.com (the login surface), while
39198
+ // token/register/revoke are on mcp.kajabi.com — do NOT "normalise" them to
39199
+ // one host. The bootstrap script (packages/api/scripts/dcr-register.ts)
39200
+ // registers a client once at deploy time against
39201
+ // https://mcp.kajabi.com/mcp/oauth/register; OAUTH_KAJABI_CLIENT_ID is set
39202
+ // from its output. Doorkeeper RESTRICTS a dynamic client to the scopes it
39203
+ // registered with, so register with at least the union of defaultScopes
39204
+ // below (--scope 'read write:contacts write:emails write:content
39205
+ // write:commerce'). Widening defaultScopes forces a client re-register
39206
+ // (new client_id) AND a per-connection re-consent: existing tokens keep
39207
+ // the old scope set and their refreshes fail under the new client_id, so
39208
+ // each connection flips to needs_reauth until the user re-runs Connect
39209
+ // (ENG-7483). The AS advertises the refresh_token grant (no
39210
+ // openid/offline_access scope needed), so the shared oauth-refresh cron
39211
+ // rotates the bearer without operator action.
39212
+ definitionId: "kajabi",
39213
+ authorizeUrl: "https://app.kajabi.com/mcp/oauth/authorize",
39214
+ tokenUrl: "https://mcp.kajabi.com/mcp/oauth/token",
39215
+ revokeUrl: "https://mcp.kajabi.com/mcp/oauth/revoke",
39216
+ // Coarse Doorkeeper scopes (NOT openid-style). Cross-domain reads (`read`)
39217
+ // plus the write surfaces this integration ships: contact tags/segments,
39218
+ // email broadcasts/sequences, and course updates (write:content gates
39219
+ // update_course, the course-thumbnail path, ENG-7483). write:commerce is
39220
+ // requested now (Brad's call on ENG-7483) so a later offer/pricing-write
39221
+ // enablement needs no extra re-consent round; the tool allowlist below
39222
+ // still exposes no commerce write, so the callable surface stays minimal.
39223
+ defaultScopes: ["read", "write:contacts", "write:emails", "write:content", "write:commerce"],
39224
+ supportsRefresh: true,
39225
+ extraAuthorizeParams: {},
39226
+ clientAuthMethod: "body",
39227
+ pkce: "S256",
39228
+ publicClient: true,
39229
+ mcpUrl: "https://mcp.kajabi.com/mcp",
39230
+ // Curated surface (matches the catalog seed's defined_scopes[].tools). Kajabi's
39231
+ // live MCP advertises ~111 tools; the agent only needs these 28.
39232
+ // CS-1427: `enable_toolset` is included so an agent can activate its own
39233
+ // products/commerce + analytics toolsets on the connection (Kajabi gates those
39234
+ // tool groups behind a runtime toolset that returns "not active" until enabled).
39235
+ // Low-risk: this proxy still caps every tools/call to the allowlist, so a broad
39236
+ // activation can't widen what's actually callable.
39237
+ // ENG-7483: `get_course`/`update_course` enable the course-thumbnail refresh
39238
+ // (update_course covers title/description/thumbnail per Kajabi's MCP docs).
39239
+ // Fail-safe on naming drift: an allowlisted tool the server doesn't advertise
39240
+ // is logged and skipped by the proxy, never a break.
39241
+ toolAllowlist: [
39242
+ "list_sites",
39243
+ "get_site_summary",
39244
+ "select_site",
39245
+ "search_contacts",
39246
+ "get_contact",
39247
+ "search_products",
39248
+ "get_product",
39249
+ "list_offers",
39250
+ "get_offer",
39251
+ "list_offer_purchases",
39252
+ "get_revenue_analytics",
39253
+ "get_contacts_analytics",
39254
+ "list_tags",
39255
+ "create_tag",
39256
+ "tag_contact",
39257
+ "untag_contact",
39258
+ "list_segments",
39259
+ "create_segment",
39260
+ "update_segment",
39261
+ "list_broadcasts",
39262
+ "get_broadcast",
39263
+ "create_broadcast",
39264
+ "list_sequences",
39265
+ "get_sequence",
39266
+ "create_sequence",
39267
+ "enable_toolset",
39268
+ "get_course",
39269
+ "update_course",
39270
+ // ENG-7629 follow-up: pages (Dee's landing-page edit) + themes (CS-1448
39271
+ // theme-builder broadcast content). Callable via the proxy union manifest
39272
+ // + per-call enable; matches the new kajabi:pages catalog scope.
39273
+ "list_landing_pages",
39274
+ "get_landing_page",
39275
+ "update_landing_page",
39276
+ "get_theme_content"
39277
+ ],
39278
+ // CS-1446: Kajabi gates several tool groups behind a runtime `enable_toolset`
39279
+ // and does not advertise their tools in `tools/list` until the group is
39280
+ // active. The harness freezes its callable-tool manifest from the
39281
+ // connect-time `tools/list`, so a group the agent activates later never
39282
+ // becomes callable (repro: enable_toolset('courses') → get_course/
39283
+ // update_course still "No such tool available"). Pre-activate the gated
39284
+ // groups whose tools we allowlist so they are advertised in that first
39285
+ // manifest. Names are Kajabi's toolset ids (courses per CS-1446;
39286
+ // products/commerce/analytics per CS-1427). Best-effort + idempotent: an
39287
+ // unknown/unscoped name is logged and skipped by the proxy, and the
39288
+ // toolAllowlist above still caps what is actually callable.
39289
+ //
39290
+ // ENG-8392: `contacts` + `emails` were missing, so thirteen allowlisted
39291
+ // tools were unreachable by construction — list_tags, create_tag,
39292
+ // tag_contact, untag_contact, list_segments, create_segment,
39293
+ // update_segment (contacts) and list_broadcasts, get_broadcast,
39294
+ // create_broadcast, list_sequences, get_sequence, create_sequence
39295
+ // (emails). A customer agent was blocked on exactly those.
39296
+ //
39297
+ // These two ids are Kajabi's OWN spelling, not ours: the server names the
39298
+ // toolset in its rejection, e.g. `list_tags` returns
39299
+ // "The 'contacts' toolset is not active. Call enable_toolset with
39300
+ // name: 'contacts' to activate it first."
39301
+ // and `list_sequences` returns the same with 'emails'. Sourced that way
39302
+ // deliberately — a wrong id here is logged and SKIPPED by the proxy, so a
39303
+ // guess would look applied and change nothing.
39304
+ //
39305
+ // Why this went unnoticed for four rounds: `get_contacts_analytics` lives
39306
+ // in the `analytics` group, which we already pre-enable. So the contacts
39307
+ // family read as partly alive (you could see a site's contact count and
39308
+ // growth curve) while every tag and segment call failed.
39309
+ preEnableToolsets: [
39310
+ "courses",
39311
+ "products",
39312
+ "commerce",
39313
+ "analytics",
39314
+ "pages",
39315
+ "themes",
39316
+ "contacts",
39317
+ "emails"
39318
+ ],
39319
+ // ENG-8512: `update_course.thumbnail` stores a RELATIVE path into Kajabi's
39320
+ // own storage — it is rendered as
39321
+ // https://kajabi-storefronts-production.kajabi-cdn.com/kajabi-storefronts-production/<value>
39322
+ // so an absolute http(s) URL is structurally not a valid value for it.
39323
+ // Kajabi accepts one anyway, returns 200, echoes it back, ingests nothing,
39324
+ // and the course shows a broken image. Reported from a live client site
39325
+ // (DTI) via CS-1548 after it produced a false "done" report and three
39326
+ // failed cards.
39327
+ //
39328
+ // Anchored at the start so only an ABSOLUTE URL is refused; every relative
39329
+ // path — the valid shape — passes untouched. This does not enable setting a
39330
+ // thumbnail from a URL (that needs an upload path, CS-1548 item 4). It stops
39331
+ // us reporting success for a write that did not happen.
39332
+ argRejects: [
39333
+ {
39334
+ tool: "update_course",
39335
+ field: "thumbnail",
39336
+ pattern: "^\\s*https?://",
39337
+ message: "thumbnail takes a relative path inside Kajabi storage, not an external URL. Kajabi accepts a URL here, returns 200 and never ingests it, so the image would silently stay broken. Upload the asset to Kajabi first and pass the storage path it returns."
39338
+ },
39339
+ // CS-1554: `search_contacts` accepts `tag_id`, does not apply it, and
39340
+ // returns the FULL unfiltered contact list with `filters_applied: null`.
39341
+ // Reported first-hand from a live client site (DTI) while auditing which
39342
+ // contacts carried an opt-in tag. Corroborating evidence: Kajabi's own
39343
+ // REST reference filters contacts with `filter[has_tag_id]`, not a bare
39344
+ // `tag_id` — so the argument is dropped as unrecognised and the response
39345
+ // says so, if you read `filters_applied`.
39346
+ //
39347
+ // Worse than the thumbnail case it sits next to. A broken image is
39348
+ // visible; an unfiltered list that reads as filtered produces a confident
39349
+ // wrong ANSWER, which a human then acts on. That is the same shape as the
39350
+ // incident in CS-1554 where the agent told the customer content did not
39351
+ // exist when it did.
39352
+ //
39353
+ // Any value at all is refused: the field is not partially supported, it
39354
+ // is inert. `[\s\S]` matches every stringified scalar including 0 and
39355
+ // false, which a `.`-style pattern anchored on truthiness would miss.
39356
+ //
39357
+ // NOT REPRODUCED BY ME — I have no Kajabi connection. This rests on the
39358
+ // CS-1554 first-hand report plus the REST reference. The file's own bar is
39359
+ // "provably wrong, never a guess", so a reviewer with a live Kajabi
39360
+ // connection should confirm before this merges. If `tag_id` DOES filter
39361
+ // for some tenant, drop this rule and keep the scalar fix.
39362
+ {
39363
+ tool: "search_contacts",
39364
+ field: "tag_id",
39365
+ pattern: "[\\s\\S]",
39366
+ message: "tag_id is accepted by Kajabi and never applied \u2014 the call returns the FULL contact list with filters_applied: null, which reads as a filtered result and is not one. There is no working tag filter on this tool today. Use list_tags plus the per-contact tag data to determine membership, and treat any tag audit built on search_contacts(tag_id) as unfiltered."
39367
+ }
39368
+ ]
39369
+ },
39370
+ "notion-cli": {
39371
+ // Notion's public OAuth app. Tokens are workspace-scoped and long-lived —
39372
+ // Notion does not issue refresh_tokens, so `supportsRefresh: false` and
39373
+ // the refresh cron skips this provider entirely. Scopes are not part of
39374
+ // Notion's authorize URL contract; consent is governed by what the user
39375
+ // grants in the OAuth screen, so `defaultScopes` stays empty.
39376
+ // `owner=user` forces the user-OAuth variant (vs internal integration).
39377
+ // Requires OAUTH_NOTION_CLI_CLIENT_ID and OAUTH_NOTION_CLI_CLIENT_SECRET.
39378
+ definitionId: "notion-cli",
39379
+ authorizeUrl: "https://api.notion.com/v1/oauth/authorize",
39380
+ tokenUrl: "https://api.notion.com/v1/oauth/token",
39381
+ defaultScopes: [],
39382
+ supportsRefresh: false,
39383
+ extraAuthorizeParams: {
39384
+ owner: "user"
39385
+ },
39386
+ clientAuthMethod: "basic"
39387
+ },
39388
+ "xero": {
39389
+ definitionId: "xero",
39390
+ authorizeUrl: "https://login.xero.com/identity/connect/authorize",
39391
+ tokenUrl: "https://identity.xero.com/connect/token",
39392
+ revokeUrl: "https://identity.xero.com/connect/revocation",
39393
+ defaultScopes: [
39394
+ "openid",
39395
+ "profile",
39396
+ "email",
39397
+ "offline_access",
39398
+ // Granular scopes (required for apps created after March 2, 2026 —
39399
+ // do NOT revert to the broad `accounting.transactions` /
39400
+ // `accounting.contacts` scopes, Xero rejects the manifest).
39401
+ // The variant *without* `.read` is the read+write granular scope.
39402
+ "accounting.settings.read",
39403
+ // contacts: write enables agent-driven supplier/customer creation
39404
+ // (required for bill creation since a bill must reference a contact).
39405
+ "accounting.contacts",
39406
+ // invoices: write enables bill creation (Type=ACCPAY invoices) and
39407
+ // updates to sales invoices alongside the existing read access.
39408
+ "accounting.invoices",
39409
+ // attachments: write enables agents to attach the source PDF to a
39410
+ // bill at creation time. Read-only would force a follow-up manual
39411
+ // upload in Xero; write closes the loop.
39412
+ "accounting.attachments",
39413
+ // accounting.transactions.read → granular read-only replacements
39414
+ // for the surfaces we don't yet need write access on.
39415
+ "accounting.payments.read",
39416
+ "accounting.banktransactions.read",
39417
+ // manualjournals: write (CS-1439) enables the xero-broker to post
39418
+ // manual journals (reclasses, accruals, corrections) on approval. The
39419
+ // variant without `.read` is the read+write granular scope. Note: an
39420
+ // existing Xero install must RECONNECT for its token to carry this — the
39421
+ // broker posts journals with the agent's own OAuth access_token.
39422
+ "accounting.manualjournals",
39423
+ // accounting.reports.read → granular read-only replacements
39424
+ "accounting.reports.balancesheet.read",
39425
+ "accounting.reports.profitandloss.read",
39426
+ "accounting.reports.trialbalance.read",
39427
+ "accounting.reports.budgetsummary.read",
39428
+ "accounting.reports.banksummary.read",
39429
+ "accounting.reports.executivesummary.read",
39430
+ "accounting.reports.aged.read"
39431
+ ],
39432
+ // Read-only variant (ENG-6170): the `.read` granular scope for every
39433
+ // surface, so a token granted under it cannot create bills/invoices or
39434
+ // mutate contacts/attachments. Used when an install is (re)connected with
39435
+ // `read_only: true` — e.g. to lock a production-books integration to reads
39436
+ // until per-vendor broker mediation (ENG-4922) gates its writes.
39437
+ readOnlyScopes: [
39438
+ "openid",
39439
+ "profile",
39440
+ "email",
39441
+ "offline_access",
39442
+ "accounting.settings.read",
39443
+ "accounting.contacts.read",
39444
+ "accounting.invoices.read",
39445
+ "accounting.attachments.read",
39446
+ "accounting.payments.read",
39447
+ "accounting.banktransactions.read",
39448
+ "accounting.manualjournals.read",
39449
+ "accounting.reports.balancesheet.read",
39450
+ "accounting.reports.profitandloss.read",
39451
+ "accounting.reports.trialbalance.read",
39452
+ "accounting.reports.budgetsummary.read",
39453
+ "accounting.reports.banksummary.read",
39454
+ "accounting.reports.executivesummary.read",
39455
+ "accounting.reports.aged.read"
39456
+ ],
39457
+ supportsRefresh: true,
39458
+ extraAuthorizeParams: {},
39459
+ clientAuthMethod: "basic",
39460
+ userInfoUrl: "https://api.xero.com/connections"
39461
+ },
39462
+ // LinkedIn Ads (Marketing API). Confidential client with a static secret and
39463
+ // refresh tokens (~60-day access token, ~1-year rotating refresh token), so it
39464
+ // mirrors the Xero shape: clientAuthMethod 'body', supportsRefresh true, no
39465
+ // PKCE. The shared authorize/callback/refresh machinery handles the rest.
39466
+ //
39467
+ // The single shared app is Augmented Team's own LinkedIn developer app
39468
+ // (client id/secret in OAUTH_LINKEDIN_ADS_CLIENT_ID / _SECRET) - every org
39469
+ // connects its own ad accounts against it; only the per-connection tokens
39470
+ // differ. Register `${API_PUBLIC_URL}/integrations/oauth/callback` as an
39471
+ // Authorized redirect URL on the LinkedIn app.
39472
+ //
39473
+ // NOTE (linkedin-verify): the grantable scope set depends on which LinkedIn
39474
+ // API products the app is approved for (Advertising API, Lead Sync API,
39475
+ // Community Management API). Keep this list to scopes the app actually holds -
39476
+ // requesting an ungranted scope makes LinkedIn reject the authorize request.
39477
+ "linkedin-ads": {
39478
+ definitionId: "linkedin-ads",
39479
+ authorizeUrl: "https://www.linkedin.com/oauth/v2/authorization",
39480
+ tokenUrl: "https://www.linkedin.com/oauth/v2/accessToken",
39481
+ // ENG-7897: this list is EXACTLY the Advertising API scope set, because
39482
+ // Advertising API is the only product added to the Brand Ninja app. Every
39483
+ // other scope we used to request here belonged to a product the app does
39484
+ // not hold, and LinkedIn rejects the ENTIRE authorize request with
39485
+ // `unauthorized_scope_error` if ANY single requested scope is ungranted -
39486
+ // so five unapproved scopes bricked the connect for every customer, on
39487
+ // every environment, rather than merely disabling five features.
39488
+ //
39489
+ // Scopes deliberately NOT requested, and the product each needs first:
39490
+ // openid / profile / email -> Sign In with LinkedIn (OpenID Connect)
39491
+ // r_organization_social -> Community Management API
39492
+ // r_marketing_leadgen_automation -> Lead Sync API
39493
+ //
39494
+ // Dropping the OIDC trio costs only the `Connected as <name>` label: the
39495
+ // userInfoUrl fetch in the callback is already best-effort and falls back
39496
+ // to a plain 'Connected'. Do NOT re-add any of these until the matching
39497
+ // product shows under "Added products" on the app - use the
39498
+ // OAUTH_LINKEDIN_ADS_SCOPES override below to widen without a deploy once
39499
+ // an approval lands, and only then promote it into this constant.
39500
+ defaultScopes: [
39501
+ // Advertising API: read + manage campaigns/creatives, and reporting.
39502
+ "r_ads",
39503
+ "rw_ads",
39504
+ "r_ads_reporting"
39505
+ ],
39506
+ // Read-only variant: reporting only, no campaign/creative mutation. Used
39507
+ // when an install is (re)connected with read_only: true. Same product
39508
+ // constraint as above - `r_organization_social` was removed with it.
39509
+ readOnlyScopes: [
39510
+ "r_ads",
39511
+ "r_ads_reporting"
39512
+ ],
39513
+ supportsRefresh: true,
39514
+ extraAuthorizeParams: {},
39515
+ clientAuthMethod: "body",
39516
+ userInfoUrl: "https://api.linkedin.com/v2/userinfo"
39517
+ }
39518
+ };
39519
+ OAUTH_PROVIDERS["xero-control-plane"] = {
39520
+ ...OAUTH_PROVIDERS["xero"]
39521
+ };
39522
+
39070
39523
  // ../core/dist/integrations/composio-account-probe.js
39071
39524
  var BODY_MAX_BYTES = 1024 * 1024;
39072
39525