@jskit-ai/connectors-catalog 0.1.1

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.
Files changed (399) hide show
  1. package/README.md +192 -0
  2. package/docs/ai-upstream-notices.md +51 -0
  3. package/docs/ai.md +191 -0
  4. package/docs/airtable.md +130 -0
  5. package/docs/algolia.md +190 -0
  6. package/docs/amazon-redshift.md +229 -0
  7. package/docs/amplitude.md +210 -0
  8. package/docs/apify.md +162 -0
  9. package/docs/apollo-io.md +200 -0
  10. package/docs/asana.md +126 -0
  11. package/docs/ashby.md +188 -0
  12. package/docs/atlassian.md +224 -0
  13. package/docs/attention.md +162 -0
  14. package/docs/aws-athena.md +150 -0
  15. package/docs/aws-credentials.md +75 -0
  16. package/docs/aws-s3.md +193 -0
  17. package/docs/bigquery.md +146 -0
  18. package/docs/brevo.md +122 -0
  19. package/docs/calendly.md +158 -0
  20. package/docs/canva.md +191 -0
  21. package/docs/chargebee.md +192 -0
  22. package/docs/clay.md +194 -0
  23. package/docs/clickhouse.md +242 -0
  24. package/docs/confidence-exp.md +74 -0
  25. package/docs/confidence-flags.md +75 -0
  26. package/docs/confidence-oauth.md +137 -0
  27. package/docs/contentful.md +123 -0
  28. package/docs/databricks.md +180 -0
  29. package/docs/dbt-semantic-layer.md +175 -0
  30. package/docs/elevenlabs.md +102 -0
  31. package/docs/figma.md +132 -0
  32. package/docs/firebase-cloud-messaging.md +262 -0
  33. package/docs/firecrawl.md +137 -0
  34. package/docs/fireflies.md +95 -0
  35. package/docs/fireworks-ai.md +53 -0
  36. package/docs/gatewayapi.md +113 -0
  37. package/docs/gemini-enterprise.md +224 -0
  38. package/docs/github-api.md +133 -0
  39. package/docs/gitlab-api.md +138 -0
  40. package/docs/gmail.md +133 -0
  41. package/docs/gong.md +205 -0
  42. package/docs/google-ads.md +156 -0
  43. package/docs/google-analytics.md +202 -0
  44. package/docs/google-docs.md +105 -0
  45. package/docs/google-drive.md +108 -0
  46. package/docs/google-maps-platform.md +208 -0
  47. package/docs/google-oauth.md +123 -0
  48. package/docs/google-search-console.md +124 -0
  49. package/docs/google-sheets.md +94 -0
  50. package/docs/google-slides.md +95 -0
  51. package/docs/granola.md +178 -0
  52. package/docs/hex.md +161 -0
  53. package/docs/heygen.md +124 -0
  54. package/docs/hubspot.md +142 -0
  55. package/docs/incident-io.md +118 -0
  56. package/docs/inngest.md +178 -0
  57. package/docs/klipy.md +149 -0
  58. package/docs/lexware.md +144 -0
  59. package/docs/lightspeed.md +193 -0
  60. package/docs/linear.md +137 -0
  61. package/docs/linkedin.md +196 -0
  62. package/docs/logo-dev.md +152 -0
  63. package/docs/mailgun.md +153 -0
  64. package/docs/mapbox.md +174 -0
  65. package/docs/microsoft-excel.md +97 -0
  66. package/docs/microsoft-fabric.md +249 -0
  67. package/docs/microsoft-oauth.md +143 -0
  68. package/docs/microsoft-onedrive.md +76 -0
  69. package/docs/microsoft-onenote.md +75 -0
  70. package/docs/microsoft-outlook.md +90 -0
  71. package/docs/microsoft-powerpoint.md +119 -0
  72. package/docs/microsoft-sharepoint.md +75 -0
  73. package/docs/microsoft-teams.md +72 -0
  74. package/docs/microsoft-word.md +119 -0
  75. package/docs/miro.md +117 -0
  76. package/docs/n8n.md +169 -0
  77. package/docs/notion.md +137 -0
  78. package/docs/oura.md +161 -0
  79. package/docs/paddle.md +168 -0
  80. package/docs/perplexity.md +85 -0
  81. package/docs/pipedrive.md +120 -0
  82. package/docs/polar.md +63 -0
  83. package/docs/posthog.md +227 -0
  84. package/docs/prestashop.md +151 -0
  85. package/docs/replicate.md +60 -0
  86. package/docs/resend.md +153 -0
  87. package/docs/salesforce.md +234 -0
  88. package/docs/sanity.md +122 -0
  89. package/docs/semrush.md +276 -0
  90. package/docs/sentry.md +112 -0
  91. package/docs/sevdesk.md +107 -0
  92. package/docs/shopify.md +206 -0
  93. package/docs/slack.md +257 -0
  94. package/docs/snowflake.md +271 -0
  95. package/docs/storyblok.md +130 -0
  96. package/docs/stripe.md +77 -0
  97. package/docs/supabase.md +51 -0
  98. package/docs/tally.md +105 -0
  99. package/docs/telegram.md +165 -0
  100. package/docs/tiktok.md +201 -0
  101. package/docs/twilio.md +284 -0
  102. package/docs/twitch.md +428 -0
  103. package/docs/wave.md +440 -0
  104. package/docs/wix.md +1249 -0
  105. package/docs/wiz.md +177 -0
  106. package/docs/woocommerce.md +400 -0
  107. package/docs/wordpress-com.md +358 -0
  108. package/docs/wordpress-self-hosted.md +252 -0
  109. package/docs/workday.md +292 -0
  110. package/docs/x-twitter.md +188 -0
  111. package/docs/xero.md +429 -0
  112. package/docs/zoho-books.md +214 -0
  113. package/docs/zoho-crm.md +192 -0
  114. package/package.json +153 -0
  115. package/patterns/ai-connections/PATTERN.md +84 -0
  116. package/patterns/ai-connections/example/ai-model.js +11 -0
  117. package/patterns/ai-connections/example/perplexity-answer.js +29 -0
  118. package/patterns/api-key-connection/PATTERN.md +368 -0
  119. package/patterns/api-key-connection/example/connections.js +18 -0
  120. package/patterns/api-key-connection/example/integrations.json +19 -0
  121. package/patterns/assistant-mcp/PATTERN.md +110 -0
  122. package/patterns/assistant-mcp/example/integrations.json +13 -0
  123. package/patterns/assistant-mcp-oauth/PATTERN.md +235 -0
  124. package/patterns/assistant-mcp-oauth/example/integrations.json +102 -0
  125. package/patterns/aws-storage-queries/PATTERN.md +169 -0
  126. package/patterns/aws-storage-queries/example/formats/data-formats.js +35 -0
  127. package/patterns/aws-storage-queries/example/formats/package-lock.json +49 -0
  128. package/patterns/aws-storage-queries/example/formats/package.json +12 -0
  129. package/patterns/aws-storage-queries/example/formats/verify-formats.mjs +37 -0
  130. package/patterns/aws-storage-queries/example/integrations.json +38 -0
  131. package/patterns/aws-storage-queries/example/s3-transfer.js +34 -0
  132. package/patterns/event-delivery/PATTERN.md +151 -0
  133. package/patterns/event-delivery/example/integrations.json +17 -0
  134. package/patterns/firebase-messaging/PATTERN.md +156 -0
  135. package/patterns/firebase-messaging/example/server/notifications.js +23 -0
  136. package/patterns/google-ads-search/PATTERN.md +92 -0
  137. package/patterns/google-ads-search/example/ads-setup.js +25 -0
  138. package/patterns/oauth-connection/PATTERN.md +724 -0
  139. package/patterns/oauth-connection/example/integrations.json +20 -0
  140. package/patterns/paddle-catalogue/PATTERN.md +78 -0
  141. package/patterns/paddle-catalogue/example/create-products.js +38 -0
  142. package/patterns/public-image/PATTERN.md +84 -0
  143. package/patterns/public-image/example/integrations.json +13 -0
  144. package/patterns/public-image/example/logo-url.js +16 -0
  145. package/patterns/redshift-queries/PATTERN.md +150 -0
  146. package/patterns/redshift-queries/example/integrations.json +33 -0
  147. package/patterns/source-scanning/PATTERN.md +83 -0
  148. package/patterns/source-scanning/example/source-scanner.js +13 -0
  149. package/src/client/firebase-cloud-messaging.js +10 -0
  150. package/src/client/google-analytics.js +72 -0
  151. package/src/client/google-maps-platform.js +39 -0
  152. package/src/client/logo-dev.js +36 -0
  153. package/src/server/ai.js +46 -0
  154. package/src/server/airtable.js +67 -0
  155. package/src/server/algolia.js +63 -0
  156. package/src/server/amazon-redshift.js +96 -0
  157. package/src/server/amplitude.js +31 -0
  158. package/src/server/apify.js +68 -0
  159. package/src/server/apollo-io.js +85 -0
  160. package/src/server/asana.js +64 -0
  161. package/src/server/ashby.js +61 -0
  162. package/src/server/atlassian.js +32 -0
  163. package/src/server/attention.js +93 -0
  164. package/src/server/aws-athena.js +82 -0
  165. package/src/server/aws-s3.js +49 -0
  166. package/src/server/awsTransport.js +75 -0
  167. package/src/server/bigquery.js +97 -0
  168. package/src/server/brevo.js +97 -0
  169. package/src/server/calendly.js +77 -0
  170. package/src/server/canva.js +13 -0
  171. package/src/server/chargebee.js +88 -0
  172. package/src/server/clay.js +78 -0
  173. package/src/server/clickhouse.js +66 -0
  174. package/src/server/confidence.js +29 -0
  175. package/src/server/contentful.js +40 -0
  176. package/src/server/databricks.js +55 -0
  177. package/src/server/dbt-semantic-layer.js +95 -0
  178. package/src/server/elevenlabs.js +81 -0
  179. package/src/server/figma.js +24 -0
  180. package/src/server/firebase-cloud-messaging.js +111 -0
  181. package/src/server/firecrawl.js +70 -0
  182. package/src/server/fireflies.js +31 -0
  183. package/src/server/fireworks-ai.js +16 -0
  184. package/src/server/gatewayapi.js +48 -0
  185. package/src/server/gemini-enterprise.js +47 -0
  186. package/src/server/github-api.js +90 -0
  187. package/src/server/gitlab-api.js +67 -0
  188. package/src/server/gmail.js +60 -0
  189. package/src/server/gong.js +52 -0
  190. package/src/server/google-ads-search.js +134 -0
  191. package/src/server/google-ads.js +80 -0
  192. package/src/server/google-docs.js +36 -0
  193. package/src/server/google-drive.js +71 -0
  194. package/src/server/google-maps-platform.js +90 -0
  195. package/src/server/google-search-console.js +31 -0
  196. package/src/server/google-sheets.js +60 -0
  197. package/src/server/google-slides.js +36 -0
  198. package/src/server/google.js +61 -0
  199. package/src/server/granola.js +115 -0
  200. package/src/server/graphqlOperation.js +15 -0
  201. package/src/server/hex.js +29 -0
  202. package/src/server/heygen.js +54 -0
  203. package/src/server/hubspot.js +85 -0
  204. package/src/server/incident-io.js +68 -0
  205. package/src/server/inngest.js +86 -0
  206. package/src/server/jsonOperation.js +34 -0
  207. package/src/server/klipy.js +34 -0
  208. package/src/server/lexware.js +59 -0
  209. package/src/server/lightspeed.js +82 -0
  210. package/src/server/linear.js +52 -0
  211. package/src/server/linkedin.js +74 -0
  212. package/src/server/mailgun.js +76 -0
  213. package/src/server/mapbox.js +46 -0
  214. package/src/server/mcpRegistration.js +41 -0
  215. package/src/server/mcpTransport.js +60 -0
  216. package/src/server/microsoft-excel.js +67 -0
  217. package/src/server/microsoft-fabric.js +81 -0
  218. package/src/server/microsoft-onedrive.js +58 -0
  219. package/src/server/microsoft-onenote.js +52 -0
  220. package/src/server/microsoft-outlook.js +58 -0
  221. package/src/server/microsoft-powerpoint.js +5 -0
  222. package/src/server/microsoft-sharepoint.js +46 -0
  223. package/src/server/microsoft-teams.js +29 -0
  224. package/src/server/microsoft-word.js +5 -0
  225. package/src/server/microsoft.js +88 -0
  226. package/src/server/miro.js +29 -0
  227. package/src/server/n8n.js +78 -0
  228. package/src/server/notion.js +76 -0
  229. package/src/server/oura.js +48 -0
  230. package/src/server/paddle.js +83 -0
  231. package/src/server/perplexity.js +15 -0
  232. package/src/server/pipedrive.js +95 -0
  233. package/src/server/polar.js +20 -0
  234. package/src/server/posthog.js +44 -0
  235. package/src/server/prestashop.js +42 -0
  236. package/src/server/replicate.js +42 -0
  237. package/src/server/resend.js +93 -0
  238. package/src/server/salesforce.js +84 -0
  239. package/src/server/sanity.js +31 -0
  240. package/src/server/semrush.js +359 -0
  241. package/src/server/sentry.js +30 -0
  242. package/src/server/sevdesk.js +20 -0
  243. package/src/server/shopify.js +99 -0
  244. package/src/server/slack.js +143 -0
  245. package/src/server/snowflake.js +139 -0
  246. package/src/server/storyblok.js +49 -0
  247. package/src/server/stripe.js +13 -0
  248. package/src/server/supabase.js +13 -0
  249. package/src/server/tally.js +78 -0
  250. package/src/server/telegram.js +55 -0
  251. package/src/server/tiktok.js +86 -0
  252. package/src/server/twilio.js +90 -0
  253. package/src/server/twitch.js +409 -0
  254. package/src/server/wave.js +520 -0
  255. package/src/server/wix.js +1048 -0
  256. package/src/server/wiz.js +117 -0
  257. package/src/server/woocommerce.js +412 -0
  258. package/src/server/wordpress-com.js +236 -0
  259. package/src/server/wordpress-self-hosted.js +134 -0
  260. package/src/server/workday.js +136 -0
  261. package/src/server/x-twitter.js +110 -0
  262. package/src/server/xero.js +397 -0
  263. package/src/server/zoho-books.js +90 -0
  264. package/src/server/zoho-crm.js +89 -0
  265. package/src/server/zohoOAuth.js +21 -0
  266. package/src/shared/ai-models.json +8049 -0
  267. package/src/shared/ai.js +63 -0
  268. package/src/shared/algolia.js +29 -0
  269. package/src/shared/amazon-redshift.js +51 -0
  270. package/src/shared/amplitude.js +28 -0
  271. package/src/shared/atlassian.js +43 -0
  272. package/src/shared/aws.js +72 -0
  273. package/src/shared/canva.js +66 -0
  274. package/src/shared/chargebee.js +26 -0
  275. package/src/shared/clickhouse.js +38 -0
  276. package/src/shared/confidence.js +28 -0
  277. package/src/shared/databricks.js +33 -0
  278. package/src/shared/dbt-semantic-layer.js +32 -0
  279. package/src/shared/definitions.js +140 -0
  280. package/src/shared/figma.js +21 -0
  281. package/src/shared/firebase-cloud-messaging.js +45 -0
  282. package/src/shared/gemini-enterprise.js +33 -0
  283. package/src/shared/gong.js +33 -0
  284. package/src/shared/google-ads-search.js +31 -0
  285. package/src/shared/google-ads.js +28 -0
  286. package/src/shared/google-maps-platform.js +28 -0
  287. package/src/shared/google.js +133 -0
  288. package/src/shared/granola.js +25 -0
  289. package/src/shared/hex.js +28 -0
  290. package/src/shared/inngest.js +35 -0
  291. package/src/shared/lightspeed.js +75 -0
  292. package/src/shared/linkedin.js +27 -0
  293. package/src/shared/logo-dev.js +21 -0
  294. package/src/shared/mapbox.js +38 -0
  295. package/src/shared/mcp.js +92 -0
  296. package/src/shared/microsoft-fabric.js +47 -0
  297. package/src/shared/microsoft.js +231 -0
  298. package/src/shared/miro.js +27 -0
  299. package/src/shared/oura.js +23 -0
  300. package/src/shared/posthog.js +33 -0
  301. package/src/shared/prestashop.js +23 -0
  302. package/src/shared/salesforce.js +36 -0
  303. package/src/shared/semrush.js +24 -0
  304. package/src/shared/shopify.js +51 -0
  305. package/src/shared/siteUrl.js +12 -0
  306. package/src/shared/slack.js +88 -0
  307. package/src/shared/snowflake.js +55 -0
  308. package/src/shared/tiktok.js +22 -0
  309. package/src/shared/tokens.js +747 -0
  310. package/src/shared/twilio.js +41 -0
  311. package/src/shared/twitch.js +49 -0
  312. package/src/shared/wave.js +52 -0
  313. package/src/shared/wix.js +40 -0
  314. package/src/shared/wiz.js +41 -0
  315. package/src/shared/wordpress-com.js +30 -0
  316. package/src/shared/wordpress.js +69 -0
  317. package/src/shared/workday.js +50 -0
  318. package/src/shared/x-twitter.js +19 -0
  319. package/src/shared/xero.js +46 -0
  320. package/src/shared/zoho-books.js +39 -0
  321. package/src/shared/zoho-crm.js +43 -0
  322. package/src/shared/zoho-regions.js +12 -0
  323. package/test/ai.test.js +136 -0
  324. package/test/airtable.test.js +125 -0
  325. package/test/algolia.test.js +207 -0
  326. package/test/amazon-redshift.test.js +272 -0
  327. package/test/amplitude.test.js +239 -0
  328. package/test/apify.test.js +93 -0
  329. package/test/apollo-io.test.js +125 -0
  330. package/test/asana.test.js +99 -0
  331. package/test/ashby.test.js +97 -0
  332. package/test/atlassian.test.js +249 -0
  333. package/test/attention.test.js +79 -0
  334. package/test/aws.test.js +405 -0
  335. package/test/brevo.test.js +71 -0
  336. package/test/calendly.test.js +136 -0
  337. package/test/canva.test.js +258 -0
  338. package/test/chargebee.test.js +195 -0
  339. package/test/clickhouse.test.js +278 -0
  340. package/test/confidence.test.js +205 -0
  341. package/test/databricks.test.js +253 -0
  342. package/test/dbt-semantic-layer.test.js +221 -0
  343. package/test/designMcp.test.js +336 -0
  344. package/test/elevenlabs.test.js +52 -0
  345. package/test/firebase-cloud-messaging.test.js +242 -0
  346. package/test/firecrawl.test.js +66 -0
  347. package/test/gemini-enterprise.test.js +218 -0
  348. package/test/github-api.test.js +167 -0
  349. package/test/gitlab-api.test.js +160 -0
  350. package/test/gong.test.js +192 -0
  351. package/test/google-ads.test.js +350 -0
  352. package/test/google-maps-platform.test.js +191 -0
  353. package/test/google.test.js +710 -0
  354. package/test/granola.test.js +240 -0
  355. package/test/hex.test.js +253 -0
  356. package/test/heygen.test.js +121 -0
  357. package/test/hubspot.test.js +103 -0
  358. package/test/inngest.test.js +219 -0
  359. package/test/lightspeed.test.js +250 -0
  360. package/test/linear.test.js +146 -0
  361. package/test/linkedin.test.js +243 -0
  362. package/test/logo-dev.test.js +79 -0
  363. package/test/mailgun.test.js +107 -0
  364. package/test/mapbox.test.js +181 -0
  365. package/test/mcp.test.js +227 -0
  366. package/test/microsoft-fabric.test.js +258 -0
  367. package/test/microsoft.test.js +554 -0
  368. package/test/n8nOAuth.test.js +188 -0
  369. package/test/notionOAuth.test.js +183 -0
  370. package/test/oura.test.js +211 -0
  371. package/test/paddleCatalogue.test.js +51 -0
  372. package/test/paddlePermissions.test.js +101 -0
  373. package/test/perplexityComposition.test.js +39 -0
  374. package/test/pipedriveOAuth.test.js +140 -0
  375. package/test/posthog.test.js +200 -0
  376. package/test/prestashop.test.js +207 -0
  377. package/test/resend.test.js +163 -0
  378. package/test/salesforce.test.js +276 -0
  379. package/test/semrush.test.js +479 -0
  380. package/test/sentry.test.js +35 -0
  381. package/test/shopify.test.js +247 -0
  382. package/test/slack.test.js +320 -0
  383. package/test/snowflake.test.js +391 -0
  384. package/test/tally.test.js +75 -0
  385. package/test/telegram.test.js +58 -0
  386. package/test/tiktok.test.js +243 -0
  387. package/test/tokens.test.js +1079 -0
  388. package/test/twilio.test.js +221 -0
  389. package/test/twitch.test.js +573 -0
  390. package/test/wave.test.js +678 -0
  391. package/test/wix.test.js +1099 -0
  392. package/test/wiz.test.js +185 -0
  393. package/test/wordpress-com.test.js +469 -0
  394. package/test/wordpress.test.js +660 -0
  395. package/test/workday.test.js +314 -0
  396. package/test/x-twitter.test.js +256 -0
  397. package/test/xero.test.js +700 -0
  398. package/test/zoho-books.test.js +253 -0
  399. package/test/zoho-crm.test.js +223 -0
@@ -0,0 +1,61 @@
1
+ import { ConnectorError } from "@jskit-ai/connectors-core/server";
2
+ import { ashbyDefinition } from "../shared/tokens.js";
3
+ import { jsonOperation } from "./jsonOperation.js";
4
+
5
+ const text = { type: "string", minLength: 1, maxLength: 256 };
6
+ const uuid = { type: "string", required: true,
7
+ pattern: "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$" };
8
+ const page = { limit: { type: "integer", min: 1, max: 100, defaultTo: 25 },
9
+ cursor: { type: "string", minLength: 1, maxLength: 8192 }, syncToken: { type: "string", minLength: 1, maxLength: 8192 } };
10
+ const candidate = { name: text, email: { ...text, pattern: "^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$" }, phoneNumber: text };
11
+ function response(check) {
12
+ return result => {
13
+ if (result?.success === false) throw new ConnectorError("connector_provider_failed", "Ashby could not complete the operation.", { statusCode: 502 });
14
+ return result?.success === true && check(result);
15
+ };
16
+ }
17
+ const record = response(result => typeof result.results?.id === "string");
18
+ const paged = response(result => Array.isArray(result.results) && typeof result.moreDataAvailable === "boolean" &&
19
+ (!result.moreDataAvailable || typeof result.nextCursor === "string" && result.nextCursor.length > 0) &&
20
+ (result.syncToken === undefined || typeof result.syncToken === "string" && result.syncToken.length > 0));
21
+ const call = (endpoint, fields, validate = record) => jsonOperation(`https://api.ashbyhq.com/${endpoint}`, fields, validate, "POST");
22
+
23
+ const candidateUpdate = call("candidate.update", { candidateId: uuid, ...candidate,
24
+ sendNotifications: { type: "boolean", defaultTo: false } });
25
+ const ashbyProvider = Object.freeze({
26
+ ...ashbyDefinition,
27
+ apiOrigins: ["https://api.ashbyhq.com"],
28
+ apiKey: { headers: (key) => {
29
+ if (/[:\s\p{Cc}]/u.test(key)) throw new ConnectorError("connector_binding_missing", "Use the Ashby API key without spaces, control characters or colons.");
30
+ return { Authorization: `Basic ${Buffer.from(`${key}:`).toString("base64")}`, Accept: "application/json; version=1" };
31
+ } },
32
+ checkOperation: "jobs.list",
33
+ operations: {
34
+ "jobs.list": call("job.list", page, paged),
35
+ "jobs.get": call("job.info", { id: uuid }),
36
+ "candidates.list": call("candidate.list", page, paged),
37
+ "candidates.get": call("candidate.info", { id: uuid }),
38
+ "candidates.create": call("candidate.create", { ...candidate, name: { ...text, required: true } }),
39
+ "candidates.update": { ...candidateUpdate, request(input) {
40
+ const request = candidateUpdate.request(input);
41
+ if (!["name", "email", "phoneNumber"].some(key => Object.hasOwn(request.body, key))) {
42
+ throw new ConnectorError("connector_input_invalid", "Supply at least one candidate profile change.", { statusCode: 422 });
43
+ }
44
+ return request;
45
+ } },
46
+ "applications.list": call("application.list", { ...page, jobId: { ...uuid, required: false },
47
+ status: { type: "string", enum: ["Hired", "Archived", "Active", "Lead"] }
48
+ }, paged),
49
+ "applications.get": call("application.info", { applicationId: uuid }),
50
+ "applications.create": call("application.create", { candidateId: uuid, jobId: uuid,
51
+ interviewPlanId: { ...uuid, required: false }, interviewStageId: { ...uuid, required: false }
52
+ }),
53
+ "applications.changeStage": call("application.changeStage", { applicationId: uuid, interviewStageId: uuid,
54
+ archiveReasonId: { ...uuid, required: false }
55
+ }),
56
+ "interviewPlans.list": call("interviewPlan.list", { ...page, includeArchived: { type: "boolean", defaultTo: false } }, paged),
57
+ "archiveReasons.list": call("archiveReason.list", { includeArchived: { type: "boolean", defaultTo: false } }, response(result => Array.isArray(result.results))),
58
+ "interviewStages.list": call("interviewStage.list", { interviewPlanId: uuid }, response(result => Array.isArray(result.results)))
59
+ }
60
+ });
61
+ export { ashbyProvider };
@@ -0,0 +1,32 @@
1
+ import { createSchema } from "json-rest-schema";
2
+ import { validateSchemaPayload } from "@jskit-ai/kernel/shared/validators";
3
+ import { atlassianDefinition } from "../shared/atlassian.js";
4
+ import { mcpOperations, exchangeMcp } from "./mcpTransport.js";
5
+ import { mcpRegistrationFields, registerMcpClient } from "./mcpRegistration.js";
6
+
7
+ const resource = "https://mcp.atlassian.com/v2/mcp";
8
+ const oauth = Object.freeze({
9
+ issuer: "https://auth.atlassian.com/VCeDsk8ZHncYF1g234fKtc4lNipbBhu3",
10
+ authorization_endpoint: "https://auth.atlassian.com/authorize",
11
+ token_endpoint: "https://auth.atlassian.com/oauth/token",
12
+ registration_endpoint: atlassianDefinition.setup.clientRegistrationEndpoint,
13
+ response_types_supported: ["code"]
14
+ });
15
+ const atlassianProvider = Object.freeze({
16
+ ...atlassianDefinition, oauth, oauthResource: resource,
17
+ apiOrigins: ["https://mcp.atlassian.com"], checkOperation: "tools.list", exchange: exchangeMcp,
18
+ operations: mcpOperations(() => `${resource}?tools=all`)
19
+ });
20
+ const registrationSchema = createSchema({
21
+ ...mcpRegistrationFields,
22
+ scopes: { type: "array", required: true, minLength: 1,
23
+ items: { type: "string", enum: atlassianDefinition.scopes.map((scope) => scope.value) },
24
+ validator: (scopes) => scopes.length > 0 && new Set(scopes).size === scopes.length || "Select at least one permission and include each once." }
25
+ });
26
+
27
+ async function registerAtlassianClient(input, options) {
28
+ const values = validateSchemaPayload({ schema: registrationSchema, mode: "replace" }, input, { statusCode: 422 });
29
+ return registerMcpClient("Atlassian", oauth, values, options);
30
+ }
31
+
32
+ export { atlassianProvider, registerAtlassianClient };
@@ -0,0 +1,93 @@
1
+ import { ConnectorError } from "@jskit-ai/connectors-core/server";
2
+ import { createSchema } from "json-rest-schema";
3
+ import { validateSchemaPayload } from "@jskit-ai/kernel/shared/validators";
4
+ import { attentionDefinition } from "../shared/tokens.js";
5
+ import { jsonOperation } from "./jsonOperation.js";
6
+
7
+ const text = { type: "string", minLength: 1, maxLength: 2000 };
8
+ const id = { type: "string", minLength: 1, maxLength: 100, pattern: "^[A-Za-z0-9_-]+$" };
9
+ const requiredId = { ...id, required: true };
10
+ const ids = { type: "array", items: id, validator: value => value.length <= 100 || "Use at most 100 IDs." };
11
+ const bool = { type: "boolean" };
12
+ const page = { page: { type: "integer", min: 1, defaultTo: 1 }, size: { type: "integer", min: 1, max: 200, defaultTo: 50 } };
13
+ const dateTime = { ...text, validator: value => /^\d{4}-\d{2}-\d{2}T/.test(value) && Number.isFinite(Date.parse(value)) || "Use an ISO timestamp." };
14
+ const list = result => Array.isArray(result?.data);
15
+ const record = result => result?.data !== null && typeof result?.data === "object" && !Array.isArray(result.data) && typeof (result.data.uuid ?? result.data.id) === "string";
16
+ const conversation = result => result?.type === "conversations" && typeof result.id === "string" && result.attributes !== null && typeof result.attributes === "object" && !Array.isArray(result.attributes);
17
+ function operation(method, endpoint, fields, validateResult, prepare = value => value) {
18
+ const schema = createSchema(fields);
19
+ return { scopes: [], request(input) {
20
+ const values = prepare(validateSchemaPayload({ schema, mode: "replace" }, input, { statusCode: 422 }));
21
+ const { id: resource, ...body } = values;
22
+ const url = new URL(`https://api.attention.tech/v2/${endpoint.replace("{id}", resource || "")}`);
23
+ if (method === "GET") { for (const [key, value] of Object.entries(body)) url.searchParams.set(key, String(value)); return { method, url: url.href }; }
24
+ if (method === "DELETE") return { method, url: url.href };
25
+ if (!Object.keys(body).length) throw new ConnectorError("connector_input_invalid", "Supply at least one change.", { statusCode: 422 });
26
+ return { method, url: url.href, body };
27
+ }, validateResult };
28
+ }
29
+ const team = { type: "object", schema: createSchema({ uuid: requiredId, primary: bool }) };
30
+ const attentionProvider = Object.freeze({
31
+ ...attentionDefinition,
32
+ apiOrigins: ["https://api.attention.tech"],
33
+ apiKey: { headers: (key) => ({ Authorization: `Bearer ${key}` }) },
34
+ checkOperation: "conversations.list",
35
+ operations: {
36
+ "conversations.get": operation("GET", "conversations/{id}", { id: requiredId, by: { type: "string", enum: ["id", "external_id"] },
37
+ detailedTranscript: { ...bool, defaultTo: true }, "filter[include_internal_participants]": bool,
38
+ "filter[include_zoom_metadata]": bool, "filter[include_import_metadata]": bool }, conversation),
39
+ "conversations.update": operation("PUT", "conversations/{id}", { id: requiredId, title: text, labels: { type: "object", additionalProperties: true } }, conversation),
40
+ "conversations.archive": operation("DELETE", "conversations/{id}", { id: requiredId }, conversation),
41
+ "conversations.import": operation("POST", "conversations/import", {
42
+ mediaURL: { ...text, required: true, validator: value => { try { const url = new URL(value); return url.protocol === "https:" && !url.username && !url.password || "Use an HTTPS media URL without embedded credentials."; } catch { return "Use an HTTPS media URL."; } } },
43
+ userID: requiredId, applicationName: text, applicationExternalID: id, conversationTitle: text,
44
+ conversationStartedAt: dateTime, opportunityID: id, skipScorecardCalculation: bool,
45
+ skipCRMFieldsCalculation: bool, skipOpportunitiesExport: bool
46
+ }, result => typeof result?.uuid === "string"),
47
+ "snippets.create": operation("POST", "snippets", { user_uuid: requiredId, conversation_id: requiredId,
48
+ internal: { ...bool, required: true }, notify_views: { ...bool, required: true }, title: text, notes: { type: "string", maxLength: 20000 },
49
+ video: { type: "object", required: true, schema: createSchema({ start_time: { type: "number", required: true, min: 0 }, end_time: { type: "number", required: true, min: 0 } }),
50
+ validator: value => value.end_time > value.start_time || "End time must follow start time." }
51
+ }, result => typeof result?.id === "string" && typeof result.url === "string"),
52
+ "analysis.ask": operation("POST", "ask_attention/v2", { conversations_ids: { ...ids, required: true },
53
+ deal_id: { type: "string", required: true, maxLength: 100 }, prompt: { ...text, required: true, maxLength: 20000 }
54
+ }, result => Array.isArray(result) && result.every(item => typeof item?.output === "string" && typeof item.error === "string" && typeof item.conversation_id === "string")),
55
+ "scorecards.list": operation("GET", "scorecards", page, list),
56
+ "scorecards.get": operation("GET", "scorecards/{id}", { id: requiredId }, record),
57
+ "scorecardItems.list": operation("GET", "scorecards/{id}/items", { id: requiredId, ...page }, list),
58
+ "scorecards.summary": operation("POST", "scorecards/summary", { fromDateTime: { ...dateTime, required: true }, toDateTime: { ...dateTime, required: true },
59
+ scorecardUUID: requiredId, scorecardsItemsUUIDs: { ...ids, required: true }, teamUUIDs: { ...ids, required: true }, userUUIDs: { ...ids, required: true }
60
+ }, list, value => { if (Date.parse(value.fromDateTime) >= Date.parse(value.toDateTime)) throw new ConnectorError("connector_input_invalid", "End time must follow start time.", { statusCode: 422 }); return value; }),
61
+ "scorecardResults.create": operation("POST", "createScorecardResult", { scorecard_uuid: requiredId, conversation_uuid: requiredId,
62
+ summary: { ...text, required: true, maxLength: 20000 }, items: { type: "array", required: true,
63
+ items: { type: "object", schema: createSchema({ scorecard_item_uuid: requiredId, numeric_result: { type: "integer" }, description: { ...text, required: true } }) },
64
+ validator: value => value.length > 0 && value.length <= 100 || "Supply 1–100 scoring items." }
65
+ }, result => result?.success === true),
66
+ "users.list": operation("GET", "organizations/users", { teamUUID: id }, list),
67
+ "roles.list": operation("GET", "organizations/roles", {}, list),
68
+ "users.create": operation("POST", "organizations/users", { email: { ...text, required: true, pattern: "^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$" },
69
+ first_name: { ...text, required: true }, last_name: { ...text, required: true }, roleUUID: requiredId,
70
+ seat_type: { type: "string", required: true, enum: ["listener", "recording"] },
71
+ teams: { type: "array", required: true, items: team, validator: value => value.length > 0 && value.length <= 100 || "Supply 1–100 teams." }
72
+ }, record),
73
+ "users.update": operation("PATCH", "organizations/users/{id}", { id: requiredId, firstName: text, lastName: text,
74
+ roleUUID: id, seat_type: { type: "string", enum: ["listener", "recording"] }, teamUUIDsToRemove: ids,
75
+ teamsToAdd: { type: "array", items: team, validator: value => value.length <= 100 || "Use at most 100 teams." }
76
+ }, record),
77
+ "users.delete": operation("DELETE", "organizations/users/{id}", { id: requiredId }, result => result === null || result !== null && typeof result === "object" && !Array.isArray(result) && Object.keys(result).length === 0),
78
+ "teams.list": operation("GET", "organizations/teams", {}, list),
79
+ "teams.create": operation("POST", "organizations/teams", { name: { ...text, required: true }, parentTeamUUID: id }, record),
80
+ "teams.update": operation("PATCH", "organizations/teams/{id}", { id: requiredId, name: text, parentTeamUUID: id }, record),
81
+ "conversations.list": jsonOperation("https://api.attention.tech/v2/conversations/list", {
82
+ page: { type: "integer", min: 1, max: 100000, defaultTo: 1 },
83
+ size: { type: "integer", min: 1, max: 50, defaultTo: 20 },
84
+ "filter[title]": { type: "string", minLength: 1, maxLength: 500 },
85
+ "filter[hide_internal]": { type: "boolean" },
86
+ detailedTranscript: { type: "boolean", defaultTo: false }
87
+ }, (result) => Array.isArray(result?.data) && (result.meta === undefined ||
88
+ result.meta !== null && typeof result.meta === "object" && !Array.isArray(result.meta) &&
89
+ ["totalRecords", "pageCount", "pageNumber", "pageSize"].every((field) => result.meta[field] === undefined ||
90
+ Number.isInteger(result.meta[field]) && result.meta[field] >= 0)))
91
+ }
92
+ });
93
+ export { attentionProvider };
@@ -0,0 +1,82 @@
1
+ import { AthenaClient, GetWorkGroupCommand, StartQueryExecutionCommand, GetQueryExecutionCommand, GetQueryResultsCommand, StopQueryExecutionCommand, ListDataCatalogsCommand, ListDatabasesCommand, ListTableMetadataCommand } from "@aws-sdk/client-athena";
2
+ import { ConnectorError } from "@jskit-ai/connectors-core/server";
3
+ import { awsAthenaDefinition } from "../shared/aws.js";
4
+ import { awsOrigin, awsOperation, awsExchange } from "./awsTransport.js";
5
+
6
+ const queryId = { type: "string", required: true, minLength: 1, maxLength: 128, pattern: "^\\S+$" };
7
+ const validQuery = (r) => typeof r?.QueryExecution?.QueryExecutionId === "string" && typeof r.QueryExecution.WorkGroup === "string" &&
8
+ ["QUEUED", "RUNNING", "SUCCEEDED", "FAILED", "CANCELLED"].includes(r.QueryExecution.Status?.State);
9
+ const metadataPage = {
10
+ maxResults: { type: "integer", min: 1, max: 50, defaultTo: 50 },
11
+ nextToken: { type: "string", noTrim: true, minLength: 1, maxLength: 1024 }
12
+ };
13
+ const catalog = { type: "string", required: true, noTrim: true, minLength: 1, maxLength: 256 };
14
+ const metadataInput = (v, s) => ({ input: { WorkGroup: s.workgroup, MaxResults: v.maxResults,
15
+ ...(v.nextToken ? { NextToken: v.nextToken } : {}),
16
+ ...(v.catalog ? { CatalogName: v.catalog } : {}),
17
+ ...(v.database ? { DatabaseName: v.database } : {}) } });
18
+ const validCursor = (r) => r.NextToken === undefined || typeof r.NextToken === "string" && r.NextToken.length > 0 && r.NextToken.length <= 1024;
19
+ const validColumns = (columns) => columns === undefined || Array.isArray(columns) && columns.every((c) => typeof c?.Name === "string" && typeof c.Type === "string");
20
+ const queryInput = (v) => ({ input: { QueryExecutionId: v.queryId } });
21
+ const awsAthenaProvider = Object.freeze({
22
+ ...awsAthenaDefinition,
23
+ apiOrigins: (settings) => [awsOrigin("athena", settings)], apiKey: { headers: () => ({}) }, checkOperation: "workgroup.get",
24
+ exchange: awsExchange("athena", AthenaClient, async (client, body, { settings, signal }) => {
25
+ const send = (command) => client.send(command, { abortSignal: signal });
26
+ if (body.command === "GetWorkGroup") {
27
+ const result = await send(new GetWorkGroupCommand(body.input));
28
+ if (result.WorkGroup?.Name !== settings.workgroup) throw new ConnectorError("connector_response_invalid", "Athena returned a different workgroup.", { statusCode: 502 });
29
+ return result;
30
+ }
31
+ if (body.command === "ListDataCatalogs") return send(new ListDataCatalogsCommand(body.input));
32
+ if (body.command === "ListDatabases") return send(new ListDatabasesCommand(body.input));
33
+ if (body.command === "ListTableMetadata") return send(new ListTableMetadataCommand(body.input));
34
+ if (body.command === "StartQueryExecution") return send(new StartQueryExecutionCommand(body.input));
35
+ const query = await send(new GetQueryExecutionCommand({ QueryExecutionId: body.input.QueryExecutionId }));
36
+ if (!validQuery(query) || query.QueryExecution.QueryExecutionId !== body.input.QueryExecutionId) {
37
+ throw new ConnectorError("connector_response_invalid", "Athena returned an unexpected query.", { statusCode: 502 });
38
+ }
39
+ if (query.QueryExecution.WorkGroup !== settings.workgroup) {
40
+ throw new ConnectorError("connector_access_denied", "This query belongs to a different workgroup.", { statusCode: 403 });
41
+ }
42
+ if (body.command === "GetQueryExecution") return query;
43
+ if (body.command === "GetQueryResults") return send(new GetQueryResultsCommand(body.input));
44
+ return send(new StopQueryExecutionCommand(body.input));
45
+ }),
46
+ operations: {
47
+ "catalogs.list": awsOperation("athena", "ListDataCatalogs", {
48
+ ...metadataPage, maxResults: { ...metadataPage.maxResults, min: 2 }
49
+ }, metadataInput, (r) => Array.isArray(r?.DataCatalogsSummary) && r.DataCatalogsSummary.length <= 50 &&
50
+ r.DataCatalogsSummary.every((c) => typeof c?.CatalogName === "string" && typeof c.Type === "string") && validCursor(r)),
51
+ "databases.list": awsOperation("athena", "ListDatabases", { ...metadataPage, catalog }, metadataInput,
52
+ (r) => Array.isArray(r?.DatabaseList) && r.DatabaseList.length <= 50 &&
53
+ r.DatabaseList.every((d) => typeof d?.Name === "string") && validCursor(r)),
54
+ "tables.list": awsOperation("athena", "ListTableMetadata", { ...metadataPage, catalog,
55
+ database: { type: "string", required: true, noTrim: true, minLength: 1, maxLength: 128 }
56
+ }, metadataInput, (r) => Array.isArray(r?.TableMetadataList) && r.TableMetadataList.length <= 50 &&
57
+ r.TableMetadataList.every((t) => typeof t?.Name === "string" && validColumns(t.Columns) && validColumns(t.PartitionKeys)) && validCursor(r)),
58
+ "workgroup.get": awsOperation("athena", "GetWorkGroup", {}, (_, s) => ({ input: { WorkGroup: s.workgroup } }),
59
+ (r) => typeof r?.WorkGroup?.Name === "string" && ["ENABLED", "DISABLED"].includes(r.WorkGroup.State)),
60
+ "query.start": awsOperation("athena", "StartQueryExecution", {
61
+ sql: { type: "string", required: true, noTrim: true, minLength: 1, maxLength: 262144,
62
+ validator: (value) => (value.trim().length > 0 && Buffer.byteLength(value, "utf8") <= 262144) || "Enter SQL of at most 262144 UTF-8 bytes." },
63
+ clientRequestToken: { type: "string", required: true, noTrim: true, minLength: 32, maxLength: 128 },
64
+ database: { type: "string", minLength: 1, maxLength: 255 }, catalog: { type: "string", minLength: 1, maxLength: 256 }
65
+ }, (v, s) => ({ input: { QueryString: v.sql, ClientRequestToken: v.clientRequestToken, WorkGroup: s.workgroup,
66
+ ...(v.database || v.catalog ? { QueryExecutionContext: { ...(v.database ? { Database: v.database } : {}), ...(v.catalog ? { Catalog: v.catalog } : {}) } } : {}),
67
+ ...(s.resultLocation ? { ResultConfiguration: { OutputLocation: s.resultLocation } } : {}) } }),
68
+ (r) => typeof r?.QueryExecutionId === "string" && /^\S{1,128}$/u.test(r.QueryExecutionId)),
69
+ "query.status": awsOperation("athena", "GetQueryExecution", { queryId }, queryInput, validQuery),
70
+ "query.results": awsOperation("athena", "GetQueryResults", { queryId,
71
+ maxResults: { type: "integer", min: 1, max: 1000, defaultTo: 100 }, nextToken: { type: "string", noTrim: true, minLength: 1, maxLength: 1024 }
72
+ }, (v) => ({ input: { QueryExecutionId: v.queryId, MaxResults: v.maxResults, QueryResultType: "DATA_ROWS", ...(v.nextToken ? { NextToken: v.nextToken } : {}) } }),
73
+ (r) => Array.isArray(r?.ResultSet?.ResultSetMetadata?.ColumnInfo) &&
74
+ r.ResultSet.ResultSetMetadata.ColumnInfo.every((c) => typeof c.Name === "string" && typeof c.Type === "string") &&
75
+ Array.isArray(r.ResultSet.Rows) && r.ResultSet.Rows.length <= 1000 &&
76
+ r.ResultSet.Rows.every((row) => Array.isArray(row.Data) && row.Data.every((cell) => cell && (cell.VarCharValue === undefined || typeof cell.VarCharValue === "string"))) &&
77
+ (r.NextToken === undefined || typeof r.NextToken === "string" && r.NextToken.length > 0)),
78
+ "query.cancel": awsOperation("athena", "StopQueryExecution", { queryId }, queryInput, (r) => r && Object.keys(r).length === 0)
79
+ }
80
+ });
81
+
82
+ export { awsAthenaProvider };
@@ -0,0 +1,49 @@
1
+ import { S3Client, ListObjectsV2Command, GetObjectCommand, PutObjectCommand } from "@aws-sdk/client-s3";
2
+ import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
3
+ import { ConnectorError } from "@jskit-ai/connectors-core/server";
4
+ import { awsS3Definition } from "../shared/aws.js";
5
+ import { awsOrigin, awsOperation, awsExchange } from "./awsTransport.js";
6
+
7
+ const key = { type: "string", required: true, noTrim: true, minLength: 1, maxLength: 1024,
8
+ validator: (value) => (Buffer.byteLength(value, "utf8") <= 1024 && !value.split("/").some((part) => part === "." || part === "..")) || "Use an object key of at most 1024 UTF-8 bytes, without standalone . or .. path segments." };
9
+ const expires = { type: "integer", min: 1, max: 900, defaultTo: 300 };
10
+ const validUrl = (result) => typeof result?.url === "string" && Number.isInteger(result.expiresInSeconds) && ["GET", "PUT"].includes(result.method);
11
+ const awsS3Provider = Object.freeze({
12
+ ...awsS3Definition,
13
+ apiOrigins: (settings) => [awsOrigin("s3", settings)],
14
+ apiKey: { headers: () => ({}) },
15
+ checkOperation: "objects.list",
16
+ exchange: awsExchange("s3", S3Client, async (client, body, { settings, signal }) => {
17
+ if (body.command === "ListObjectsV2") {
18
+ const result = await client.send(new ListObjectsV2Command(body.input), { abortSignal: signal });
19
+ if (result.Name !== settings.bucket || (result.Contents?.length || 0) > body.input.MaxKeys) {
20
+ throw new ConnectorError("connector_response_invalid", "S3 returned an unexpected bucket or page size.", { statusCode: 502 });
21
+ }
22
+ return result;
23
+ }
24
+ const upload = body.command === "PutObject";
25
+ const command = upload ? new PutObjectCommand(body.input) : new GetObjectCommand(body.input);
26
+ const url = await getSignedUrl(client, command, { expiresIn: body.expiresInSeconds });
27
+ return { url, method: upload ? "PUT" : "GET", expiresInSeconds: body.expiresInSeconds };
28
+ }),
29
+ operations: {
30
+ "objects.list": awsOperation("s3", "ListObjectsV2", {
31
+ maxKeys: { type: "integer", min: 1, max: 1000, defaultTo: 100 },
32
+ prefix: { type: "string", noTrim: true, maxLength: 1024 },
33
+ delimiter: { type: "string", enum: ["/"] },
34
+ continuationToken: { type: "string", noTrim: true, minLength: 1, maxLength: 8192 }
35
+ }, (v, s) => ({ input: { Bucket: s.bucket, MaxKeys: v.maxKeys, EncodingType: "url",
36
+ ...(v.prefix === undefined ? {} : { Prefix: v.prefix }), ...(v.delimiter ? { Delimiter: v.delimiter } : {}),
37
+ ...(v.continuationToken ? { ContinuationToken: v.continuationToken } : {}) } }),
38
+ (r) => typeof r?.Name === "string" && typeof r.IsTruncated === "boolean" && Number.isInteger(r.KeyCount) && r.KeyCount >= 0 &&
39
+ (!r.IsTruncated || typeof r.NextContinuationToken === "string" && r.NextContinuationToken.length > 0) &&
40
+ (r.Contents === undefined || Array.isArray(r.Contents) && r.Contents.every((o) => typeof o.Key === "string" && Number.isInteger(o.Size) && o.Size >= 0)) &&
41
+ (r.CommonPrefixes === undefined || Array.isArray(r.CommonPrefixes) && r.CommonPrefixes.every((p) => typeof p.Prefix === "string")), ["read"]),
42
+ "objects.downloadUrl": awsOperation("s3", "GetObject", { key, expiresInSeconds: expires },
43
+ (v, s) => ({ input: { Bucket: s.bucket, Key: v.key }, expiresInSeconds: v.expiresInSeconds }), validUrl, ["read"]),
44
+ "objects.uploadUrl": awsOperation("s3", "PutObject", { key, expiresInSeconds: expires },
45
+ (v, s) => ({ input: { Bucket: s.bucket, Key: v.key }, expiresInSeconds: v.expiresInSeconds }), validUrl, ["write"])
46
+ }
47
+ });
48
+
49
+ export { awsS3Provider };
@@ -0,0 +1,75 @@
1
+ import { createSchema } from "json-rest-schema";
2
+ import { validateSchemaPayload } from "@jskit-ai/kernel/shared/validators";
3
+ import { FetchHttpHandler } from "@smithy/fetch-http-handler";
4
+ import { ConnectorError } from "@jskit-ai/connectors-core/server";
5
+
6
+ const awsOrigin = (service, settings) => `https://${service}.${settings.region}.amazonaws.com`;
7
+
8
+ function awsOperation(service, command, fields, makeInput, validateResult, scopes = []) {
9
+ const schema = createSchema(fields);
10
+ return {
11
+ scopes,
12
+ request(input, settings) {
13
+ const values = validateSchemaPayload({ schema, mode: "replace" }, input, { statusCode: 422 });
14
+ return { method: "POST", url: awsOrigin(service, settings), body: { command, ...makeInput(values, settings) } };
15
+ },
16
+ validateResult
17
+ };
18
+ }
19
+
20
+ function awsExchange(service, Client, execute) {
21
+ return async (endpoint, options, { settings, apiKey, resolveReference, fetchImpl }) => {
22
+ let credentials;
23
+ try {
24
+ const accessKeyId = await resolveReference(settings.accessKeyIdRef);
25
+ const sessionToken = settings.sessionTokenRef ? await resolveReference(settings.sessionTokenRef) : undefined;
26
+ if (typeof accessKeyId !== "string" || !/^[A-Z0-9]{16,128}$/u.test(accessKeyId) || !apiKey ||
27
+ (settings.sessionTokenRef && (typeof sessionToken !== "string" || !sessionToken || /\s/u.test(sessionToken))) ||
28
+ (accessKeyId.startsWith("ASIA") && !sessionToken)) throw new Error("Invalid credentials");
29
+ credentials = { accessKeyId, secretAccessKey: apiKey, ...(sessionToken ? { sessionToken } : {}) };
30
+ } catch {
31
+ throw new ConnectorError("connector_binding_missing", "Provide the AWS access key ID, secret access key and any required session token bindings.");
32
+ }
33
+ const origin = awsOrigin(service, settings);
34
+ if (new URL(endpoint).origin !== origin) throw new ConnectorError("connector_destination_invalid", "The AWS region does not match the operation destination.");
35
+ const client = new Client({ region: settings.region, endpoint: origin, credentials, maxAttempts: 1,
36
+ ...(service === "s3" ? { forcePathStyle: true, followRegionRedirects: false, requestChecksumCalculation: "WHEN_REQUIRED" } : {}),
37
+ requestHandler: new FetchHttpHandler({
38
+ requestInit: () => ({ credentials: "omit", redirect: "error" }),
39
+ customFetch: async (request) => {
40
+ if (new URL(request.url).origin !== origin) throw new ConnectorError("connector_destination_invalid", "The AWS SDK selected an unexpected destination.");
41
+ options.signal.throwIfAborted();
42
+ return fetchImpl(request.url, { method: request.method, headers: request.headers,
43
+ ...(request.body ? { body: await request.text() } : {}), signal: options.signal, credentials: "omit", redirect: "error" });
44
+ }
45
+ })
46
+ });
47
+ try {
48
+ options.signal.throwIfAborted();
49
+ const result = await execute(client, options.body, { settings, signal: options.signal });
50
+ options.signal.throwIfAborted();
51
+ const { $metadata, ...data } = result;
52
+ return data;
53
+ } catch (error) {
54
+ options.signal.throwIfAborted();
55
+ if (error instanceof ConnectorError || ["AbortError", "TimeoutError"].includes(error.name)) throw error;
56
+ const status = error.$metadata?.httpStatusCode;
57
+ if (["ExpiredToken", "ExpiredTokenException", "InvalidAccessKeyId", "InvalidClientTokenId", "UnrecognizedClientException", "InvalidToken", "SignatureDoesNotMatch"].includes(error.name) || status === 401) {
58
+ throw new ConnectorError("connector_reconnect_required", "Renew the AWS credential bindings and connect again.", { statusCode: 401 });
59
+ }
60
+ if (["TooManyRequestsException", "ThrottlingException", "SlowDown", "RequestLimitExceeded", "ActiveStatementsExceededException", "ActiveSessionsExceededException", "ActiveWaitingRequestsExceededException"].includes(error.name) || status === 429) {
61
+ throw new ConnectorError("connector_rate_limited", "The AWS request limit was reached.", { statusCode: 429 });
62
+ }
63
+ if (status === 403) throw new ConnectorError("connector_permission_denied", "AWS denied this operation. Check IAM, resource and database permissions.", { statusCode: 403 });
64
+ if (status === 404 || error.name === "ResourceNotFoundException") throw new ConnectorError("connector_not_found", "The AWS resource was not found.", { statusCode: 404 });
65
+ if (status === 301 || status === 307 || ["AuthorizationHeaderMalformed", "PermanentRedirect", "InvalidRequestException", "ValidationException"].includes(error.name)) {
66
+ throw new ConnectorError("connector_configuration_invalid", "Check the AWS region, resource and operation settings.", { statusCode: 422 });
67
+ }
68
+ throw new ConnectorError("connector_provider_failed", "The AWS request failed or returned an unreadable response.", { statusCode: 502 });
69
+ } finally {
70
+ client.destroy();
71
+ }
72
+ };
73
+ }
74
+
75
+ export { awsOrigin, awsOperation, awsExchange };
@@ -0,0 +1,97 @@
1
+ import { createSchema } from "json-rest-schema";
2
+ import { validateSchemaPayload } from "@jskit-ai/kernel/shared/validators";
3
+ import { jsonOperation } from "./jsonOperation.js";
4
+ import { bigqueryDefinition } from "../shared/google.js";
5
+ import { googleRead, googleProvider, pageToken } from "./google.js";
6
+ const origin = "https://bigquery.googleapis.com";
7
+ const queryScopes = ["https://www.googleapis.com/auth/bigquery", "https://www.googleapis.com/auth/cloud-platform"];
8
+ const resultFields = {
9
+ maxResults: { type: "integer", min: 1, max: 1000, defaultTo: 100 },
10
+ timeoutMs: { type: "integer", min: 0, max: 10000, defaultTo: 1000 },
11
+ location: { type: "string", minLength: 1, maxLength: 100, pattern: "^[a-zA-Z0-9-]+$" }
12
+ };
13
+ const resultsSchema = createSchema({ ...resultFields,
14
+ jobId: { type: "string", required: true, minLength: 1, maxLength: 1024, pattern: "^[a-zA-Z0-9_-]+$" }, pageToken: { ...pageToken, noTrim: true }
15
+ });
16
+ const validQueryResult = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value) && !value.error &&
17
+ typeof value.jobComplete === "boolean" && (value.rows === undefined || Array.isArray(value.rows) && value.rows.every((row) =>
18
+ Array.isArray(row?.f) && row.f.every((cell) => cell && typeof cell === "object" && !Array.isArray(cell) && Object.hasOwn(cell, "v")))) &&
19
+ (value.schema === undefined || Array.isArray(value.schema?.fields)) &&
20
+ (value.pageToken === undefined || typeof value.pageToken === "string") &&
21
+ (value.totalRows === undefined || (typeof value.totalRows === "string" && /^\d+$/.test(value.totalRows))) &&
22
+ (value.jobComplete || (typeof value.jobReference?.jobId === "string" && value.jobReference.jobId.length > 0)));
23
+ const submitQuery = jsonOperation((settings) => `${origin}/bigquery/v2/projects/${encodeURIComponent(settings.projectId)}/queries`, {
24
+ ...resultFields,
25
+ query: { type: "string", required: true, noTrim: true, minLength: 1, maxLength: 1000000, validator: (value) => value.trim().length > 0 || "Enter SQL." },
26
+ parameters: { type: "array", validator: (values) => (values.length <= 100 && new Set(values.map((value) => value.name)).size === values.length) || "Use at most 100 uniquely named parameters.",
27
+ items: { type: "object", schema: createSchema({
28
+ name: { type: "string", required: true, minLength: 1, maxLength: 128, pattern: "^[a-zA-Z_][a-zA-Z0-9_]*$" },
29
+ type: { type: "string", required: true, enum: ["STRING", "INT64", "FLOAT64", "NUMERIC", "BIGNUMERIC", "BOOL", "DATE", "DATETIME", "TIME", "TIMESTAMP", "BYTES"] },
30
+ value: { type: "string", required: true, noTrim: true, maxLength: 1000000 }
31
+ }) }
32
+ },
33
+ maximumBytesBilled: { type: "string", minLength: 1, maxLength: 19, pattern: "^[0-9]+$" },
34
+ requestId: { type: "string", minLength: 1, maxLength: 36, pattern: "^[a-zA-Z0-9_-]+$" }
35
+ }, validQueryResult, "POST");
36
+ const metadataScopes = [...queryScopes, "https://www.googleapis.com/auth/bigquery.readonly", "https://www.googleapis.com/auth/cloud-platform.read-only"];
37
+ const resourceId = { type: "string", required: true, noTrim: true, minLength: 1, maxLength: 1024,
38
+ // eslint-disable-next-line no-control-regex -- Reject literal control characters in provider input.
39
+ validator: (value) => (!/[\/\\\u0000-\u001f]/u.test(value) && value !== "." && value !== "..") || "Enter a resource ID, not a path." };
40
+ const metadataPage = { maxResults: resultFields.maxResults, pageToken: { ...pageToken, noTrim: true } };
41
+ const jobFields = { jobId: resultsSchema.getFieldDefinitions().jobId, location: resultFields.location };
42
+ const validJob = (job) => Boolean(typeof job?.jobReference?.jobId === "string" && job.jobReference.jobId.length > 0 && typeof job.jobReference.projectId === "string" &&
43
+ ["PENDING", "RUNNING", "DONE"].includes(job.status?.state));
44
+ const validPage = (result, field, reference) => Boolean(result && typeof result === "object" && !Array.isArray(result) && !result.error &&
45
+ (result[field] === undefined || Array.isArray(result[field]) && result[field].every((item) => typeof item?.[reference]?.projectId === "string")) &&
46
+ (result.nextPageToken === undefined || typeof result.nextPageToken === "string"));
47
+
48
+ function projectOperation(fields, destination, validateResult, scopes = metadataScopes, method = "GET") {
49
+ const schema = createSchema(fields);
50
+ return { scopes, validateResult,
51
+ request(input, settings) {
52
+ const values = validateSchemaPayload({ schema, mode: "replace" }, input, { statusCode: 422 });
53
+ const { path, query = {} } = destination(values);
54
+ const url = new URL(`${origin}/bigquery/v2/projects/${encodeURIComponent(settings.projectId)}${path}`);
55
+ for (const [name, value] of Object.entries(query)) if (value !== undefined) url.searchParams.set(name, String(value));
56
+ return { method, url: url.href };
57
+ }
58
+ };
59
+ }
60
+ const bigqueryProvider = googleProvider(bigqueryDefinition, origin, "projects.list", {
61
+ "datasets.list": projectOperation(metadataPage, (query) => ({ path: "/datasets", query }),
62
+ (result) => validPage(result, "datasets", "datasetReference")),
63
+ "tables.list": projectOperation({ datasetId: resourceId, ...metadataPage }, ({ datasetId, ...query }) => ({ path: `/datasets/${encodeURIComponent(datasetId)}/tables`, query }),
64
+ (result) => validPage(result, "tables", "tableReference")),
65
+ "tables.get": projectOperation({ datasetId: resourceId, tableId: resourceId }, ({ datasetId, tableId }) => ({ path: `/datasets/${encodeURIComponent(datasetId)}/tables/${encodeURIComponent(tableId)}` }),
66
+ (result) => Boolean(result?.tableReference?.tableId && result.tableReference.datasetId && typeof result.tableReference.projectId === "string" &&
67
+ (result.schema === undefined || Array.isArray(result.schema.fields)))),
68
+ "jobs.get": projectOperation(jobFields, ({ jobId, ...query }) => ({ path: `/jobs/${encodeURIComponent(jobId)}`, query }), validJob),
69
+ "jobs.cancel": projectOperation(jobFields, ({ jobId, ...query }) => ({ path: `/jobs/${encodeURIComponent(jobId)}/cancel`, query }),
70
+ (result) => validJob(result?.job), queryScopes, "POST"),
71
+ "jobs.query": { ...submitQuery, scopes: queryScopes,
72
+ request(input, settings) {
73
+ const request = submitQuery.request(input, settings);
74
+ const { parameters, ...body } = request.body;
75
+ return { ...request, body: { ...body, useLegacySql: false,
76
+ ...(parameters?.length ? { parameterMode: "NAMED", queryParameters: parameters.map(({ name, type, value }) => ({
77
+ name, parameterType: { type }, parameterValue: { value }
78
+ })) } : {})
79
+ } };
80
+ }
81
+ },
82
+ "jobs.getQueryResults": { scopes: queryScopes, validateResult: validQueryResult,
83
+ request(input, settings) {
84
+ const { jobId, ...query } = validateSchemaPayload({ schema: resultsSchema, mode: "replace" }, input, { statusCode: 422 });
85
+ const url = new URL(`${origin}/bigquery/v2/projects/${encodeURIComponent(settings.projectId)}/queries/${encodeURIComponent(jobId)}`);
86
+ for (const [key, value] of Object.entries(query)) if (value !== undefined) url.searchParams.set(key, String(value));
87
+ return { method: "GET", url: url.href };
88
+ }
89
+ },
90
+ "projects.list": googleRead(["bigquery.readonly", "bigquery", "cloud-platform.read-only", "cloud-platform"], {
91
+ maxResults: { type: "integer", min: 1, max: 50, defaultTo: 50 }, pageToken
92
+ }, (query) => ({ url: `${origin}/bigquery/v2/projects`, query }),
93
+ (result) => Boolean(result && typeof result === "object" && !Array.isArray(result) && !result.error
94
+ && Number.isInteger(result.totalItems) && result.totalItems >= 0
95
+ && (Array.isArray(result.projects) || (result.totalItems === 0 && result.projects === undefined))))
96
+ });
97
+ export { bigqueryProvider };
@@ -0,0 +1,97 @@
1
+ import { ConnectorError } from "@jskit-ai/connectors-core/server";
2
+ import { createSchema } from "json-rest-schema";
3
+ import { validateSchemaPayload } from "@jskit-ai/kernel/shared/validators";
4
+ import { brevoDefinition } from "../shared/tokens.js";
5
+ import { jsonOperation } from "./jsonOperation.js";
6
+
7
+ const text = { type: "string", minLength: 1, maxLength: 1000 };
8
+ const email = { ...text, maxLength: 320, pattern: "^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$" };
9
+ const id = { type: "integer", min: 1, required: true };
10
+ const bag = { type: "object", additionalProperties: true };
11
+ const bool = { type: "boolean" };
12
+ const page = { limit: { type: "integer", min: 1, max: 50, defaultTo: 50 }, offset: { type: "integer", min: 0, defaultTo: 0 } };
13
+ const mailbox = { type: "object", schema: createSchema({ email: { ...email, required: true }, name: text }) };
14
+ const ids = { type: "array", items: { type: "integer", min: 1 }, validator: value => value.length <= 100 || "Use at most 100 list IDs." };
15
+ const emails = { type: "array", items: email, validator: value => value.length > 0 && value.length <= 150 || "Supply 1–150 email addresses." };
16
+ const domain = { type: "string", minLength: 3, maxLength: 253, pattern: "^(?:[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?\\.)+[A-Za-z]{2,}$", required: true };
17
+ const empty = result => result === null || result !== null && typeof result === "object" && !Array.isArray(result) && Object.keys(result).length === 0;
18
+ const record = result => Number.isInteger(result?.id);
19
+ function operation(method, endpoint, fields, validateResult, prepare = value => value) {
20
+ const schema = createSchema(fields);
21
+ return { scopes: [], request(input) {
22
+ const values = prepare(validateSchemaPayload({ schema, mode: "replace" }, input, { statusCode: 422 }));
23
+ const { resource, ...body } = values;
24
+ const url = new URL(`https://api.brevo.com/v3/${endpoint.replace("{resource}", encodeURIComponent(resource ?? ""))}`);
25
+ if (method === "GET") { for (const [key, value] of Object.entries(body)) url.searchParams.set(key, String(value)); return { method, url: url.href }; }
26
+ return { method, url: url.href, ...(Object.keys(body).length ? { body } : {}) };
27
+ }, validateResult };
28
+ }
29
+ function changed(value) {
30
+ if (Object.keys(value).every(key => key === "resource")) throw new ConnectorError("connector_input_invalid", "Supply at least one change.", { statusCode: 422 });
31
+ return value;
32
+ }
33
+ const content = value => {
34
+ if (value.templateId === undefined && !(value.subject && (value.htmlContent || value.textContent))) {
35
+ throw new ConnectorError("connector_input_invalid", "Choose a template or provide a subject and email content.", { statusCode: 422 });
36
+ }
37
+ return value;
38
+ };
39
+ const contact = { attributes: bag, emailBlacklisted: bool, smsBlacklisted: bool, listIds: ids };
40
+ const brevoProvider = Object.freeze({
41
+ ...brevoDefinition, apiOrigins: ["https://api.brevo.com"],
42
+ apiKey: { headers: (key) => ({ "api-key": key }) },
43
+ checkOperation: "contacts.list",
44
+ operations: {
45
+ "email.send": operation("POST", "smtp/email", { sender: { ...mailbox, required: true },
46
+ to: { type: "array", required: true, items: mailbox, validator: value => value.length > 0 && value.length <= 50 || "Supply 1–50 recipients." },
47
+ subject: text, htmlContent: { ...text, maxLength: 1000000 }, textContent: { ...text, maxLength: 1000000 },
48
+ templateId: { ...id, required: false }, params: bag, replyTo: mailbox
49
+ }, result => typeof result?.messageId === "string", content),
50
+ "sms.send": operation("POST", "transactionalSMS/sms", { sender: { ...text, required: true, maxLength: 15,
51
+ validator: value => /^[A-Za-z0-9]{1,11}$/.test(value) || /^[0-9]{12,15}$/.test(value) || "Use 1–11 letters/digits or 12–15 digits for the SMS sender." },
52
+ recipient: { type: "string", required: true, pattern: "^\\+?[0-9]{6,15}$" }, type: { type: "string", required: true, enum: ["transactional", "marketing"] },
53
+ content: { ...text, maxLength: 10000 }, templateId: { ...id, required: false }, params: bag, unicodeEnabled: bool, organisationPrefix: text, tag: text
54
+ }, result => Number.isInteger(result?.messageId) && typeof result.reference === "string", value => {
55
+ if ((value.content === undefined) === (value.templateId === undefined)) throw new ConnectorError("connector_input_invalid", "Choose SMS content or a template, not both.", { statusCode: 422 }); return value;
56
+ }),
57
+ "contacts.get": operation("GET", "contacts/{resource}", { resource: { ...email, required: true } }, record),
58
+ "contacts.create": operation("POST", "contacts", { email: { ...email, required: true }, ...contact,
59
+ updateEnabled: { ...bool, defaultTo: false } }, result => empty(result) || record(result)),
60
+ "contacts.update": operation("PUT", "contacts/{resource}", { resource: { ...email, required: true }, ...contact, unlinkListIds: ids }, empty, changed),
61
+ "folders.list": operation("GET", "contacts/folders", page, result => Array.isArray(result?.folders)),
62
+ "lists.list": operation("GET", "contacts/lists", page, result => Array.isArray(result?.lists)),
63
+ "lists.create": operation("POST", "contacts/lists", { name: { ...text, required: true }, folderId: id }, record),
64
+ "lists.addContacts": operation("POST", "contacts/lists/{resource}/contacts/add", { resource: id, emails: { ...emails, required: true } }, result => result?.contacts !== null && typeof result?.contacts === "object"),
65
+ "lists.removeContacts": operation("POST", "contacts/lists/{resource}/contacts/remove", { resource: id, emails: { ...emails, required: true } }, result => result?.contacts !== null && typeof result?.contacts === "object"),
66
+ "senders.list": operation("GET", "senders", {}, result => Array.isArray(result?.senders)),
67
+ "senders.create": operation("POST", "senders", { email: { ...email, required: true }, name: { ...text, required: true } }, record),
68
+ "domains.create": operation("POST", "senders/domains", { name: domain }, result => typeof result?.domain_name === "string" && typeof result.message === "string"),
69
+ "domains.get": operation("GET", "senders/domains/{resource}", { resource: domain }, result => typeof result?.domain === "string" && typeof result.verified === "boolean" && typeof result.authenticated === "boolean" && result.dns_records !== null && typeof result.dns_records === "object"),
70
+ "domains.authenticate": operation("PUT", "senders/domains/{resource}/authenticate", { resource: domain }, result => typeof result?.domain_name === "string" && typeof result.message === "string"),
71
+ "campaigns.list": operation("GET", "emailCampaigns", page, result => Array.isArray(result?.campaigns)),
72
+ "campaigns.get": operation("GET", "emailCampaigns/{resource}", { resource: id }, record),
73
+ "campaigns.create": operation("POST", "emailCampaigns", { name: { ...text, required: true }, sender: { ...mailbox, required: true },
74
+ subject: text, htmlContent: { ...text, maxLength: 1000000 }, templateId: { ...id, required: false }, params: bag,
75
+ recipients: { type: "object", required: true, schema: createSchema({ listIds: { ...ids, required: true,
76
+ validator: value => value.length > 0 && value.length <= 100 || "Choose 1–100 recipient lists." }, exclusionListIds: ids }) },
77
+ replyTo: email, previewText: text, tag: text
78
+ }, record, content),
79
+ "campaigns.sendTest": operation("POST", "emailCampaigns/{resource}/sendTest", { resource: id, emailTo: { ...emails, required: true } }, empty),
80
+ "campaigns.sendNow": operation("POST", "emailCampaigns/{resource}/sendNow", { resource: id }, empty),
81
+ "email.events": operation("GET", "smtp/statistics/events", { ...page, messageId: text, email, days: { type: "integer", min: 1, max: 90 },
82
+ event: { type: "string", enum: ["blocked", "bounces", "deferred", "delivered", "hardBounces", "invalid", "opened", "requests", "softBounces", "spam", "unsubscribed", "clicks", "error", "loadedByProxy"] }
83
+ }, result => Array.isArray(result?.events)),
84
+ "sms.events": operation("GET", "transactionalSMS/statistics/events", { ...page, phoneNumber: text, days: { type: "integer", min: 1, max: 90 } }, result => Array.isArray(result?.events)),
85
+ "events.create": operation("POST", "events", { event_name: { type: "string", required: true, maxLength: 255, pattern: "^[A-Za-z0-9_-]+$" },
86
+ identifiers: { type: "object", required: true, schema: createSchema({ email_id: { ...email, required: true } }) },
87
+ event_properties: { ...bag, validator: value => Buffer.byteLength(JSON.stringify(value)) <= 50000 || "Event properties exceed 50KB." }, contact_properties: bag,
88
+ event_date: { ...text, validator: value => Number.isFinite(Date.parse(value)) && /^\d{4}-\d{2}-\d{2}T/.test(value) || "Use an ISO timestamp." }
89
+ }, empty),
90
+ "contacts.list": jsonOperation("https://api.brevo.com/v3/contacts", {
91
+ limit: { type: "integer", min: 1, max: 1000, defaultTo: 50 },
92
+ offset: { type: "integer", min: 0, defaultTo: 0 },
93
+ sort: { type: "string", enum: ["asc", "desc"], defaultTo: "desc" }
94
+ }, (result) => Array.isArray(result?.contacts) && Number.isInteger(result.count))
95
+ }
96
+ });
97
+ export { brevoProvider };