@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.
@@ -18,7 +18,7 @@ import {
18
18
  rotateDailySession,
19
19
  sessionFileExists,
20
20
  todayLocalIso
21
- } from "./chunk-N6DU2RYO.js";
21
+ } from "./chunk-LLBJLKGL.js";
22
22
  import {
23
23
  reapOrphanChannelMcps
24
24
  } from "./chunk-XWVM4KPK.js";
@@ -5141,4 +5141,4 @@ export {
5141
5141
  stopAllSessionsAndWait,
5142
5142
  getProjectDir
5143
5143
  };
5144
- //# sourceMappingURL=chunk-NRMBKDER.js.map
5144
+ //# sourceMappingURL=chunk-NTWABPKO.js.map
@@ -100,7 +100,7 @@ async function spawnPairSession(session) {
100
100
  return { ok: true };
101
101
  } catch {
102
102
  }
103
- const { resolveClaudeBinary } = await import("./persistent-session-CGIB77UQ.js");
103
+ const { resolveClaudeBinary } = await import("./persistent-session-GHIBAWHI.js");
104
104
  const claudeBin = resolveClaudeBinary();
105
105
  const pairEnv = {
106
106
  ...process.env,
@@ -373,4 +373,4 @@ export {
373
373
  startClaudePair,
374
374
  submitClaudePairCode
375
375
  };
376
- //# sourceMappingURL=claude-pair-runtime-MI65DVTR.js.map
376
+ //# sourceMappingURL=claude-pair-runtime-3J7KK6BP.js.map
@@ -58,7 +58,7 @@ import {
58
58
  safeWriteJsonAtomic,
59
59
  setConfigHash,
60
60
  tripClass
61
- } from "../chunk-5S334W7F.js";
61
+ } from "../chunk-3IDAWL35.js";
62
62
  import {
63
63
  getProjectDir as getProjectDir2,
64
64
  getReadyTasks,
@@ -120,7 +120,7 @@ import {
120
120
  takeZombieDetection,
121
121
  toOpencodeModel,
122
122
  writeEgressAllowlist
123
- } from "../chunk-NRMBKDER.js";
123
+ } from "../chunk-NTWABPKO.js";
124
124
  import {
125
125
  ACCOUNT_ENFORCEMENT_MARKER_FILENAME,
126
126
  AnchorSessionClient,
@@ -209,7 +209,7 @@ import {
209
209
  subagentActivityAgeSeconds,
210
210
  sumTranscriptUsageInWindow,
211
211
  transcriptActivityAgeSeconds
212
- } from "../chunk-N6DU2RYO.js";
212
+ } from "../chunk-LLBJLKGL.js";
213
213
  import {
214
214
  reapOrphanChannelMcps
215
215
  } from "../chunk-XWVM4KPK.js";
@@ -12870,7 +12870,7 @@ var pendingDayRolloverReset = /* @__PURE__ */ new Set();
12870
12870
  var dayRolloverInboundHold = /* @__PURE__ */ new Map();
12871
12871
  var INBOUND_HOLD_EPISODE_GAP_MS = 12e4;
12872
12872
  async function channelInboundActivityAgeSecondsFor(codeName) {
12873
- const { newestPendingInboundActivityMtimeMs } = await import("../responsiveness-probe-YINXZH3S.js");
12873
+ const { newestPendingInboundActivityMtimeMs } = await import("../responsiveness-probe-TT3LAALK.js");
12874
12874
  const newest = newestPendingInboundActivityMtimeMs(dirname10(paneLogPath(codeName)));
12875
12875
  if (newest === null) return null;
12876
12876
  return Math.max(0, Math.floor((Date.now() - newest) / 1e3));
@@ -13530,7 +13530,7 @@ var agentRestartTimezoneInputs = /* @__PURE__ */ new Map();
13530
13530
  var lastVersionCheckAt = 0;
13531
13531
  var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
13532
13532
  var lastResponsivenessProbeAt = 0;
13533
- var agtCliVersion = true ? "0.28.778" : "dev";
13533
+ var agtCliVersion = true ? "0.28.779" : "dev";
13534
13534
  function resolveBrewPath(execFileSync2) {
13535
13535
  try {
13536
13536
  const out = execFileSync2("which", ["brew"], { timeout: 5e3 }).toString().trim();
@@ -15074,7 +15074,7 @@ function flushRestartedAgentDiagnostics(hostId, codeNames) {
15074
15074
  if (codeNames.length === 0) return;
15075
15075
  void (async () => {
15076
15076
  try {
15077
- const { collectDiagnostics } = await import("../persistent-session-CGIB77UQ.js");
15077
+ const { collectDiagnostics } = await import("../persistent-session-GHIBAWHI.js");
15078
15078
  await api.post("/host/heartbeat", {
15079
15079
  host_id: hostId,
15080
15080
  agent_diagnostics: collectDiagnostics(codeNames, quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics, pidPressureFor, forwardedToolsFor)
@@ -15215,7 +15215,7 @@ async function pollCycleInner() {
15215
15215
  }
15216
15216
  try {
15217
15217
  const { detectHostSecurity } = await import("../host-security-6PDFG7F5.js");
15218
- const { collectDiagnostics } = await import("../persistent-session-CGIB77UQ.js");
15218
+ const { collectDiagnostics } = await import("../persistent-session-GHIBAWHI.js");
15219
15219
  const diagCodeNames = [...agentState.persistentSessionAgents];
15220
15220
  const agentDiagnostics = diagCodeNames.length > 0 ? collectDiagnostics(diagCodeNames, quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics, pidPressureFor, forwardedToolsFor) : void 0;
15221
15221
  let tailscaleHostname;
@@ -15384,7 +15384,7 @@ async function pollCycleInner() {
15384
15384
  collectPanelessActivityProbes,
15385
15385
  getResponsivenessIntervalMs,
15386
15386
  occupancyQualificationClassifications
15387
- } = await import("../responsiveness-probe-YINXZH3S.js");
15387
+ } = await import("../responsiveness-probe-TT3LAALK.js");
15388
15388
  const probeIntervalMs = getResponsivenessIntervalMs();
15389
15389
  if (now - lastResponsivenessProbeAt > probeIntervalMs) {
15390
15390
  const probeCodeNames = [...agentState.persistentSessionAgents];
@@ -15494,7 +15494,7 @@ async function pollCycleInner() {
15494
15494
  collectResponsivenessProbes,
15495
15495
  livePendingInboundOldestAgeSeconds,
15496
15496
  parkPendingInbound
15497
- } = await import("../responsiveness-probe-YINXZH3S.js");
15497
+ } = await import("../responsiveness-probe-TT3LAALK.js");
15498
15498
  const { getProjectDir: wedgeProjectDir } = await import("../scheduler-engine-NDP36U7O.js");
15499
15499
  const wedgeNow = /* @__PURE__ */ new Date();
15500
15500
  const liveAgents = agentState.persistentSessionAgents;
@@ -15650,7 +15650,7 @@ async function pollCycleInner() {
15650
15650
  }
15651
15651
  try {
15652
15652
  const { scrapeMcpFailedBannerCount } = await import("../pane-mcp-banner-scraper-JA437JIB.js");
15653
- const { probeSessionAuth } = await import("../session-auth-dead-OZBNHFPN.js");
15653
+ const { probeSessionAuth } = await import("../session-auth-dead-OHPGSHYL.js");
15654
15654
  const observations = [];
15655
15655
  const pendingCacheCommits = [];
15656
15656
  const modelApiErrorReportingOn = hostFlagStore().getBoolean("model-api-error-reporting");
@@ -19690,7 +19690,7 @@ async function handleRestartDoorbell(agentId, requestedAt, restartReason) {
19690
19690
  void api.post("/host/restart-ack", { host_id: hostId, agent_id: agentId, restart_requested_at: requestedAt }).catch((err) => log(`[restart-lane] ack failed for '${codeName}': ${err.message}`));
19691
19691
  void (async () => {
19692
19692
  try {
19693
- const { collectDiagnostics } = await import("../persistent-session-CGIB77UQ.js");
19693
+ const { collectDiagnostics } = await import("../persistent-session-GHIBAWHI.js");
19694
19694
  await api.post("/host/heartbeat", {
19695
19695
  host_id: hostId,
19696
19696
  agent_diagnostics: collectDiagnostics([codeName], quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics, pidPressureFor, forwardedToolsFor)
@@ -19736,7 +19736,7 @@ async function respawnAgentAfterMcpStop(codeName, reason) {
19736
19736
  }
19737
19737
  try {
19738
19738
  const hostId = await getHostId();
19739
- const { collectDiagnostics } = await import("../persistent-session-CGIB77UQ.js");
19739
+ const { collectDiagnostics } = await import("../persistent-session-GHIBAWHI.js");
19740
19740
  await api.post("/host/heartbeat", {
19741
19741
  host_id: hostId,
19742
19742
  agent_diagnostics: collectDiagnostics([codeName], quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics, pidPressureFor, forwardedToolsFor)
@@ -20372,7 +20372,7 @@ async function processClaudePairSessions(agents) {
20372
20372
  killPairSession,
20373
20373
  pairTmuxSession,
20374
20374
  finalizeClaudePairOnboarding
20375
- } = await import("../claude-pair-runtime-MI65DVTR.js");
20375
+ } = await import("../claude-pair-runtime-3J7KK6BP.js");
20376
20376
  for (const pairId of pendingResp.cancelled_pair_ids ?? []) {
20377
20377
  log(`[claude-pair] sweeping orphan tmux session for pair ${pairId.slice(0, 8)}`);
20378
20378
  const killed = await killPairSession(pairTmuxSession(pairId));
@@ -32883,6 +32883,459 @@ var ASSET_TYPES = {
32883
32883
  var DEFAULT_CHUNK_BUDGET_BYTES = 180 * 1024;
32884
32884
  var textEncoder = new TextEncoder();
32885
32885
 
32886
+ // ../core/dist/integrations/oauth-providers.js
32887
+ var OAUTH_PROVIDERS = {
32888
+ "google-workspace": {
32889
+ definitionId: "google-workspace",
32890
+ authorizeUrl: "https://accounts.google.com/o/oauth2/v2/auth",
32891
+ tokenUrl: "https://oauth2.googleapis.com/token",
32892
+ revokeUrl: "https://oauth2.googleapis.com/revoke",
32893
+ defaultScopes: [
32894
+ "https://www.googleapis.com/auth/gmail.modify",
32895
+ "https://www.googleapis.com/auth/calendar",
32896
+ "https://www.googleapis.com/auth/drive",
32897
+ "https://www.googleapis.com/auth/spreadsheets",
32898
+ "https://www.googleapis.com/auth/documents",
32899
+ "https://www.googleapis.com/auth/chat.messages",
32900
+ "https://www.googleapis.com/auth/chat.spaces.readonly"
32901
+ ],
32902
+ supportsRefresh: true,
32903
+ extraAuthorizeParams: {
32904
+ access_type: "offline",
32905
+ prompt: "consent"
32906
+ },
32907
+ clientAuthMethod: "body",
32908
+ userInfoUrl: "https://www.googleapis.com/oauth2/v2/userinfo"
32909
+ },
32910
+ "github": {
32911
+ definitionId: "github",
32912
+ authorizeUrl: "https://github.com/login/oauth/authorize",
32913
+ tokenUrl: "https://github.com/login/oauth/access_token",
32914
+ // ENG-6187: revoke the existing grant on reconnect so GitHub re-prompts and
32915
+ // the four scopes below are actually granted. Without this, a stale narrow
32916
+ // grant (e.g. an old read:user-only authorization) is silently re-issued and
32917
+ // the missing-scopes banner loops forever.
32918
+ grantRevokeUrl: "https://api.github.com/applications/{client_id}/grant",
32919
+ defaultScopes: ["repo", "read:org", "gist", "workflow"],
32920
+ supportsRefresh: true,
32921
+ extraAuthorizeParams: {},
32922
+ clientAuthMethod: "body",
32923
+ userInfoUrl: "https://api.github.com/user"
32924
+ },
32925
+ "granola": {
32926
+ // Granola MCP — remote streamable-HTTP at https://mcp.granola.ai/mcp.
32927
+ // The AS is at mcp-auth.granola.ai and exposes RFC 8414 metadata at
32928
+ // /.well-known/oauth-authorization-server. Auth is OAuth 2.0 with
32929
+ // mandatory PKCE (S256) and a public client (no client_secret) issued
32930
+ // via Dynamic Client Registration (RFC 7591). The bootstrap script
32931
+ // (`packages/api/scripts/dcr-register.ts`) registers a client once at
32932
+ // deploy time; OAUTH_GRANOLA_CLIENT_ID is set from its output.
32933
+ definitionId: "granola",
32934
+ authorizeUrl: "https://mcp-auth.granola.ai/oauth2/authorize",
32935
+ tokenUrl: "https://mcp-auth.granola.ai/oauth2/token",
32936
+ // Minimal scope set: `offline_access` earns the refresh_token so the
32937
+ // refresh cron can rotate the bearer without operator action; `openid`
32938
+ // is required for the OIDC code flow even when we don't request an
32939
+ // id_token. Profile/email are intentionally omitted — we have no
32940
+ // userInfoUrl wired up here, so requesting them would over-ask consent
32941
+ // for fields the callback can't read.
32942
+ defaultScopes: ["openid", "offline_access"],
32943
+ supportsRefresh: true,
32944
+ extraAuthorizeParams: {},
32945
+ clientAuthMethod: "body",
32946
+ pkce: "S256",
32947
+ publicClient: true,
32948
+ mcpUrl: "https://mcp.granola.ai/mcp",
32949
+ // Curated surface (matches the catalog seed's defined_scopes[].tools).
32950
+ toolAllowlist: ["search-meetings", "read-transcript", "read-summary", "list-folders"]
32951
+ },
32952
+ "brand-ninja": {
32953
+ // ENG-6820: Brand Ninja External-Content MCP, remote streamable-HTTP at
32954
+ // https://ext-api.app.brandninja.ai/v1/mcp. Same shape as Granola: the
32955
+ // server exposes RFC 8414 authorization-server metadata at
32956
+ // /.well-known/oauth-authorization-server (values below are taken verbatim
32957
+ // from that document, not inferred). Auth is OAuth 2.0 authorization-code
32958
+ // with mandatory PKCE (S256) and a public client (token_endpoint_auth_method
32959
+ // 'none') issued via Dynamic Client Registration (RFC 7591). The bootstrap
32960
+ // script (packages/api/scripts/dcr-register.ts) registers a client once at
32961
+ // deploy time against the registration_endpoint
32962
+ // (https://ext-api.app.brandninja.ai/v1/oauth/register); OAUTH_BRAND_NINJA_CLIENT_ID
32963
+ // is set from its output. The AS advertises the refresh_token grant, so the
32964
+ // shared oauth-refresh cron rotates the bearer without operator action.
32965
+ definitionId: "brand-ninja",
32966
+ authorizeUrl: "https://prod-brandninja.auth.ap-southeast-2.amazoncognito.com/oauth2/authorize",
32967
+ tokenUrl: "https://prod-brandninja.auth.ap-southeast-2.amazoncognito.com/oauth2/token",
32968
+ // The resource server (ext-api.app.brandninja.ai) advertises exactly two
32969
+ // scopes: external-api/content.write (the default content surface) and
32970
+ // external-api/admin (read-only credential metadata, granted per account
32971
+ // admin). Default install is least-privilege: content.write only; an
32972
+ // operator can widen to admin out of band. No openid/offline_access in the
32973
+ // advertised scope set, so Cognito issues the refresh_token for the code grant
32974
+ // regardless, so requesting only the resource scope keeps consent minimal.
32975
+ defaultScopes: ["external-api/content.write"],
32976
+ supportsRefresh: true,
32977
+ extraAuthorizeParams: {},
32978
+ clientAuthMethod: "body",
32979
+ pkce: "S256",
32980
+ publicClient: true,
32981
+ mcpUrl: "https://ext-api.app.brandninja.ai/v1/mcp",
32982
+ // Curated surface (matches the catalog seed's defined_scopes[].tools).
32983
+ toolAllowlist: [
32984
+ "submit_content_request",
32985
+ "get_content_status",
32986
+ "list_content_requests",
32987
+ "list_channels",
32988
+ "list_brands",
32989
+ "list_topics",
32990
+ "list_timeline_templates",
32991
+ "list_content_types",
32992
+ "list_source_skills",
32993
+ "list_output_skills",
32994
+ "list_conversion_skills",
32995
+ "list_credentials",
32996
+ "search_transcripts",
32997
+ "create_source_clips",
32998
+ "create_timeline_ranking",
32999
+ "create_topic",
33000
+ "create_timeline_template",
33001
+ "create_knowledge",
33002
+ "link_knowledge"
33003
+ ]
33004
+ },
33005
+ "kajabi": {
33006
+ // Kajabi MCP — remote streamable-HTTP at https://mcp.kajabi.com/mcp.
33007
+ // Same Granola/Brand-Ninja shape: OAuth 2.0 authorization-code with
33008
+ // mandatory PKCE (S256) and a public client (token_endpoint_auth_method
33009
+ // 'none') issued via Dynamic Client Registration (RFC 7591). Values below
33010
+ // are taken verbatim from Kajabi's RFC 8414 metadata at
33011
+ // https://mcp.kajabi.com/.well-known/oauth-authorization-server (a Rails
33012
+ // Doorkeeper AS), not inferred. NOTE the authorize host differs from the
33013
+ // token host: authorize is on app.kajabi.com (the login surface), while
33014
+ // token/register/revoke are on mcp.kajabi.com — do NOT "normalise" them to
33015
+ // one host. The bootstrap script (packages/api/scripts/dcr-register.ts)
33016
+ // registers a client once at deploy time against
33017
+ // https://mcp.kajabi.com/mcp/oauth/register; OAUTH_KAJABI_CLIENT_ID is set
33018
+ // from its output. Doorkeeper RESTRICTS a dynamic client to the scopes it
33019
+ // registered with, so register with at least the union of defaultScopes
33020
+ // below (--scope 'read write:contacts write:emails write:content
33021
+ // write:commerce'). Widening defaultScopes forces a client re-register
33022
+ // (new client_id) AND a per-connection re-consent: existing tokens keep
33023
+ // the old scope set and their refreshes fail under the new client_id, so
33024
+ // each connection flips to needs_reauth until the user re-runs Connect
33025
+ // (ENG-7483). The AS advertises the refresh_token grant (no
33026
+ // openid/offline_access scope needed), so the shared oauth-refresh cron
33027
+ // rotates the bearer without operator action.
33028
+ definitionId: "kajabi",
33029
+ authorizeUrl: "https://app.kajabi.com/mcp/oauth/authorize",
33030
+ tokenUrl: "https://mcp.kajabi.com/mcp/oauth/token",
33031
+ revokeUrl: "https://mcp.kajabi.com/mcp/oauth/revoke",
33032
+ // Coarse Doorkeeper scopes (NOT openid-style). Cross-domain reads (`read`)
33033
+ // plus the write surfaces this integration ships: contact tags/segments,
33034
+ // email broadcasts/sequences, and course updates (write:content gates
33035
+ // update_course, the course-thumbnail path, ENG-7483). write:commerce is
33036
+ // requested now (Brad's call on ENG-7483) so a later offer/pricing-write
33037
+ // enablement needs no extra re-consent round; the tool allowlist below
33038
+ // still exposes no commerce write, so the callable surface stays minimal.
33039
+ defaultScopes: ["read", "write:contacts", "write:emails", "write:content", "write:commerce"],
33040
+ supportsRefresh: true,
33041
+ extraAuthorizeParams: {},
33042
+ clientAuthMethod: "body",
33043
+ pkce: "S256",
33044
+ publicClient: true,
33045
+ mcpUrl: "https://mcp.kajabi.com/mcp",
33046
+ // Curated surface (matches the catalog seed's defined_scopes[].tools). Kajabi's
33047
+ // live MCP advertises ~111 tools; the agent only needs these 28.
33048
+ // CS-1427: `enable_toolset` is included so an agent can activate its own
33049
+ // products/commerce + analytics toolsets on the connection (Kajabi gates those
33050
+ // tool groups behind a runtime toolset that returns "not active" until enabled).
33051
+ // Low-risk: this proxy still caps every tools/call to the allowlist, so a broad
33052
+ // activation can't widen what's actually callable.
33053
+ // ENG-7483: `get_course`/`update_course` enable the course-thumbnail refresh
33054
+ // (update_course covers title/description/thumbnail per Kajabi's MCP docs).
33055
+ // Fail-safe on naming drift: an allowlisted tool the server doesn't advertise
33056
+ // is logged and skipped by the proxy, never a break.
33057
+ toolAllowlist: [
33058
+ "list_sites",
33059
+ "get_site_summary",
33060
+ "select_site",
33061
+ "search_contacts",
33062
+ "get_contact",
33063
+ "search_products",
33064
+ "get_product",
33065
+ "list_offers",
33066
+ "get_offer",
33067
+ "list_offer_purchases",
33068
+ "get_revenue_analytics",
33069
+ "get_contacts_analytics",
33070
+ "list_tags",
33071
+ "create_tag",
33072
+ "tag_contact",
33073
+ "untag_contact",
33074
+ "list_segments",
33075
+ "create_segment",
33076
+ "update_segment",
33077
+ "list_broadcasts",
33078
+ "get_broadcast",
33079
+ "create_broadcast",
33080
+ "list_sequences",
33081
+ "get_sequence",
33082
+ "create_sequence",
33083
+ "enable_toolset",
33084
+ "get_course",
33085
+ "update_course",
33086
+ // ENG-7629 follow-up: pages (Dee's landing-page edit) + themes (CS-1448
33087
+ // theme-builder broadcast content). Callable via the proxy union manifest
33088
+ // + per-call enable; matches the new kajabi:pages catalog scope.
33089
+ "list_landing_pages",
33090
+ "get_landing_page",
33091
+ "update_landing_page",
33092
+ "get_theme_content"
33093
+ ],
33094
+ // CS-1446: Kajabi gates several tool groups behind a runtime `enable_toolset`
33095
+ // and does not advertise their tools in `tools/list` until the group is
33096
+ // active. The harness freezes its callable-tool manifest from the
33097
+ // connect-time `tools/list`, so a group the agent activates later never
33098
+ // becomes callable (repro: enable_toolset('courses') → get_course/
33099
+ // update_course still "No such tool available"). Pre-activate the gated
33100
+ // groups whose tools we allowlist so they are advertised in that first
33101
+ // manifest. Names are Kajabi's toolset ids (courses per CS-1446;
33102
+ // products/commerce/analytics per CS-1427). Best-effort + idempotent: an
33103
+ // unknown/unscoped name is logged and skipped by the proxy, and the
33104
+ // toolAllowlist above still caps what is actually callable.
33105
+ //
33106
+ // ENG-8392: `contacts` + `emails` were missing, so thirteen allowlisted
33107
+ // tools were unreachable by construction — list_tags, create_tag,
33108
+ // tag_contact, untag_contact, list_segments, create_segment,
33109
+ // update_segment (contacts) and list_broadcasts, get_broadcast,
33110
+ // create_broadcast, list_sequences, get_sequence, create_sequence
33111
+ // (emails). A customer agent was blocked on exactly those.
33112
+ //
33113
+ // These two ids are Kajabi's OWN spelling, not ours: the server names the
33114
+ // toolset in its rejection, e.g. `list_tags` returns
33115
+ // "The 'contacts' toolset is not active. Call enable_toolset with
33116
+ // name: 'contacts' to activate it first."
33117
+ // and `list_sequences` returns the same with 'emails'. Sourced that way
33118
+ // deliberately — a wrong id here is logged and SKIPPED by the proxy, so a
33119
+ // guess would look applied and change nothing.
33120
+ //
33121
+ // Why this went unnoticed for four rounds: `get_contacts_analytics` lives
33122
+ // in the `analytics` group, which we already pre-enable. So the contacts
33123
+ // family read as partly alive (you could see a site's contact count and
33124
+ // growth curve) while every tag and segment call failed.
33125
+ preEnableToolsets: [
33126
+ "courses",
33127
+ "products",
33128
+ "commerce",
33129
+ "analytics",
33130
+ "pages",
33131
+ "themes",
33132
+ "contacts",
33133
+ "emails"
33134
+ ],
33135
+ // ENG-8512: `update_course.thumbnail` stores a RELATIVE path into Kajabi's
33136
+ // own storage — it is rendered as
33137
+ // https://kajabi-storefronts-production.kajabi-cdn.com/kajabi-storefronts-production/<value>
33138
+ // so an absolute http(s) URL is structurally not a valid value for it.
33139
+ // Kajabi accepts one anyway, returns 200, echoes it back, ingests nothing,
33140
+ // and the course shows a broken image. Reported from a live client site
33141
+ // (DTI) via CS-1548 after it produced a false "done" report and three
33142
+ // failed cards.
33143
+ //
33144
+ // Anchored at the start so only an ABSOLUTE URL is refused; every relative
33145
+ // path — the valid shape — passes untouched. This does not enable setting a
33146
+ // thumbnail from a URL (that needs an upload path, CS-1548 item 4). It stops
33147
+ // us reporting success for a write that did not happen.
33148
+ argRejects: [
33149
+ {
33150
+ tool: "update_course",
33151
+ field: "thumbnail",
33152
+ pattern: "^\\s*https?://",
33153
+ 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."
33154
+ },
33155
+ // CS-1554: `search_contacts` accepts `tag_id`, does not apply it, and
33156
+ // returns the FULL unfiltered contact list with `filters_applied: null`.
33157
+ // Reported first-hand from a live client site (DTI) while auditing which
33158
+ // contacts carried an opt-in tag. Corroborating evidence: Kajabi's own
33159
+ // REST reference filters contacts with `filter[has_tag_id]`, not a bare
33160
+ // `tag_id` — so the argument is dropped as unrecognised and the response
33161
+ // says so, if you read `filters_applied`.
33162
+ //
33163
+ // Worse than the thumbnail case it sits next to. A broken image is
33164
+ // visible; an unfiltered list that reads as filtered produces a confident
33165
+ // wrong ANSWER, which a human then acts on. That is the same shape as the
33166
+ // incident in CS-1554 where the agent told the customer content did not
33167
+ // exist when it did.
33168
+ //
33169
+ // Any value at all is refused: the field is not partially supported, it
33170
+ // is inert. `[\s\S]` matches every stringified scalar including 0 and
33171
+ // false, which a `.`-style pattern anchored on truthiness would miss.
33172
+ //
33173
+ // NOT REPRODUCED BY ME — I have no Kajabi connection. This rests on the
33174
+ // CS-1554 first-hand report plus the REST reference. The file's own bar is
33175
+ // "provably wrong, never a guess", so a reviewer with a live Kajabi
33176
+ // connection should confirm before this merges. If `tag_id` DOES filter
33177
+ // for some tenant, drop this rule and keep the scalar fix.
33178
+ {
33179
+ tool: "search_contacts",
33180
+ field: "tag_id",
33181
+ pattern: "[\\s\\S]",
33182
+ 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."
33183
+ }
33184
+ ]
33185
+ },
33186
+ "notion-cli": {
33187
+ // Notion's public OAuth app. Tokens are workspace-scoped and long-lived —
33188
+ // Notion does not issue refresh_tokens, so `supportsRefresh: false` and
33189
+ // the refresh cron skips this provider entirely. Scopes are not part of
33190
+ // Notion's authorize URL contract; consent is governed by what the user
33191
+ // grants in the OAuth screen, so `defaultScopes` stays empty.
33192
+ // `owner=user` forces the user-OAuth variant (vs internal integration).
33193
+ // Requires OAUTH_NOTION_CLI_CLIENT_ID and OAUTH_NOTION_CLI_CLIENT_SECRET.
33194
+ definitionId: "notion-cli",
33195
+ authorizeUrl: "https://api.notion.com/v1/oauth/authorize",
33196
+ tokenUrl: "https://api.notion.com/v1/oauth/token",
33197
+ defaultScopes: [],
33198
+ supportsRefresh: false,
33199
+ extraAuthorizeParams: {
33200
+ owner: "user"
33201
+ },
33202
+ clientAuthMethod: "basic"
33203
+ },
33204
+ "xero": {
33205
+ definitionId: "xero",
33206
+ authorizeUrl: "https://login.xero.com/identity/connect/authorize",
33207
+ tokenUrl: "https://identity.xero.com/connect/token",
33208
+ revokeUrl: "https://identity.xero.com/connect/revocation",
33209
+ defaultScopes: [
33210
+ "openid",
33211
+ "profile",
33212
+ "email",
33213
+ "offline_access",
33214
+ // Granular scopes (required for apps created after March 2, 2026 —
33215
+ // do NOT revert to the broad `accounting.transactions` /
33216
+ // `accounting.contacts` scopes, Xero rejects the manifest).
33217
+ // The variant *without* `.read` is the read+write granular scope.
33218
+ "accounting.settings.read",
33219
+ // contacts: write enables agent-driven supplier/customer creation
33220
+ // (required for bill creation since a bill must reference a contact).
33221
+ "accounting.contacts",
33222
+ // invoices: write enables bill creation (Type=ACCPAY invoices) and
33223
+ // updates to sales invoices alongside the existing read access.
33224
+ "accounting.invoices",
33225
+ // attachments: write enables agents to attach the source PDF to a
33226
+ // bill at creation time. Read-only would force a follow-up manual
33227
+ // upload in Xero; write closes the loop.
33228
+ "accounting.attachments",
33229
+ // accounting.transactions.read → granular read-only replacements
33230
+ // for the surfaces we don't yet need write access on.
33231
+ "accounting.payments.read",
33232
+ "accounting.banktransactions.read",
33233
+ // manualjournals: write (CS-1439) enables the xero-broker to post
33234
+ // manual journals (reclasses, accruals, corrections) on approval. The
33235
+ // variant without `.read` is the read+write granular scope. Note: an
33236
+ // existing Xero install must RECONNECT for its token to carry this — the
33237
+ // broker posts journals with the agent's own OAuth access_token.
33238
+ "accounting.manualjournals",
33239
+ // accounting.reports.read → granular read-only replacements
33240
+ "accounting.reports.balancesheet.read",
33241
+ "accounting.reports.profitandloss.read",
33242
+ "accounting.reports.trialbalance.read",
33243
+ "accounting.reports.budgetsummary.read",
33244
+ "accounting.reports.banksummary.read",
33245
+ "accounting.reports.executivesummary.read",
33246
+ "accounting.reports.aged.read"
33247
+ ],
33248
+ // Read-only variant (ENG-6170): the `.read` granular scope for every
33249
+ // surface, so a token granted under it cannot create bills/invoices or
33250
+ // mutate contacts/attachments. Used when an install is (re)connected with
33251
+ // `read_only: true` — e.g. to lock a production-books integration to reads
33252
+ // until per-vendor broker mediation (ENG-4922) gates its writes.
33253
+ readOnlyScopes: [
33254
+ "openid",
33255
+ "profile",
33256
+ "email",
33257
+ "offline_access",
33258
+ "accounting.settings.read",
33259
+ "accounting.contacts.read",
33260
+ "accounting.invoices.read",
33261
+ "accounting.attachments.read",
33262
+ "accounting.payments.read",
33263
+ "accounting.banktransactions.read",
33264
+ "accounting.manualjournals.read",
33265
+ "accounting.reports.balancesheet.read",
33266
+ "accounting.reports.profitandloss.read",
33267
+ "accounting.reports.trialbalance.read",
33268
+ "accounting.reports.budgetsummary.read",
33269
+ "accounting.reports.banksummary.read",
33270
+ "accounting.reports.executivesummary.read",
33271
+ "accounting.reports.aged.read"
33272
+ ],
33273
+ supportsRefresh: true,
33274
+ extraAuthorizeParams: {},
33275
+ clientAuthMethod: "basic",
33276
+ userInfoUrl: "https://api.xero.com/connections"
33277
+ },
33278
+ // LinkedIn Ads (Marketing API). Confidential client with a static secret and
33279
+ // refresh tokens (~60-day access token, ~1-year rotating refresh token), so it
33280
+ // mirrors the Xero shape: clientAuthMethod 'body', supportsRefresh true, no
33281
+ // PKCE. The shared authorize/callback/refresh machinery handles the rest.
33282
+ //
33283
+ // The single shared app is Augmented Team's own LinkedIn developer app
33284
+ // (client id/secret in OAUTH_LINKEDIN_ADS_CLIENT_ID / _SECRET) - every org
33285
+ // connects its own ad accounts against it; only the per-connection tokens
33286
+ // differ. Register `${API_PUBLIC_URL}/integrations/oauth/callback` as an
33287
+ // Authorized redirect URL on the LinkedIn app.
33288
+ //
33289
+ // NOTE (linkedin-verify): the grantable scope set depends on which LinkedIn
33290
+ // API products the app is approved for (Advertising API, Lead Sync API,
33291
+ // Community Management API). Keep this list to scopes the app actually holds -
33292
+ // requesting an ungranted scope makes LinkedIn reject the authorize request.
33293
+ "linkedin-ads": {
33294
+ definitionId: "linkedin-ads",
33295
+ authorizeUrl: "https://www.linkedin.com/oauth/v2/authorization",
33296
+ tokenUrl: "https://www.linkedin.com/oauth/v2/accessToken",
33297
+ // ENG-7897: this list is EXACTLY the Advertising API scope set, because
33298
+ // Advertising API is the only product added to the Brand Ninja app. Every
33299
+ // other scope we used to request here belonged to a product the app does
33300
+ // not hold, and LinkedIn rejects the ENTIRE authorize request with
33301
+ // `unauthorized_scope_error` if ANY single requested scope is ungranted -
33302
+ // so five unapproved scopes bricked the connect for every customer, on
33303
+ // every environment, rather than merely disabling five features.
33304
+ //
33305
+ // Scopes deliberately NOT requested, and the product each needs first:
33306
+ // openid / profile / email -> Sign In with LinkedIn (OpenID Connect)
33307
+ // r_organization_social -> Community Management API
33308
+ // r_marketing_leadgen_automation -> Lead Sync API
33309
+ //
33310
+ // Dropping the OIDC trio costs only the `Connected as <name>` label: the
33311
+ // userInfoUrl fetch in the callback is already best-effort and falls back
33312
+ // to a plain 'Connected'. Do NOT re-add any of these until the matching
33313
+ // product shows under "Added products" on the app - use the
33314
+ // OAUTH_LINKEDIN_ADS_SCOPES override below to widen without a deploy once
33315
+ // an approval lands, and only then promote it into this constant.
33316
+ defaultScopes: [
33317
+ // Advertising API: read + manage campaigns/creatives, and reporting.
33318
+ "r_ads",
33319
+ "rw_ads",
33320
+ "r_ads_reporting"
33321
+ ],
33322
+ // Read-only variant: reporting only, no campaign/creative mutation. Used
33323
+ // when an install is (re)connected with read_only: true. Same product
33324
+ // constraint as above - `r_organization_social` was removed with it.
33325
+ readOnlyScopes: [
33326
+ "r_ads",
33327
+ "r_ads_reporting"
33328
+ ],
33329
+ supportsRefresh: true,
33330
+ extraAuthorizeParams: {},
33331
+ clientAuthMethod: "body",
33332
+ userInfoUrl: "https://api.linkedin.com/v2/userinfo"
33333
+ }
33334
+ };
33335
+ OAUTH_PROVIDERS["xero-control-plane"] = {
33336
+ ...OAUTH_PROVIDERS["xero"]
33337
+ };
33338
+
32886
33339
  // ../core/dist/integrations/composio-account-probe.js
32887
33340
  var BODY_MAX_BYTES = 1024 * 1024;
32888
33341