@zackbart/connecta 0.34.1 → 0.35.0

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 (429) hide show
  1. package/CHANGELOG.md +363 -0
  2. package/README.md +62 -106
  3. package/SECURITY.md +3 -2
  4. package/bin/connecta.mjs +41 -16
  5. package/bin/version.mjs +1 -1
  6. package/dist/access-tokens.d.ts +2 -1
  7. package/dist/access-tokens.js +66 -30
  8. package/dist/activity.d.ts +1 -1
  9. package/dist/activity.js +2 -0
  10. package/dist/agent-config.d.ts +23 -0
  11. package/dist/agent-config.js +59 -0
  12. package/dist/agent-description.js +16 -0
  13. package/dist/agent-identity.js +4 -4
  14. package/dist/agent-observation.js +39 -0
  15. package/dist/auth/clerk-sdk/client.d.ts +2 -0
  16. package/dist/auth/clerk-sdk/generated.js +554 -563
  17. package/dist/auth/clerk-transport.js +70 -3
  18. package/dist/auth/clerk.d.ts +13 -0
  19. package/dist/auth/clerk.js +653 -246
  20. package/dist/auth/cloudflare-access.js +46 -42
  21. package/dist/auth/downstream-client-metadata.js +2 -2
  22. package/dist/auth/downstream-oauth.js +2051 -1160
  23. package/dist/auth/downstream-revocation.js +88 -88
  24. package/dist/auth/native-call.js +20 -0
  25. package/dist/auth/native.js +184 -0
  26. package/dist/auth/oauth-start-reset.js +11 -5
  27. package/dist/auth/oauth-token-response.js +121 -102
  28. package/dist/auth/oauth-v2-migration.js +97 -78
  29. package/dist/auth/renewable-grant.js +10 -0
  30. package/dist/auth/static-oauth.js +663 -271
  31. package/dist/auth/vercel-agent.js +266 -0
  32. package/dist/auth-admission.js +204 -0
  33. package/dist/auth-elicitation.js +350 -241
  34. package/dist/authorization-handoff.js +164 -90
  35. package/dist/call-admission-context.js +3 -10
  36. package/dist/call-admission.js +2 -0
  37. package/dist/call-classification.js +9 -0
  38. package/dist/call-recovery.js +32 -13
  39. package/dist/canonical-json.js +8 -0
  40. package/dist/catalog-cache.js +369 -213
  41. package/dist/catalog-service.js +659 -530
  42. package/dist/catalog.js +5 -1
  43. package/dist/concurrency.js +0 -25
  44. package/dist/config-defaults.d.ts +1 -0
  45. package/dist/config-defaults.js +1 -0
  46. package/dist/config-schema.d.ts +3 -1
  47. package/dist/config-schema.js +13 -3
  48. package/dist/config.d.ts +40 -6
  49. package/dist/config.js +40 -26
  50. package/dist/connector-caller.d.ts +4 -0
  51. package/dist/connector-caller.js +17 -4
  52. package/dist/connectors/api-connector.d.ts +12 -7
  53. package/dist/connectors/api-connector.js +142 -56
  54. package/dist/connectors/guarded-fetch.d.ts +51 -17
  55. package/dist/connectors/guarded-fetch.js +215 -121
  56. package/dist/connectors/negotiation-cache.js +20 -10
  57. package/dist/connectors/remote-mcp.d.ts +10 -30
  58. package/dist/connectors/remote-mcp.js +955 -563
  59. package/dist/d1.js +10 -243
  60. package/dist/derived-context.js +5 -0
  61. package/dist/describe-config.d.ts +3 -1
  62. package/dist/describe-config.js +2 -0
  63. package/dist/downstream-input.js +347 -260
  64. package/dist/errors.d.ts +87 -6
  65. package/dist/errors.js +114 -4
  66. package/dist/execute.js +87 -50
  67. package/dist/executor-admission.js +3 -0
  68. package/dist/executors/quickjs.js +163 -112
  69. package/dist/guarded-transport.js +50 -0
  70. package/dist/guest-runtime.js +1 -1
  71. package/dist/http/client.js +439 -0
  72. package/dist/http/errors.js +558 -0
  73. package/dist/http/fields.js +49 -0
  74. package/dist/http/hatch-paging.js +318 -0
  75. package/dist/http/hatch.js +1279 -0
  76. package/dist/http/json-schema.js +267 -0
  77. package/dist/http/node-transport.js +241 -0
  78. package/dist/http/oauth.js +162 -0
  79. package/dist/http/paging.js +228 -0
  80. package/dist/http/provider.js +221 -0
  81. package/dist/http/safety.js +82 -0
  82. package/dist/http/services.js +79 -0
  83. package/dist/http/tools.js +383 -0
  84. package/dist/http/vendor-index.js +138 -0
  85. package/dist/http/vendor.js +448 -0
  86. package/dist/http/write-request.js +66 -0
  87. package/dist/http-policy.d.ts +11 -0
  88. package/dist/http-policy.js +45 -0
  89. package/dist/inbound-credential.js +1 -1
  90. package/dist/index.d.ts +2 -2
  91. package/dist/index.js +4 -0
  92. package/dist/invocation-auth.js +30 -19
  93. package/dist/invocation.js +218 -85
  94. package/dist/mcp-result.js +0 -4
  95. package/dist/membership.d.ts +4 -0
  96. package/dist/meta-output.js +1 -1
  97. package/dist/meta-tools.js +531 -406
  98. package/dist/native-recovery.js +52 -0
  99. package/dist/native-tools.js +382 -0
  100. package/dist/node.d.ts +4 -3
  101. package/dist/node.js +45 -8
  102. package/dist/oauth-handoff.js +177 -104
  103. package/dist/oauth-partition.js +6 -20
  104. package/dist/oauth-sealing.js +6 -7
  105. package/dist/operation-vocabulary.js +101 -0
  106. package/dist/operator-record.d.ts +2 -2
  107. package/dist/operator-record.js +17 -4
  108. package/dist/operator-ui/agents.d.ts +5 -4
  109. package/dist/operator-ui/agents.js +17 -7
  110. package/dist/operator-ui/config-summary.js +5 -4
  111. package/dist/operator-ui/connection-map.js +7 -5
  112. package/dist/operator-ui/contract.d.ts +7 -3
  113. package/dist/operator-ui/endpoints.js +4 -13
  114. package/dist/operator-ui/fix-prompts.d.ts +1 -1
  115. package/dist/operator-ui/fix-prompts.js +0 -8
  116. package/dist/operator-ui/generated.js +3 -3
  117. package/dist/operator-ui/model.d.ts +2 -3
  118. package/dist/operator-ui/overview.js +5 -2
  119. package/dist/operator-ui/setup-commands.js +0 -4
  120. package/dist/operator-ui/view.d.ts +2 -25
  121. package/dist/operator-ui/view.js +2 -76
  122. package/dist/program-writes.js +47 -26
  123. package/dist/provider-call-facts.d.ts +115 -0
  124. package/dist/provider-call-facts.js +182 -0
  125. package/dist/provider-call.d.ts +112 -0
  126. package/dist/provider-call.js +271 -0
  127. package/dist/provider.d.ts +65 -31
  128. package/dist/provider.js +116 -39
  129. package/dist/providers/_shared/google/hatch.js +91 -0
  130. package/dist/providers/_shared/google/kit.js +537 -0
  131. package/dist/providers/_shared/google/result-size.js +15 -2
  132. package/dist/providers/_shared/google/service-account.d.ts +2 -1
  133. package/dist/providers/_shared/google/service-account.js +8 -4
  134. package/dist/providers/_shared/google/workspace.d.ts +6 -210
  135. package/dist/providers/_shared/google/workspace.js +6 -425
  136. package/dist/providers/_shared/rest/compact-index.js +86 -0
  137. package/dist/providers/_shared/rest/dispatch.js +3 -23
  138. package/dist/providers/_shared/rest/kit.js +546 -0
  139. package/dist/providers/_shared/rest/operation-index.js +230 -14
  140. package/dist/providers/_shared/rest/response-media.js +11 -0
  141. package/dist/providers/_shared/rest/tools.js +101 -78
  142. package/dist/providers/_shared/rest/value-safety.js +353 -195
  143. package/dist/providers/_shared/sensitive-fields.js +27 -0
  144. package/dist/providers/breeze/api.js +584 -0
  145. package/dist/providers/breeze/index.d.ts +1 -3
  146. package/dist/providers/breeze/index.js +67 -1420
  147. package/dist/providers/breeze/openapi.generated.js +60 -0
  148. package/dist/providers/breeze/skill.generated.js +2 -6
  149. package/dist/providers/breeze/tools.js +696 -0
  150. package/dist/providers/ccb/api.js +496 -0
  151. package/dist/providers/ccb/index.d.ts +15 -37
  152. package/dist/providers/ccb/index.js +77 -1702
  153. package/dist/providers/ccb/lib/constants.d.ts +34 -0
  154. package/dist/providers/ccb/lib/constants.js +74 -0
  155. package/dist/providers/ccb/lib/guide.js +9 -0
  156. package/dist/providers/ccb/lib/verdicts.js +387 -0
  157. package/dist/providers/ccb/openapi.generated.js +397 -0
  158. package/dist/providers/ccb/safety.js +295 -0
  159. package/dist/providers/ccb/skill.generated.js +2 -6
  160. package/dist/providers/ccb/tools.js +853 -0
  161. package/dist/providers/cloudflare/api.js +1066 -0
  162. package/dist/providers/cloudflare/index.d.ts +5 -22
  163. package/dist/providers/cloudflare/index.js +79 -197
  164. package/dist/providers/cloudflare/lib/config.d.ts +16 -0
  165. package/dist/providers/cloudflare/lib/config.js +2 -0
  166. package/dist/providers/cloudflare/lib/discovery.js +29 -0
  167. package/dist/providers/cloudflare/{graphql.js → lib/graphql.js} +1 -1
  168. package/dist/providers/cloudflare/lib/hatch.js +103 -0
  169. package/dist/providers/cloudflare/lib/path-bounds.js +28 -0
  170. package/dist/providers/cloudflare/lib/response-media.js +75 -0
  171. package/dist/providers/cloudflare/openapi.generated.js +4 -3
  172. package/dist/providers/cloudflare/{value-safety.js → safety.js} +487 -0
  173. package/dist/providers/cloudflare/skill.generated.js +2 -10
  174. package/dist/providers/cloudflare/tools.js +360 -0
  175. package/dist/providers/docs/api.js +51 -0
  176. package/dist/providers/docs/index.d.ts +1 -12
  177. package/dist/providers/docs/index.js +53 -1165
  178. package/dist/providers/docs/lib/constants.d.ts +18 -0
  179. package/dist/providers/docs/lib/constants.js +18 -0
  180. package/dist/providers/docs/lib/discovery.js +26 -0
  181. package/dist/providers/docs/lib/requests.js +48 -0
  182. package/dist/providers/docs/lib/schemas.js +168 -0
  183. package/dist/providers/docs/openapi.generated.js +16 -0
  184. package/dist/providers/docs/safety.js +54 -0
  185. package/dist/providers/docs/skill.generated.js +2 -6
  186. package/dist/providers/docs/tools.js +802 -0
  187. package/dist/providers/drive/api.js +177 -0
  188. package/dist/providers/drive/index.d.ts +1 -12
  189. package/dist/providers/drive/index.js +38 -1448
  190. package/dist/providers/drive/lib/constants.d.ts +6 -0
  191. package/dist/providers/drive/lib/constants.js +6 -0
  192. package/dist/providers/drive/lib/contracts.js +532 -0
  193. package/dist/providers/drive/lib/hatch.js +197 -0
  194. package/dist/providers/drive/openapi.generated.js +77 -0
  195. package/dist/providers/drive/safety.js +90 -0
  196. package/dist/providers/drive/skill.generated.js +2 -6
  197. package/dist/providers/drive/tools.js +1290 -0
  198. package/dist/providers/forms/api.js +71 -0
  199. package/dist/providers/forms/index.d.ts +1 -12
  200. package/dist/providers/forms/index.js +49 -1145
  201. package/dist/providers/forms/lib/constants.d.ts +14 -0
  202. package/dist/providers/forms/lib/constants.js +19 -0
  203. package/dist/providers/forms/lib/hatch.js +72 -0
  204. package/dist/providers/forms/openapi.generated.js +23 -0
  205. package/dist/providers/forms/safety.js +34 -0
  206. package/dist/providers/forms/skill.generated.js +2 -8
  207. package/dist/providers/forms/tools.js +1024 -0
  208. package/dist/providers/github/api.js +150 -0
  209. package/dist/providers/github/index.d.ts +4 -5
  210. package/dist/providers/github/index.js +30 -87
  211. package/dist/providers/github/{auth.d.ts → lib/auth.d.ts} +5 -3
  212. package/dist/providers/github/{auth.js → lib/auth.js} +40 -43
  213. package/dist/providers/github/lib/discovery.js +132 -0
  214. package/dist/providers/github/lib/operations.js +58 -0
  215. package/dist/providers/github/{scope.d.ts → lib/scope.d.ts} +2 -0
  216. package/dist/providers/github/{scope.js → lib/scope.js} +25 -1
  217. package/dist/providers/github/openapi.generated.js +1216 -0
  218. package/dist/providers/github/safety.js +216 -0
  219. package/dist/providers/github/skill.generated.js +2 -5
  220. package/dist/providers/github/tools.js +506 -0
  221. package/dist/providers/gmail/api.js +114 -0
  222. package/dist/providers/gmail/index.d.ts +1 -12
  223. package/dist/providers/gmail/index.js +73 -1621
  224. package/dist/providers/gmail/lib/constants.d.ts +13 -0
  225. package/dist/providers/gmail/lib/constants.js +16 -0
  226. package/dist/providers/gmail/lib/index.js +173 -0
  227. package/dist/providers/gmail/lib/schemas.js +176 -0
  228. package/dist/providers/gmail/lib/threading.js +120 -0
  229. package/dist/providers/gmail/openapi.generated.js +92 -0
  230. package/dist/providers/gmail/safety.js +21 -0
  231. package/dist/providers/gmail/skill.generated.js +2 -6
  232. package/dist/providers/gmail/tools.js +1435 -0
  233. package/dist/providers/overflow/api.js +1008 -0
  234. package/dist/providers/overflow/index.d.ts +1 -17
  235. package/dist/providers/overflow/index.js +60 -1488
  236. package/dist/providers/overflow/skill.generated.js +2 -5
  237. package/dist/providers/overflow/tools.js +873 -0
  238. package/dist/providers/planning-center/api.js +467 -0
  239. package/dist/providers/planning-center/index.d.ts +4 -35
  240. package/dist/providers/planning-center/index.js +69 -2445
  241. package/dist/providers/planning-center/lib/constants.d.ts +58 -0
  242. package/dist/providers/planning-center/lib/constants.js +83 -0
  243. package/dist/providers/planning-center/lib/discovery.js +40 -0
  244. package/dist/providers/planning-center/lib/guide.js +39 -0
  245. package/dist/providers/planning-center/lib/json-api.js +83 -0
  246. package/dist/providers/planning-center/lib/verdicts.js +1758 -0
  247. package/dist/providers/planning-center/openapi.generated.js +1764 -0
  248. package/dist/providers/planning-center/safety.js +188 -0
  249. package/dist/providers/planning-center/skill.generated.js +2 -27
  250. package/dist/providers/planning-center/tools.js +1010 -0
  251. package/dist/providers/sheets/api.js +133 -0
  252. package/dist/providers/sheets/index.d.ts +4 -16
  253. package/dist/providers/sheets/index.js +61 -1172
  254. package/dist/providers/sheets/lib/constants.d.ts +14 -0
  255. package/dist/providers/sheets/lib/constants.js +14 -0
  256. package/dist/providers/sheets/lib/hatch.js +163 -0
  257. package/dist/providers/sheets/lib/requests.js +105 -0
  258. package/dist/providers/sheets/lib/schemas.js +138 -0
  259. package/dist/providers/sheets/openapi.generated.js +30 -0
  260. package/dist/providers/sheets/safety.js +27 -0
  261. package/dist/providers/sheets/skill.generated.js +2 -7
  262. package/dist/providers/sheets/tools.js +734 -0
  263. package/dist/providers/slides/api.js +147 -0
  264. package/dist/providers/slides/index.d.ts +2 -10
  265. package/dist/providers/slides/index.js +48 -2555
  266. package/dist/providers/slides/lib/constants.d.ts +11 -0
  267. package/dist/providers/slides/lib/constants.js +11 -0
  268. package/dist/providers/slides/lib/requests.js +60 -0
  269. package/dist/providers/slides/lib/schemas.js +565 -0
  270. package/dist/providers/slides/openapi.generated.js +18 -0
  271. package/dist/providers/slides/skill.generated.js +2 -6
  272. package/dist/providers/slides/tools.js +1968 -0
  273. package/dist/providers/stripe/api.js +401 -0
  274. package/dist/providers/stripe/index.d.ts +5 -27
  275. package/dist/providers/stripe/index.js +74 -210
  276. package/dist/providers/stripe/lib/config.d.ts +4 -0
  277. package/dist/providers/stripe/lib/config.js +2 -0
  278. package/dist/providers/stripe/openapi.generated.js +4 -3
  279. package/dist/providers/stripe/{value-safety.js → safety.js} +5 -5
  280. package/dist/providers/stripe/skill.generated.js +2 -11
  281. package/dist/providers/stripe/tools.js +77 -0
  282. package/dist/providers/tithely/api.js +531 -0
  283. package/dist/providers/tithely/index.d.ts +17 -0
  284. package/dist/providers/tithely/index.js +86 -965
  285. package/dist/providers/tithely/skill.generated.js +2 -5
  286. package/dist/providers/tithely/tools.js +426 -0
  287. package/dist/providers/vercel/api.js +551 -0
  288. package/dist/providers/vercel/index.d.ts +9 -25
  289. package/dist/providers/vercel/index.js +66 -260
  290. package/dist/providers/vercel/lib/config.d.ts +2 -0
  291. package/dist/providers/vercel/lib/config.js +2 -0
  292. package/dist/providers/vercel/lib/index.js +117 -0
  293. package/dist/providers/vercel/lib/logs.js +100 -0
  294. package/dist/providers/vercel/lib/pin.js +270 -0
  295. package/dist/providers/vercel/openapi.generated.js +10 -5
  296. package/dist/providers/vercel/{value-safety.js → safety.js} +247 -38
  297. package/dist/providers/vercel/skill.generated.js +2 -7
  298. package/dist/providers/vercel/tools.js +642 -0
  299. package/dist/redaction-identity.js +47 -0
  300. package/dist/redaction-marker.js +125 -0
  301. package/dist/registry.d.ts +21 -15
  302. package/dist/registry.js +409 -376
  303. package/dist/routes/access-tokens.js +1 -1
  304. package/dist/routes/actor-labels.js +1 -1
  305. package/dist/routes/connect.js +1 -1
  306. package/dist/routes/dispatch.js +103 -0
  307. package/dist/routes/mcp.js +170 -79
  308. package/dist/routes/oauth.js +3 -1
  309. package/dist/routes/operator.js +2 -2
  310. package/dist/routes/shared.d.ts +5 -4
  311. package/dist/routes/shared.js +244 -177
  312. package/dist/routes/skills.js +10 -8
  313. package/dist/routes/ui-config.js +27 -3
  314. package/dist/routes/ui-data.js +21 -20
  315. package/dist/routes/ui.js +1 -1
  316. package/dist/runtime/call-admission.js +0 -26
  317. package/dist/runtime/call-entry.js +30 -0
  318. package/dist/runtime/call.js +556 -0
  319. package/dist/runtime/connector-scope.js +7 -0
  320. package/dist/runtime/credential-probe.js +34 -0
  321. package/dist/runtime/executor.js +40 -0
  322. package/dist/runtime/native-storage.js +63 -0
  323. package/dist/runtime/registry.js +27 -0
  324. package/dist/runtime/request-scope.js +72 -0
  325. package/dist/runtime/run.js +15 -7
  326. package/dist/runtime/shared-read.js +34 -5
  327. package/dist/runtime/storage.js +34 -21
  328. package/dist/sent-secrets.d.ts +194 -37
  329. package/dist/sent-secrets.js +1743 -340
  330. package/dist/server.js +5 -56
  331. package/dist/skills.js +322 -222
  332. package/dist/sqlite.js +15 -14
  333. package/dist/storage/d1-copy.js +255 -0
  334. package/dist/storage/d1-driver.js +15 -0
  335. package/dist/storage/keys.js +28 -0
  336. package/dist/storage/sql-driver.js +88 -0
  337. package/dist/storage/sql-native.js +544 -0
  338. package/dist/storage/sql-schema.d.ts +6 -0
  339. package/dist/storage/sql-schema.js +15 -0
  340. package/dist/storage/sql.d.ts +14 -20
  341. package/dist/storage/sql.js +29 -535
  342. package/dist/timeout.js +0 -26
  343. package/dist/tool-safety.js +20 -10
  344. package/dist/types.d.ts +7 -109
  345. package/dist/upward.js +3 -0
  346. package/dist/usage-guide.js +3 -1
  347. package/dist/validate.d.ts +4 -2
  348. package/dist/validate.js +86 -9
  349. package/dist/version.d.ts +1 -1
  350. package/dist/version.js +1 -1
  351. package/dist/worker.js +312 -292
  352. package/dist/workspace-subject.js +5 -1
  353. package/dist/write-outcome.js +454 -34
  354. package/documentation/README.md +27 -7
  355. package/documentation/architecture.md +2547 -226
  356. package/documentation/auth.md +821 -98
  357. package/documentation/code-mode.md +49 -16
  358. package/documentation/deploying.md +164 -20
  359. package/documentation/hosted-mcp-migration.md +460 -0
  360. package/documentation/integrating.md +129 -144
  361. package/documentation/meta-tools.md +409 -32
  362. package/documentation/operating.md +27 -3
  363. package/documentation/operator-tests.md +3 -0
  364. package/documentation/operator-ui.md +68 -45
  365. package/documentation/provider-auth-migration.md +16 -63
  366. package/documentation/provider-migration-0.29.md +10 -5
  367. package/documentation/provider-migration-0.35.md +975 -0
  368. package/documentation/providers.md +965 -0
  369. package/examples/worker/AGENTS.md +23 -9
  370. package/examples/worker/README.md +254 -135
  371. package/examples/worker/alchemy.run.ts +116 -0
  372. package/examples/worker/gitignore +10 -0
  373. package/examples/worker/src/connecta.config.ts +23 -21
  374. package/examples/worker/wrangler.jsonc +14 -5
  375. package/guides/connecting-agents.md +157 -0
  376. package/guides/how-it-works.md +156 -0
  377. package/guides/safety-and-trust.md +153 -0
  378. package/package.json +27 -46
  379. package/templates/node/.env.example +3 -0
  380. package/templates/node/README.md +22 -2
  381. package/templates/node/docker-compose.yml +1 -0
  382. package/templates/node/package.json +3 -2
  383. package/templates/node/src/connecta.config.ts +8 -0
  384. package/dist/catalog-drift.d.ts +0 -138
  385. package/dist/catalog-drift.js +0 -578
  386. package/dist/providers/_shared/request-token.js +0 -83
  387. package/dist/providers/_shared/rest/operation-index.d.ts +0 -133
  388. package/dist/providers/_shared/rest/tools.d.ts +0 -141
  389. package/dist/providers/_shared/rest/value-safety.d.ts +0 -201
  390. package/dist/providers/basecamp/index.d.ts +0 -45
  391. package/dist/providers/basecamp/index.js +0 -997
  392. package/dist/providers/basecamp/skill.generated.js +0 -9
  393. package/dist/providers/cloudflare/rest.d.ts +0 -37
  394. package/dist/providers/cloudflare/rest.js +0 -1097
  395. package/dist/providers/github/hosted.js +0 -218
  396. package/dist/providers/github/rest.js +0 -321
  397. package/dist/providers/infisical/index.d.ts +0 -7
  398. package/dist/providers/infisical/index.js +0 -713
  399. package/dist/providers/infisical/skill.generated.js +0 -8
  400. package/dist/providers/intercom/index.d.ts +0 -24
  401. package/dist/providers/intercom/index.js +0 -121
  402. package/dist/providers/intercom/skill.generated.js +0 -8
  403. package/dist/providers/linear/index.d.ts +0 -55
  404. package/dist/providers/linear/index.js +0 -537
  405. package/dist/providers/linear/skill.generated.js +0 -8
  406. package/dist/providers/mixpanel/index.d.ts +0 -59
  407. package/dist/providers/mixpanel/index.js +0 -472
  408. package/dist/providers/mixpanel/operations.js +0 -20
  409. package/dist/providers/mixpanel/rest.js +0 -603
  410. package/dist/providers/mixpanel/skill.generated.js +0 -10
  411. package/dist/providers/mixpanel/value-safety.js +0 -16
  412. package/dist/providers/notion/index.d.ts +0 -67
  413. package/dist/providers/notion/index.js +0 -368
  414. package/dist/providers/notion/openapi.generated.js +0 -75
  415. package/dist/providers/notion/rest.d.ts +0 -24
  416. package/dist/providers/notion/rest.js +0 -1226
  417. package/dist/providers/notion/skill.generated.js +0 -11
  418. package/dist/providers/notion/value-safety.js +0 -56
  419. package/dist/providers/revenuecat/index.d.ts +0 -51
  420. package/dist/providers/revenuecat/index.js +0 -697
  421. package/dist/providers/revenuecat/skill.generated.js +0 -11
  422. package/dist/providers/stripe/rest.d.ts +0 -14
  423. package/dist/providers/stripe/rest.js +0 -402
  424. package/dist/providers/vercel/rest.d.ts +0 -20
  425. package/dist/providers/vercel/rest.js +0 -1020
  426. package/dist/providers/wispr-flow/index.d.ts +0 -14
  427. package/dist/providers/wispr-flow/index.js +0 -117
  428. package/dist/providers/wispr-flow/skill.generated.js +0 -8
  429. /package/dist/providers/github/{key.js → lib/key.js} +0 -0
@@ -1,11 +1,10 @@
1
1
  import { skill } from "./skill.generated.js";
2
2
  /**
3
- * Tithe.ly's giving API (v1), hand-written against the per-operation reference
4
- * at https://docs.tithe.ly/reference/introduction. No SDK exists to depend on,
5
- * and none would be welcome: this is a payments API, and every tool below was
6
- * chosen and written, not generated (decision record 0001: hand-authored tools).
7
- *
8
- * Facts the code leans on, each from the reference unless marked otherwise:
3
+ * Tithe.ly's giving API (v1) on the provider kit (decision 0010). No SDK
4
+ * exists to depend on, and none would be welcome: this is a payments API, and
5
+ * every tool was chosen and written, not generated (decision record 0001:
6
+ * hand-authored tools). The vendor lives in `api.ts` and the tools in
7
+ * `tools.ts`; this file holds the definition and its options.
9
8
  *
10
9
  * - **Access is gated.** Keys are issued on request: email support@tithe.ly
11
10
  * with the church or organization and what you are building, and Tithe.ly
@@ -15,989 +14,111 @@ import { skill } from "./skill.generated.js";
15
14
  * - **Two published endpoints.** `https://tithe.ly/api/v1/` (live) and
16
15
  * `https://tithelydev.com/api/v1/` (test). See `environment` below for why
17
16
  * the choice is required rather than defaulted.
18
- * - **HTTP Basic auth**, public key as user and private key as password.
19
- * - **Envelopes vary by operation.** Lists answer `{status, type, data: []}`;
20
- * single reads and writes answer `{status, <kind>_id, type, object}`; the
21
- * reference's charge examples show a bare object. Amounts are integer cents
22
- * serialized as strings (sometimes numbers), and dates are Unix-second
23
- * strings — `deposit_date` may be the literal `"pending"`.
24
- * - **Refusals arrive as HTTP 200** `{status: "fail", reason}` with no code.
25
- * The reference documents no error shape at all; this one comes from the
26
- * recorded fixtures of the third-party Omnipay driver
27
- * (github.com/elvanto/omnipay-tithely), which also confirms the charge
28
- * envelope and form-encoded request bodies. Treat it as well-supported
29
- * observation, not contract.
30
- * - **Writes are form-encoded.** Each operation snippet declares an
31
- * `application/json` body — ReadMe's default — but every curl sample sends
32
- * `-d field=value`, and so does the Omnipay driver. Form encoding is what the
33
- * documented examples actually send, so the mutate hatch frames that.
34
- * - **Cursor pagination** by id: `starting_after` / `ending_before` with
35
- * `limit` (default 10; the charges page documents a maximum of 100) and
36
- * `order_by` DESC|ASC. No `has_more` is returned.
37
17
  * - **No published rate limit**, so no default `callAdmission` (P12).
38
18
  * - **No deposits or settlements resource.** A charge carries `amount`,
39
19
  * `net_amount`, `fees`, and `deposit_date`; that is the whole payout story.
40
- * - **Not targeted:** an undocumented v2 (`https://tithe.ly/api/v2`, with
41
- * transactions, funds, organizations, and mail per a third-party catalogue)
42
- * exists but is not public. The guarded transport confines every path below
43
- * `/api/v1`, so the hatches cannot reach it either.
44
- *
45
- * Drift: there is no combined specification, but each reference page served
46
- * as `.md` embeds a one-operation OpenAPI 3.1 snippet. `scripts/drift-check.mjs`
47
- * assembles those snippets for the pages listed in
48
- * `src/providers/tithely/drift.json` and digests each operation like any
49
- * other provider's, so `pnpm run providers:check --provider tithely` runs
50
- * credential-free.
51
20
  */
52
- import { apiConnector as api, defined } from "../../connectors/api-connector.js";
53
- import { guardedFetch, retryAfterMs, } from "../../connectors/guarded-fetch.js";
54
- import { ConnectorCallError } from "../../errors.js";
55
21
  import { keys, optionsOf } from "../../config-schema.js";
56
22
  import { PROVIDER_COMMON } from "../../connectors/option-shapes.js";
57
- import { asProviderFactory } from "../../provider.js";
23
+ import { defineApiProvider } from "../../http/provider.js";
24
+ import { sensitiveFieldsOption } from "../_shared/sensitive-fields.js";
25
+ import { authenticate, DEFAULT_PAGE_SIZE, errors, KEY_PAIR_CREDENTIAL, MAX_PAGE_SIZE, probe, result, TITHELY_MAX_RESPONSE_BYTES, TITHELY_INDEX, TITHELY_NAMED, TITHELY_VERDICTS, TithelyApi, tithelySafety, } from "./api.js";
26
+ import { tithelyTools } from "./tools.js";
58
27
  /** Tithe.ly's two published v1 endpoints, one per environment. */
59
28
  export const TITHELY_API_BASE_URLS = {
60
29
  live: "https://tithe.ly/api/v1",
61
30
  test: "https://tithelydev.com/api/v1",
62
31
  };
63
- /** Tithe.ly's default page size is 10 and its documented maximum 100. */
64
- const PROVIDER_MAX_PAGE_SIZE = 100;
65
- /**
66
- * One below the provider's maximum, because each list asks for one extra row
67
- * to learn whether another page exists — Tithe.ly returns no `has_more`, and a
68
- * full page is not proof of a next one.
69
- */
70
- const MAX_PAGE_SIZE = PROVIDER_MAX_PAGE_SIZE - 1;
71
- const DEFAULT_PAGE_SIZE = 25;
72
- /** A full list page of fully embedded organizations stays far below this. */
73
- const TITHELY_MAX_RESPONSE_BYTES = 4 * 1024 * 1024;
74
- function asRecord(value) {
75
- return value && typeof value === "object" && !Array.isArray(value) ? value : {};
76
- }
77
- function asArray(value) {
78
- return Array.isArray(value) ? value : [];
79
- }
80
- function compact(value) {
81
- return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined));
82
- }
83
- function text(value) {
84
- return typeof value === "string" && value !== "" ? value : undefined;
85
- }
86
- // --- Credentials --------------------------------------------------------------
87
- const KEY_PAIR_CREDENTIAL = {
88
- label: "Tithe.ly API keys",
89
- description: "The public and private key pair for this connection's environment. API access is by request: email support@tithe.ly to have keys issued. Test keys work only against tithelydev.com and live keys only against tithe.ly.",
90
- fields: [
91
- {
92
- name: "publicKey",
93
- label: "Public key",
94
- description: "Starts with pub_. Sent as the HTTP Basic user name.",
95
- placeholder: "pub_…",
96
- inputType: "text",
97
- },
98
- {
99
- name: "privateKey",
100
- label: "Private key",
101
- description: "Starts with pri_. Sent as the HTTP Basic password; never expose it in a browser.",
102
- placeholder: "pri_…",
103
- inputType: "password",
104
- },
105
- ],
106
- };
107
- /**
108
- * Refuse a pair that cannot be right before it costs a request: a missing
109
- * half, keys swapped between the fields, or a header-unsafe character. Only
110
- * the obvious mismatches — a key without either prefix is passed through,
111
- * because Tithe.ly has never promised the prefix and a stricter check would
112
- * lock out a key format it later introduces.
113
- */
114
- function keyPairProblem(values) {
115
- const publicKey = values?.["publicKey"]?.trim() ?? "";
116
- const privateKey = values?.["privateKey"]?.trim() ?? "";
117
- if (!publicKey || !privateKey) {
118
- return "Both the Tithe.ly public key and private key are required.";
119
- }
120
- if (publicKey.startsWith("pri_") && privateKey.startsWith("pub_")) {
121
- return "The Tithe.ly keys are swapped: the pub_ key belongs in the public-key field and the pri_ key in the private-key field.";
122
- }
123
- if (publicKey.startsWith("pri_")) {
124
- return "The public-key field holds a private (pri_) key. Put the pub_ key there.";
125
- }
126
- if (privateKey.startsWith("pub_")) {
127
- return "The private-key field holds a public (pub_) key. Put the pri_ key there.";
128
- }
129
- if (!/^[\x21-\x7e]+$/.test(publicKey) || !/^[\x21-\x7e]+$/.test(privateKey)) {
130
- return "A Tithe.ly key contains whitespace or a non-ASCII character; paste the key exactly as issued.";
131
- }
132
- return { publicKey, privateKey };
133
- }
134
- const operatorKeyPair = async (ctx) => {
135
- const pair = keyPairProblem((await ctx.credential?.getAll()) ?? null);
136
- if (typeof pair === "string") {
137
- throw new ConnectorCallError("auth_required", `${pair} Call authorize_connector for recovery options; an operator adds the keys on this connection in the operator UI.`);
138
- }
139
- return { Authorization: `Basic ${btoa(`${pair.publicKey}:${pair.privateKey}`)}` };
140
- };
141
- // --- Transport and failures -----------------------------------------------------
142
- function detailFor(payload, status) {
143
- const root = asRecord(payload);
144
- const error = root["error"];
145
- const message = text(root["reason"]) ?? text(root["message"]) ?? text(error) ?? text(asRecord(error)["message"]);
146
- return message ? `Tithe.ly: ${message.trim()}` : `Tithe.ly returned HTTP ${status}.`;
147
- }
148
- /**
149
- * Map a failure by the caller's next move (H11). Tithe.ly documents no error
150
- * statuses, so every branch states what it cannot know rather than choosing
151
- * the convenient reading.
152
- */
153
- function tithelyFailure(status, headers, payload) {
154
- const detail = detailFor(payload, status);
155
- if (status === 429) {
156
- const wait = retryAfterMs(headers);
157
- return new ConnectorCallError("rate_limited", `${detail} Tithe.ly publishes no rate limit; wait before retrying, and add a callAdmission budget if this recurs.`, wait === undefined ? {} : { retryAfterMs: wait });
158
- }
159
- if (status === 401) {
160
- return new ConnectorCallError("auth_required", `${detail} Tithe.ly rejected the key pair. It may be revoked, mistyped, or issued for the other environment — test keys work only on tithelydev.com, live keys only on tithe.ly. An operator must fix the keys on this connection.`);
161
- }
162
- if (status === 403) {
163
- return new ConnectorCallError("provider_permission_denied", `${detail} Tithe.ly refused the request: the key pair may not reach this resource, or API access may not be enabled for this environment. Tithe.ly does not say which. Ask the account administrator to grant API access to this resource and environment.`, { retryable: false });
164
- }
165
- if (status === 404) {
166
- // Not `not_found`: nothing documents whether an id outside this key's
167
- // reach answers 404 too, so absence cannot be told from a permission gap.
168
- return new ConnectorCallError("connector_call_failed", `${detail} The path does not exist, or the id is unknown or outside this key pair's reach; Tithe.ly does not distinguish them. Re-read the id from its list tool.`, { retryable: false });
169
- }
170
- if (status === 400 || status === 422) {
171
- return new ConnectorCallError("invalid_args", detail);
172
- }
173
- if (status >= 500) {
174
- const wait = retryAfterMs(headers);
175
- return new ConnectorCallError("unavailable", `${detail} Tithe.ly is failing upstream.`, wait === undefined ? {} : { retryAfterMs: wait });
176
- }
177
- return new ConnectorCallError("connector_call_failed", detail, {
178
- retryable: false,
179
- });
180
- }
181
- /**
182
- * Tithe.ly's in-band refusal: HTTP 200 with a `status` other than `success`.
183
- * Its v1 API reports a declined payment, an unknown id, and a rejected
184
- * argument this same way, with prose and no code, so the classification stays
185
- * generic and the message says so — the reason is for the reader, never for
186
- * this code to parse.
187
- */
188
- function inBandFailure(payload) {
189
- const status = asRecord(payload)["status"];
190
- if (status === undefined || status === "success")
191
- return undefined;
192
- return new ConnectorCallError("connector_call_failed", `${detailFor(payload, 200)} (status "${String(status)}"). Tithe.ly reports declined payments, unknown ids, and rejected arguments all this way, without a code; read the reason before retrying.`, { retryable: false });
193
- }
194
- function tithelyTransport(baseUrl, authenticate) {
195
- return guardedFetch({
196
- provider: "Tithe.ly",
197
- baseUrl,
198
- headers: { Accept: "application/json" },
199
- maxResponseBytes: TITHELY_MAX_RESPONSE_BYTES,
200
- authenticate,
201
- });
202
- }
203
- async function callTithely(send, request, ctx) {
204
- return await send(request, ctx, async (response) => {
205
- const parsed = await response.jsonResult();
206
- if (!response.ok) {
207
- throw tithelyFailure(response.status, response.headers, "value" in parsed ? parsed.value : undefined);
208
- }
209
- if (!("value" in parsed)) {
210
- throw new ConnectorCallError("connector_call_failed", "Tithe.ly returned a successful response that is not JSON.", { retryable: false });
211
- }
212
- const refused = inBandFailure(parsed.value);
213
- if (refused)
214
- throw refused;
215
- return parsed.value;
216
- });
217
- }
218
- // --- Projection -------------------------------------------------------------------
219
- /** Integer cents from Tithe.ly's string-or-number amounts. */
220
- function cents(value) {
221
- if (typeof value === "number" && Number.isFinite(value))
222
- return Math.trunc(value);
223
- if (typeof value === "string" && /^-?\d+$/.test(value.trim())) {
224
- return Number(value.trim());
225
- }
226
- return undefined;
227
- }
228
- /** ISO 8601 UTC from a Unix-seconds string or number. */
229
- function isoTime(value) {
230
- const seconds = typeof value === "number"
231
- ? value
232
- : typeof value === "string" && /^\d+$/.test(value.trim())
233
- ? Number(value.trim())
234
- : undefined;
235
- if (seconds === undefined || !Number.isFinite(seconds))
236
- return undefined;
237
- const date = new Date(seconds * 1000);
238
- return Number.isNaN(date.getTime()) ? undefined : date.toISOString();
239
- }
240
- /**
241
- * The entity inside an envelope, with the envelope's own id folded in. Single
242
- * reads put the id beside `object` rather than inside it.
243
- */
244
- function entity(payload, idKey) {
245
- const root = asRecord(payload);
246
- const inner = asRecord(root["object"] ?? root["data"]);
247
- const record = Object.keys(inner).length > 0 ? inner : root;
248
- const id = record[idKey] ?? root[idKey];
249
- return id === undefined ? record : { ...record, [idKey]: id };
250
- }
251
- function projectAddress(value) {
252
- const address = asRecord(value);
253
- if (Object.keys(address).length === 0)
254
- return undefined;
255
- return compact({
256
- streetAddress: text(address["street_address"]),
257
- city: text(address["city"]),
258
- state: text(address["state"]),
259
- postal: text(address["postal"]),
260
- country: text(address["country"]),
261
- });
262
- }
263
- const FUND_STATUS = {
264
- "0": "archived",
265
- "1": "active",
266
- "2": "hidden",
267
- };
268
- function projectFunds(organization) {
269
- const full = asArray(organization["giving_types_full"]);
270
- if (full.length > 0) {
271
- return full.map((value) => {
272
- const fund = asRecord(value);
273
- const status = fund["status"] === undefined ? undefined : String(fund["status"]);
274
- return compact({
275
- id: fund["id"] === undefined ? undefined : String(fund["id"]),
276
- name: String(fund["name"] ?? ""),
277
- status: status === undefined ? undefined : (FUND_STATUS[status] ?? status),
278
- });
279
- });
280
- }
281
- return asArray(organization["giving_types"])
282
- .filter((name) => typeof name === "string")
283
- .map((name) => ({ name }));
284
- }
285
32
  /**
286
- * Deliberately omits `bank` (payout bank name and account last four) and
287
- * `legal` (the legal contact's name and date of birth). Neither answers a
288
- * giving question, and both are exactly what should not leak into a
289
- * transcript; `raw: true` returns them for the operator who asks.
33
+ * The organization fields every result drops by default, by name and at any
34
+ * depth: `bank` (payout bank name and account last four) and `legal` (the
35
+ * legal contact's name and date of birth). Neither answers a giving question,
36
+ * and both are exactly what should not leak into a transcript.
290
37
  */
291
- function projectOrganization(value) {
292
- const organization = asRecord(value);
293
- return compact({
294
- organizationId: text(organization["organization_id"]),
295
- name: text(organization["name"]),
296
- ownerAccountId: text(organization["account_id"]),
297
- website: text(organization["website"]),
298
- phoneNumber: text(organization["phone_number"]),
299
- address: projectAddress(organization["address"]),
300
- payoutCurrency: text(asRecord(organization["bank"])["currency"]),
301
- createdAt: isoTime(organization["created_date"]),
302
- funds: projectFunds(organization),
303
- });
304
- }
305
- function projectAccountSummary(value) {
306
- const account = asRecord(value);
307
- return compact({
308
- accountId: text(account["account_id"]),
309
- firstName: text(account["first_name"]),
310
- lastName: text(account["last_name"]),
311
- email: text(account["email"]),
312
- createdAt: isoTime(account["created_date"]),
313
- });
314
- }
315
- function projectAccount(value) {
316
- const account = asRecord(value);
317
- return compact({
318
- ...projectAccountSummary(account),
319
- phoneNumber: text(account["phone_number"]),
320
- address: projectAddress(account["address"]),
321
- });
322
- }
323
- /** A nested reference that is sometimes an id string and sometimes an object. */
324
- function reference(value, idKey) {
325
- if (typeof value === "string")
326
- return { id: value, record: {} };
327
- const record = asRecord(value);
328
- return { ...defined({ id: text(record[idKey]) }), record };
329
- }
330
- /** The donor as a giving record names them; address and phone stay on get_account. */
331
- function projectDonor(record) {
332
- const donor = compact({
333
- firstName: text(record["first_name"]),
334
- lastName: text(record["last_name"]),
335
- email: text(record["email"]),
336
- });
337
- return Object.keys(donor).length === 0 ? undefined : donor;
338
- }
339
- function projectCharge(value) {
340
- const charge = asRecord(value);
341
- const organization = reference(charge["organization"], "organization_id");
342
- const donor = reference(charge["donor_account"], "account_id");
343
- const method = reference(charge["payment_method"], "pm_id");
344
- const deposit = charge["deposit_date"];
345
- return compact({
346
- chargeId: text(charge["charge_id"]),
347
- status: text(charge["charge_status"]),
348
- amountCents: cents(charge["amount"]),
349
- netAmountCents: cents(charge["net_amount"]),
350
- feesCents: cents(charge["fees"]),
351
- currency: text(charge["currency"]),
352
- fund: text(charge["giving_type"]),
353
- chargedAt: isoTime(charge["charge_date"]),
354
- depositedAt: deposit === "pending" ? "pending" : isoTime(deposit),
355
- recurring: typeof charge["recurring_transaction"] === "boolean" ? charge["recurring_transaction"] : undefined,
356
- feesCovered: typeof charge["fees_covered"] === "boolean" ? charge["fees_covered"] : undefined,
357
- memo: text(charge["memo"]),
358
- organizationId: organization.id,
359
- organizationName: text(organization.record["name"]),
360
- accountId: donor.id ?? text(method.record["account_id"]),
361
- donor: projectDonor(donor.record),
362
- paymentMethodId: method.id,
363
- paymentMethod: Object.keys(method.record).length === 0
364
- ? undefined
365
- : compact({
366
- type: text(method.record["pm_type"]),
367
- brand: text(method.record["brand"]),
368
- last4: text(method.record["last_4_digits"]),
369
- }),
370
- });
371
- }
372
- function projectRecurring(value) {
373
- const recurring = asRecord(value);
374
- const organization = reference(recurring["organization"], "organization_id");
375
- const donor = reference(recurring["donor_account"], "account_id");
376
- const method = reference(recurring["payment_method"], "pm_id");
377
- return compact({
378
- recurringId: text(recurring["recurring_id"]),
379
- amountCents: cents(recurring["amount"]),
380
- currency: text(recurring["currency"]),
381
- fund: text(recurring["giving_type"]),
382
- term: text(recurring["term"]),
383
- startAt: isoTime(recurring["start_date"]),
384
- organizationId: organization.id,
385
- organizationName: text(organization.record["name"]),
386
- accountId: donor.id,
387
- donor: projectDonor(donor.record),
388
- paymentMethodId: method.id,
389
- });
390
- }
391
- function projectPaymentMethod(value) {
392
- const method = asRecord(value);
393
- return compact({
394
- paymentMethodId: text(method["pm_id"]),
395
- accountId: text(method["account_id"]),
396
- type: text(method["pm_type"]),
397
- brand: text(method["brand"]),
398
- last4: text(method["last_4_digits"]),
399
- });
400
- }
401
- /**
402
- * Fetch one page by asking for `limit + 1` rows: the extra row is the only
403
- * honest `hasMore` Tithe.ly allows, since it returns no flag and a full page
404
- * proves nothing. The cursor is the id of the last row kept, which is exactly
405
- * what Tithe.ly's `starting_after` takes.
406
- */
407
- async function listPage(send, ctx, path, idKey, args, defaultPageSize, filters = {}) {
408
- const limit = args["limit"] ?? defaultPageSize;
409
- const payload = await callTithely(send, {
410
- method: "GET",
411
- path,
412
- query: {
413
- ...filters,
414
- limit: limit + 1,
415
- order_by: args["order"] === "asc" ? "ASC" : "DESC",
416
- starting_after: args["cursor"],
417
- },
418
- }, ctx);
419
- const data = asRecord(payload)["data"];
420
- if (data !== undefined && !Array.isArray(data)) {
421
- throw new ConnectorCallError("connector_call_failed", "Tithe.ly returned a list response whose data is not an array.", { retryable: false });
422
- }
423
- const all = data ?? [];
424
- const rows = all.slice(0, limit);
425
- const hasMore = all.length > limit;
426
- let nextCursor = null;
427
- if (hasMore) {
428
- nextCursor = text(asRecord(rows[rows.length - 1])[idKey]) ?? null;
429
- if (nextCursor === null) {
430
- throw new ConnectorCallError("connector_call_failed", `Tithe.ly returned a page whose last row has no ${idKey}, so the next page cannot be addressed.`, { retryable: false });
431
- }
432
- }
433
- return { rows, page: { hasMore, nextCursor } };
434
- }
435
- /** An ISO 8601 date-time with no `Z` or offset, which `Date.parse` reads as local time. */
436
- const OFFSETLESS_DATE_TIME = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?$/;
437
- /**
438
- * Unix seconds from an integer or an ISO 8601 date/date-time. A date-time
439
- * without an offset is UTC, as the schema promises, not the runtime's zone.
440
- */
441
- function unixSeconds(value, name) {
442
- if (value === undefined)
443
- return undefined;
444
- if (typeof value === "number")
445
- return value;
446
- const raw = String(value).trim();
447
- if (/^\d+$/.test(raw))
448
- return Number(raw);
449
- const parsed = Date.parse(OFFSETLESS_DATE_TIME.test(raw) ? `${raw}Z` : raw);
450
- if (!Number.isFinite(parsed)) {
451
- throw new ConnectorCallError("invalid_args", `${name} must be Unix seconds or an ISO 8601 date or date-time; received "${raw}".`);
38
+ export const TITHELY_SENSITIVE_FIELDS = ["bank", "legal"];
39
+ /** The closed options tithely() accepts; see `assertKnownOptions`. */
40
+ const TITHELY_OPTIONS = optionsOf()({
41
+ ...PROVIDER_COMMON,
42
+ ...keys("environment", "defaultPageSize", "baseUrl", "sensitiveFields"),
43
+ });
44
+ function environmentOf(options) {
45
+ const environment = options.environment;
46
+ if (environment !== "live" && environment !== "test") {
47
+ throw new Error('tithely() requires environment: "live" (tithe.ly) or "test" (tithelydev.com). Tithe.ly keys are environment-specific and unrecognizable, so connecta will not guess.');
452
48
  }
453
- return Math.floor(parsed / 1000);
49
+ return environment;
454
50
  }
51
+ const sensitiveFields = (options) => sensitiveFieldsOption("tithely", options.sensitiveFields);
455
52
  /**
456
- * Tithe.ly lists charges and recurring gifts only for a donor or an
457
- * organization, so a call naming neither can only fail: refuse it locally.
53
+ * The connection context ahead of the maintained guide (decision 0011, G):
54
+ * the environment, purpose, the deployment's sensitive-field choice, and the
55
+ * options that change what an agent can do. `ProviderContext.usageGuide`
56
+ * renders it with `SKILL.md`'s body and the generated Conventions.
458
57
  */
459
- function scopeFilters(args, noun) {
460
- if (args["organizationId"] === undefined && args["accountId"] === undefined) {
461
- throw new ConnectorCallError("invalid_args", `Tithe.ly lists ${noun} only for an organization or a donor: pass organizationId (from list_organizations) or accountId (from list_accounts).`);
462
- }
463
- return {
464
- organization_id: args["organizationId"],
465
- account_id: args["accountId"],
466
- };
467
- }
468
- // --- Schemas ----------------------------------------------------------------------
469
- function namedInput(properties, required) {
470
- return { type: "object", properties, required, additionalProperties: false };
471
- }
472
- const RAW_PROPERTY = {
473
- type: "boolean",
474
- description: "Return Tithe.ly's untouched records instead of the lean projection.",
475
- };
476
- const CURSOR_PROPERTY = {
477
- type: "string",
478
- minLength: 1,
479
- description: "Opaque nextCursor from the previous page. Pass it back unchanged.",
480
- };
481
- const ORDER_PROPERTY = {
482
- type: "string",
483
- enum: ["desc", "asc"],
484
- description: "Newest first (desc, Tithe.ly's default) or oldest first. Keep it constant while paging.",
485
- };
486
- function limitProperty(defaultPageSize) {
487
- return {
488
- type: "integer",
489
- minimum: 1,
490
- maximum: MAX_PAGE_SIZE,
491
- description: `Rows per page, 1 to ${MAX_PAGE_SIZE} (Tithe.ly allows 100; connecta asks for one extra to compute hasMore). Defaults to ${defaultPageSize}.`,
492
- };
493
- }
494
- const ORGANIZATION_ID_PROPERTY = {
495
- type: "string",
496
- minLength: 1,
497
- description: "Organization id (org_…) from list_organizations.",
498
- };
499
- const ACCOUNT_ID_PROPERTY = {
500
- type: "string",
501
- minLength: 1,
502
- description: "Donor account id (user_…) from list_accounts.",
503
- };
504
- const PAGE_SCHEMA = {
505
- type: "object",
506
- properties: {
507
- hasMore: { type: "boolean" },
508
- nextCursor: { type: ["string", "null"] },
509
- },
510
- required: ["hasMore", "nextCursor"],
511
- };
512
- function listSchema(key, item) {
513
- return {
514
- type: "object",
515
- properties: { [key]: { type: "array", items: item }, page: PAGE_SCHEMA },
516
- required: [key, "page"],
517
- };
518
- }
519
- const ADDRESS_SCHEMA = {
520
- type: "object",
521
- properties: {
522
- streetAddress: { type: "string" },
523
- city: { type: "string" },
524
- state: { type: "string" },
525
- postal: { type: "string" },
526
- country: { type: "string" },
527
- },
528
- };
529
- const DONOR_SCHEMA = {
530
- type: "object",
531
- properties: {
532
- firstName: { type: "string" },
533
- lastName: { type: "string" },
534
- email: { type: "string" },
535
- },
536
- };
537
- const ORGANIZATION_SCHEMA = {
538
- type: "object",
539
- properties: {
540
- organizationId: { type: "string" },
541
- name: { type: "string" },
542
- ownerAccountId: { type: "string" },
543
- website: { type: "string" },
544
- phoneNumber: { type: "string" },
545
- address: ADDRESS_SCHEMA,
546
- payoutCurrency: { type: "string" },
547
- createdAt: { type: "string" },
548
- funds: {
549
- type: "array",
550
- items: {
551
- type: "object",
552
- properties: {
553
- id: { type: "string" },
554
- name: { type: "string" },
555
- status: { type: "string" },
556
- },
557
- required: ["name"],
558
- },
559
- },
560
- },
561
- required: ["funds"],
562
- };
563
- const ACCOUNT_SUMMARY_SCHEMA = {
564
- type: "object",
565
- properties: {
566
- accountId: { type: "string" },
567
- firstName: { type: "string" },
568
- lastName: { type: "string" },
569
- email: { type: "string" },
570
- createdAt: { type: "string" },
571
- },
572
- };
573
- const ACCOUNT_SCHEMA = {
574
- type: "object",
575
- properties: {
576
- ...ACCOUNT_SUMMARY_SCHEMA["properties"],
577
- phoneNumber: { type: "string" },
578
- address: ADDRESS_SCHEMA,
579
- },
580
- };
581
- const CHARGE_SCHEMA = {
582
- type: "object",
583
- properties: {
584
- chargeId: { type: "string" },
585
- status: { type: "string" },
586
- amountCents: { type: "integer" },
587
- netAmountCents: { type: "integer" },
588
- feesCents: { type: "integer" },
589
- currency: { type: "string" },
590
- fund: { type: "string" },
591
- chargedAt: { type: "string" },
592
- depositedAt: { type: "string" },
593
- recurring: { type: "boolean" },
594
- feesCovered: { type: "boolean" },
595
- memo: { type: "string" },
596
- organizationId: { type: "string" },
597
- organizationName: { type: "string" },
598
- accountId: { type: "string" },
599
- donor: DONOR_SCHEMA,
600
- paymentMethodId: { type: "string" },
601
- paymentMethod: {
602
- type: "object",
603
- properties: {
604
- type: { type: "string" },
605
- brand: { type: "string" },
606
- last4: { type: "string" },
607
- },
608
- },
609
- },
610
- };
611
- const RECURRING_SCHEMA = {
612
- type: "object",
613
- properties: {
614
- recurringId: { type: "string" },
615
- amountCents: { type: "integer" },
616
- currency: { type: "string" },
617
- fund: { type: "string" },
618
- term: { type: "string" },
619
- startAt: { type: "string" },
620
- organizationId: { type: "string" },
621
- organizationName: { type: "string" },
622
- accountId: { type: "string" },
623
- donor: DONOR_SCHEMA,
624
- paymentMethodId: { type: "string" },
625
- },
626
- };
627
- const PAYMENT_METHOD_SCHEMA = {
628
- type: "object",
629
- properties: {
630
- paymentMethodId: { type: "string" },
631
- accountId: { type: "string" },
632
- type: { type: "string" },
633
- brand: { type: "string" },
634
- last4: { type: "string" },
635
- },
636
- };
637
- const PATH_PROPERTY = {
638
- type: "string",
639
- minLength: 1,
640
- maxLength: 512,
641
- description: "Path below /api/v1 beginning with '/', such as /charges-list or /refunds/ch_123. No query string.",
642
- };
643
- function pairsProperty(description, value) {
58
+ function usageGuide(options) {
59
+ const live = environmentOf(options) === "live";
644
60
  return {
645
- type: "array",
646
- maxItems: 50,
647
- description,
648
- items: {
649
- type: "object",
650
- properties: {
651
- name: { type: "string", minLength: 1, description: "Tithe.ly's snake_case parameter name." },
652
- value: { type: ["string", "number", "boolean"], description: value },
653
- },
654
- required: ["name", "value"],
655
- additionalProperties: false,
656
- },
61
+ heading: "Tithe.ly giving usage",
62
+ context: [
63
+ live
64
+ ? "Live Tithe.ly (tithe.ly): real donors and real money."
65
+ : "Test Tithe.ly (tithelydev.com): a test account; no real money moves.",
66
+ `Account purpose: ${options.purpose}`,
67
+ sensitiveFields(options) === "return"
68
+ ? "This connection returns organizations' payout bank and legal-contact details through tithely_api_read; the named reads still omit them."
69
+ : "This connection withholds organizations' payout bank and legal-contact details from every result.",
70
+ ...(options.readOnly === true ? ["This connection is read-only: every write is refused."] : []),
71
+ ...(options.defaultPageSize !== undefined
72
+ ? [`List tools return ${options.defaultPageSize} rows by default on this connection.`]
73
+ : []),
74
+ ],
75
+ summary: `${live ? "Live" : "Test"} giving: donor-data reduction, cents and ISO dates, ids that join, and the approval-gated write hatch.`,
76
+ // Required: the reduction rule for donor data and the id joins live only
77
+ // in the guide. No schema can carry either.
78
+ required: true,
657
79
  };
658
80
  }
659
- const QUERY_PROPERTY = pairsProperty("Query parameters as name/value pairs, e.g. account_id.", "Parameter value; stringified once by the transport.");
660
- const FORM_PROPERTY = pairsProperty("Form fields as name/value pairs, sent form-encoded as Tithe.ly's documented examples send them. Amounts are integer cents.", "Field value; stringified once by the transport.");
661
- function pairs(value) {
662
- const out = {};
663
- for (const row of asArray(value)) {
664
- const pair = asRecord(row);
665
- if (typeof pair["name"] !== "string")
666
- continue;
667
- const item = pair["value"];
668
- if (typeof item === "string" || typeof item === "number" || typeof item === "boolean") {
669
- out[pair["name"]] = String(item);
670
- }
671
- }
672
- return out;
673
- }
674
- const UNTOUCHED_RESULT = {
675
- type: "object",
676
- properties: { result: { description: "Tithe.ly's untouched response body, or null when empty." } },
677
- required: ["result"],
678
- };
679
- // --- Tools ------------------------------------------------------------------------
680
- function tools(send, defaultPageSize) {
681
- const readOnly = { readOnlyHint: true };
682
- const destructive = { readOnlyHint: false, destructiveHint: true };
683
- const limit = limitProperty(defaultPageSize);
684
- const raw = (args) => args["raw"] === true;
685
- return [
686
- {
687
- name: "tithely_api_get",
688
- description: "Call any Tithe.ly v1 GET endpoint and return its untouched response. Prefer named reads: raw organization records include bank last-4 and legal-contact DOB.",
689
- annotations: readOnly,
690
- inputSchema: namedInput({ path: PATH_PROPERTY, query: QUERY_PROPERTY }, ["path"]),
691
- outputSchema: UNTOUCHED_RESULT,
692
- handler: async (args, ctx) => ({
693
- result: (await callTithely(send, { method: "GET", path: String(args["path"]), query: pairs(args["query"]) }, ctx)) ??
694
- null,
695
- }),
696
- },
697
- {
698
- name: "tithely_api_mutate",
699
- description: "Send a form-encoded Tithe.ly v1 POST or DELETE: charges, refunds, recurring gifts, payment methods, accounts. Moves money or donor payment state; always approval-gated.",
700
- annotations: destructive,
701
- inputSchema: namedInput({
702
- method: {
703
- type: "string",
704
- enum: ["POST", "DELETE"],
705
- description: "POST creates, updates, charges, or refunds; DELETE cancels a recurring gift or removes a payment method.",
706
- },
707
- path: PATH_PROPERTY,
708
- query: QUERY_PROPERTY,
709
- form: FORM_PROPERTY,
710
- }, ["method", "path"]),
711
- outputSchema: UNTOUCHED_RESULT,
712
- handler: async (args, ctx) => {
713
- const form = pairs(args["form"]);
714
- const hasForm = Object.keys(form).length > 0;
715
- return {
716
- result: (await callTithely(send, {
717
- method: args["method"],
718
- path: String(args["path"]),
719
- query: pairs(args["query"]),
720
- ...(hasForm
721
- ? {
722
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
723
- rawBody: new URLSearchParams(form).toString(),
724
- }
725
- : {}),
726
- }, ctx)) ?? null,
727
- };
728
- },
729
- },
730
- {
731
- name: "list_organizations",
732
- description: "List the organizations these keys manage, with their funds (giving types) and status. Omits payout bank and legal-contact details.",
733
- annotations: readOnly,
734
- inputSchema: namedInput({ limit, cursor: CURSOR_PROPERTY, order: ORDER_PROPERTY, raw: RAW_PROPERTY }, []),
735
- outputSchema: listSchema("organizations", ORGANIZATION_SCHEMA),
736
- handler: async (args, ctx) => {
737
- const { rows, page } = await listPage(send, ctx, "/organizations-list", "organization_id", args, defaultPageSize);
738
- return { organizations: raw(args) ? rows : rows.map(projectOrganization), page };
739
- },
740
- },
741
- {
742
- name: "get_organization",
743
- description: "Get one organization with its funds (giving types) and whether each is active, hidden, or archived. Omits payout bank and legal-contact details.",
744
- annotations: readOnly,
745
- inputSchema: namedInput({ organizationId: ORGANIZATION_ID_PROPERTY, raw: RAW_PROPERTY }, ["organizationId"]),
746
- outputSchema: ORGANIZATION_SCHEMA,
747
- handler: async (args, ctx) => {
748
- const payload = await callTithely(send, { method: "GET", path: `/organizations/${encodeURIComponent(args["organizationId"])}` }, ctx);
749
- const record = entity(payload, "organization_id");
750
- return raw(args) ? record : projectOrganization(record);
751
- },
752
- },
753
- {
754
- name: "list_accounts",
755
- description: "List donor accounts by name, email, and id. Contact details stay on get_account; giving history is list_charges by accountId.",
756
- annotations: readOnly,
757
- inputSchema: namedInput({
758
- scope: {
759
- type: "string",
760
- enum: ["all", "api"],
761
- description: "all (default) lists every account Tithe.ly lets these keys see; api lists only accounts created through this API.",
762
- },
763
- limit,
764
- cursor: CURSOR_PROPERTY,
765
- order: ORDER_PROPERTY,
766
- raw: RAW_PROPERTY,
767
- }, []),
768
- outputSchema: listSchema("accounts", ACCOUNT_SUMMARY_SCHEMA),
769
- handler: async (args, ctx) => {
770
- const path = args["scope"] === "api" ? "/accounts-list" : "/accounts-list-all";
771
- const { rows, page } = await listPage(send, ctx, path, "account_id", args, defaultPageSize);
772
- return { accounts: raw(args) ? rows : rows.map(projectAccountSummary), page };
773
- },
774
- },
775
- {
776
- name: "get_account",
777
- description: "Get one donor account with email, phone, and postal address. Does not include giving; use list_charges with accountId.",
778
- annotations: readOnly,
779
- inputSchema: namedInput({ accountId: ACCOUNT_ID_PROPERTY, raw: RAW_PROPERTY }, ["accountId"]),
780
- outputSchema: ACCOUNT_SCHEMA,
781
- handler: async (args, ctx) => {
782
- const payload = await callTithely(send, { method: "GET", path: `/accounts/${encodeURIComponent(args["accountId"])}` }, ctx);
783
- const record = entity(payload, "account_id");
784
- return raw(args) ? record : projectAccount(record);
785
- },
786
- },
787
- {
788
- name: "list_charges",
789
- description: "List gifts (charges) to one organization or from one donor, optionally within a created-date range, with gross, net, and fees in cents.",
790
- annotations: readOnly,
791
- inputSchema: namedInput({
792
- organizationId: {
793
- ...ORGANIZATION_ID_PROPERTY,
794
- description: "Organization (org_…) the gifts went to. This or accountId is required.",
795
- },
796
- accountId: {
797
- ...ACCOUNT_ID_PROPERTY,
798
- description: "Donor account (user_…) that gave. This or organizationId is required.",
799
- },
800
- createdAfter: {
801
- type: ["integer", "string"],
802
- minimum: 0,
803
- minLength: 1,
804
- description: "Only charges created after this time: Unix seconds or an ISO 8601 date/date-time (UTC when no offset).",
805
- },
806
- createdBefore: {
807
- type: ["integer", "string"],
808
- minimum: 0,
809
- minLength: 1,
810
- description: "Only charges created before this time, in the same forms as createdAfter.",
811
- },
812
- limit,
813
- cursor: CURSOR_PROPERTY,
814
- order: ORDER_PROPERTY,
815
- raw: RAW_PROPERTY,
816
- }, []),
817
- outputSchema: listSchema("charges", CHARGE_SCHEMA),
818
- handler: async (args, ctx) => {
819
- const after = unixSeconds(args["createdAfter"], "createdAfter");
820
- const before = unixSeconds(args["createdBefore"], "createdBefore");
821
- if (after !== undefined && before !== undefined && after > before) {
822
- throw new ConnectorCallError("invalid_args", "createdAfter is later than createdBefore, so no charge can match.");
823
- }
824
- const { rows, page } = await listPage(send, ctx, "/charges-list", "charge_id", args, defaultPageSize, {
825
- ...scopeFilters(args, "charges"),
826
- created_after: after,
827
- created_before: before,
828
- });
829
- return { charges: raw(args) ? rows : rows.map(projectCharge), page };
830
- },
831
- },
832
- {
833
- name: "get_charge",
834
- description: "Get one charge with amounts in cents, fund, deposit date, donor name and email, and payment-method brand and last four.",
835
- annotations: readOnly,
836
- inputSchema: namedInput({
837
- chargeId: { type: "string", minLength: 1, description: "Charge id (ch_…) from list_charges." },
838
- raw: RAW_PROPERTY,
839
- }, ["chargeId"]),
840
- outputSchema: CHARGE_SCHEMA,
841
- handler: async (args, ctx) => {
842
- const payload = await callTithely(send, { method: "GET", path: `/charges/${encodeURIComponent(args["chargeId"])}` }, ctx);
843
- const record = entity(payload, "charge_id");
844
- return raw(args) ? record : projectCharge(record);
845
- },
846
- },
847
- {
848
- name: "list_recurring_charges",
849
- description: "List recurring gifts for one organization or one donor: amount, fund, term, and start date. Does not list the charges they produced.",
850
- annotations: readOnly,
851
- inputSchema: namedInput({
852
- organizationId: {
853
- ...ORGANIZATION_ID_PROPERTY,
854
- description: "Organization (org_…) receiving the gifts. This or accountId is required.",
855
- },
856
- accountId: {
857
- ...ACCOUNT_ID_PROPERTY,
858
- description: "Donor account (user_…) giving. This or organizationId is required.",
859
- },
860
- limit,
861
- cursor: CURSOR_PROPERTY,
862
- order: ORDER_PROPERTY,
863
- raw: RAW_PROPERTY,
864
- }, []),
865
- outputSchema: listSchema("recurringCharges", RECURRING_SCHEMA),
866
- handler: async (args, ctx) => {
867
- const { rows, page } = await listPage(send, ctx, "/recurring-list", "recurring_id", args, defaultPageSize, scopeFilters(args, "recurring gifts"));
868
- return { recurringCharges: raw(args) ? rows : rows.map(projectRecurring), page };
869
- },
870
- },
871
- {
872
- name: "get_recurring_charge",
873
- description: "Get one recurring gift by id: amount in cents, fund, term, start date, organization, and donor name and email.",
874
- annotations: readOnly,
875
- inputSchema: namedInput({
876
- recurringId: {
877
- type: "string",
878
- minLength: 1,
879
- description: "Recurring gift id (rc_…) from list_recurring_charges.",
880
- },
881
- raw: RAW_PROPERTY,
882
- }, ["recurringId"]),
883
- outputSchema: RECURRING_SCHEMA,
884
- handler: async (args, ctx) => {
885
- const payload = await callTithely(send, { method: "GET", path: `/recurring/${encodeURIComponent(args["recurringId"])}` }, ctx);
886
- const record = entity(payload, "recurring_id");
887
- return raw(args) ? record : projectRecurring(record);
888
- },
889
- },
890
- {
891
- name: "list_payment_methods",
892
- description: "List one donor's stored payment methods as type, brand, and last four. Every method in one response; never card numbers or tokens.",
893
- annotations: readOnly,
894
- inputSchema: namedInput({ accountId: ACCOUNT_ID_PROPERTY, raw: RAW_PROPERTY }, ["accountId"]),
895
- outputSchema: {
896
- type: "object",
897
- properties: { paymentMethods: { type: "array", items: PAYMENT_METHOD_SCHEMA } },
898
- required: ["paymentMethods"],
899
- },
900
- handler: async (args, ctx) => {
901
- const payload = await callTithely(send, { method: "GET", path: "/payment-methods-list", query: { account_id: args["accountId"] } }, ctx);
902
- const rows = asArray(asRecord(payload)["data"]);
903
- return { paymentMethods: raw(args) ? rows : rows.map(projectPaymentMethod) };
904
- },
905
- },
906
- ];
907
- }
908
- // --- Guide ------------------------------------------------------------------------
909
- function usageGuide(purpose, environment, instructions) {
910
- const accountInstructions = instructions?.trim();
911
- const where = environment === "live"
912
- ? "Live Tithe.ly (tithe.ly): real donors and real money."
913
- : "Test Tithe.ly (tithelydev.com): a test account; no real money moves.";
914
- return `# Tithe.ly giving usage
915
-
916
- ${where}
917
-
918
- Account purpose: ${purpose}${skill.fragments.guide_0}${accountInstructions ? `\n## ${skill.instructionsHeading}\n\n${accountInstructions}\n` : ""}`;
919
- }
920
- // --- Construction -----------------------------------------------------------------
921
- async function testKeyPair(send, environment, values, ctx) {
922
- const pair = keyPairProblem(values);
923
- // A mismatched pair is refused here, before any request leaves.
924
- if (typeof pair === "string")
925
- return { ok: false, message: pair };
926
- try {
927
- const payload = await callTithely(send, { method: "GET", path: "/organizations-list", query: { limit: 1 } }, {
928
- ...ctx,
929
- credential: {
930
- get: async (field) => (field ? (values[field] ?? null) : null),
931
- getAll: async () => values,
932
- },
933
- });
934
- const first = asRecord(asArray(asRecord(payload)["data"])[0]);
935
- const name = text(first["name"]);
936
- const id = text(first["organization_id"]);
937
- return {
938
- ok: true,
939
- message: name
940
- ? `Keys accepted by Tithe.ly (${environment}); first organization reached: ${name}${id ? ` (${id})` : ""}.`
941
- : `Keys accepted by Tithe.ly (${environment}); no organization is visible to them.`,
942
- };
943
- }
944
- catch (error) {
945
- return {
946
- ok: false,
947
- message: error instanceof ConnectorCallError ? error.message : "Tithe.ly rejected the keys.",
948
- };
949
- }
950
- }
951
- /** The closed options tithely() accepts; see `assertKnownOptions`. */
952
- const TITHELY_OPTIONS = optionsOf()({
953
- ...PROVIDER_COMMON,
954
- ...keys("environment", "defaultPageSize", "baseUrl"),
955
- });
956
81
  /** A maintained Tithe.ly giving connection over the v1 REST API. */
957
- export const tithely = asProviderFactory({
82
+ export const tithely = defineApiProvider({
958
83
  name: "tithely",
959
84
  title: "Tithe.ly",
960
- kind: "api",
961
85
  readme: "Tithe.ly",
962
- bundle: { "baselineGzip": 18076, "maxGzip": 78076 },
86
+ bundle: {
87
+ baselineGzip: 333313,
88
+ note: "./providers/tithely remeasures at 333,313 B gzip in its decision 0011 step 2, from 323,386 B on main at 57176ce1 (Node 26 local, system zlib): the standard hatch over an index derived from request and response contracts for all 21 operations (budgeted, paged discovery), and the kit's standard lists and fields. Reported against this baseline, not capped (#899).",
89
+ },
963
90
  skill,
964
91
  options: TITHELY_OPTIONS,
965
- create: tithelyConnector,
966
- });
967
- function tithelyConnector(id, options) {
968
- const purpose = options.purpose?.trim();
969
- if (!purpose) {
970
- throw new Error("tithely() requires a non-empty account purpose.");
971
- }
972
- const environment = options.environment;
973
- if (environment !== "live" && environment !== "test") {
974
- throw new Error('tithely() requires environment: "live" (tithe.ly) or "test" (tithelydev.com). Tithe.ly keys are environment-specific and unrecognizable, so connecta will not guess.');
975
- }
976
- const defaultPageSize = options.defaultPageSize ?? DEFAULT_PAGE_SIZE;
977
- if (!Number.isInteger(defaultPageSize) || defaultPageSize < 1 || defaultPageSize > MAX_PAGE_SIZE) {
978
- throw new Error(`tithely() defaultPageSize must be a whole number between 1 and ${MAX_PAGE_SIZE}.`);
979
- }
980
- const send = tithelyTransport(options.baseUrl?.trim() || TITHELY_API_BASE_URLS[environment], operatorKeyPair);
981
- const live = environment === "live";
982
- return api(id, {
983
- ...defined({
984
- authScope: options.authScope,
985
- callAdmission: options.callAdmission,
986
- maxResultBytes: options.maxResultBytes,
92
+ displayTitle: (options) => (environmentOf(options) === "live" ? "Tithe.ly Giving" : "Tithe.ly Giving (test)"),
93
+ description: (options) => environmentOf(options) === "live"
94
+ ? `Tithe.ly giving — live donors and real money — ${options.purpose}`
95
+ : `Tithe.ly giving (test environment, no real money) — ${options.purpose}`,
96
+ vendor: TithelyApi,
97
+ baseUrl: (options) => options.baseUrl?.trim() || TITHELY_API_BASE_URLS[environmentOf(options)],
98
+ maxResponseBytes: TITHELY_MAX_RESPONSE_BYTES,
99
+ headers: { Accept: "application/json" },
100
+ pageSize: { default: DEFAULT_PAGE_SIZE, max: MAX_PAGE_SIZE },
101
+ // No Tithe.ly write takes an idempotency key; construction checks this against the write tools.
102
+ idempotencyKey: false,
103
+ credential: KEY_PAIR_CREDENTIAL,
104
+ authenticate,
105
+ errors,
106
+ safety: (options) => tithelySafety({ names: TITHELY_SENSITIVE_FIELDS, choice: sensitiveFields(options) }),
107
+ result,
108
+ probe,
109
+ guide: usageGuide,
110
+ hatch: {
111
+ name: "tithely",
112
+ title: "Tithe.ly",
113
+ index: TITHELY_INDEX,
114
+ verdicts: TITHELY_VERDICTS,
115
+ named: TITHELY_NAMED,
116
+ pathExample: "/charges/ch_123",
117
+ notes: (options) => ({
118
+ read: sensitiveFields(options) === "return"
119
+ ? "Organization bank and legal details are returned."
120
+ : "Organization bank and legal details are dropped.",
987
121
  }),
988
- title: options.title ?? (live ? "Tithe.ly Giving" : "Tithe.ly Giving (test)"),
989
- description: live
990
- ? `Tithe.ly giving — live donors and real money — ${purpose}`
991
- : `Tithe.ly giving (test environment, no real money) — ${purpose}`,
992
- credential: KEY_PAIR_CREDENTIAL,
993
- testCredentials: (values, ctx) => testKeyPair(send, environment, values, ctx),
994
- usageGuide: {
995
- content: usageGuide(purpose, environment, options.instructions),
996
- summary: `${live ? "Live" : "Test"} giving: donor-data reduction, cents and ISO dates, cursor paging, and the form-encoded write hatch.`,
997
- // Required: the reduction rule for donor data and the write map behind
998
- // the mutate hatch live only here. No schema can carry either.
999
- required: true,
1000
- },
1001
- tools: tools(send, defaultPageSize),
1002
- });
1003
- }
122
+ },
123
+ tools: tithelyTools,
124
+ });