@vellumai/assistant 0.5.11 → 0.5.13

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 (209) hide show
  1. package/Dockerfile +42 -9
  2. package/docs/architecture/integrations.md +34 -32
  3. package/node_modules/@vellumai/ces-contracts/src/__tests__/grants.test.ts +7 -7
  4. package/node_modules/@vellumai/ces-contracts/src/handles.ts +5 -4
  5. package/node_modules/@vellumai/ces-contracts/src/index.ts +7 -0
  6. package/node_modules/@vellumai/ces-contracts/src/rpc.ts +5 -0
  7. package/node_modules/@vellumai/credential-storage/src/index.ts +1 -1
  8. package/openapi.yaml +87 -9
  9. package/package.json +1 -1
  10. package/src/__tests__/catalog-cache.test.ts +164 -0
  11. package/src/__tests__/catalog-search.test.ts +61 -0
  12. package/src/__tests__/cli-command-risk-guard.test.ts +181 -6
  13. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +396 -0
  14. package/src/__tests__/conversation-error.test.ts +3 -2
  15. package/src/__tests__/credential-security-invariants.test.ts +9 -15
  16. package/src/__tests__/credential-vault-unit.test.ts +32 -34
  17. package/src/__tests__/credential-vault.test.ts +25 -33
  18. package/src/__tests__/credentials-cli.test.ts +3 -3
  19. package/src/__tests__/daemon-credential-client.test.ts +2 -2
  20. package/src/__tests__/first-greeting.test.ts +7 -0
  21. package/src/__tests__/host-bash-proxy.test.ts +79 -0
  22. package/src/__tests__/host-cu-proxy.test.ts +90 -0
  23. package/src/__tests__/host-file-proxy.test.ts +89 -0
  24. package/src/__tests__/integration-status.test.ts +5 -5
  25. package/src/__tests__/list-messages-attachments.test.ts +171 -0
  26. package/src/__tests__/mcp-abort-signal.test.ts +205 -0
  27. package/src/__tests__/messaging-send-tool.test.ts +5 -5
  28. package/src/__tests__/navigate-settings-tab.test.ts +6 -2
  29. package/src/__tests__/notification-telegram-adapter.test.ts +125 -0
  30. package/src/__tests__/oauth-cli.test.ts +126 -119
  31. package/src/__tests__/oauth-provider-profiles.test.ts +55 -20
  32. package/src/__tests__/oauth-scope-policy.test.ts +4 -6
  33. package/src/__tests__/onboarding-template-contract.test.ts +2 -2
  34. package/src/__tests__/platform.test.ts +3 -168
  35. package/src/__tests__/secret-routes-managed-proxy.test.ts +78 -0
  36. package/src/__tests__/secure-keys-managed-failover.test.ts +73 -0
  37. package/src/__tests__/skill-feature-flags.test.ts +8 -0
  38. package/src/__tests__/skill-secret-handling-guard.test.ts +212 -0
  39. package/src/__tests__/skills-uninstall.test.ts +2 -2
  40. package/src/__tests__/slack-messaging-token-resolution.test.ts +22 -24
  41. package/src/__tests__/slack-share-routes.test.ts +5 -5
  42. package/src/__tests__/system-prompt.test.ts +39 -0
  43. package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +1 -1
  44. package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +5 -4
  45. package/src/cli/AGENTS.md +47 -7
  46. package/src/cli/commands/browser-relay.ts +2 -17
  47. package/src/cli/commands/contacts.ts +6 -4
  48. package/src/cli/commands/conversations.ts +13 -1
  49. package/src/cli/commands/credential-execution.ts +16 -1
  50. package/src/cli/commands/credentials.ts +2 -8
  51. package/src/cli/commands/oauth/__tests__/connect.test.ts +29 -108
  52. package/src/cli/commands/oauth/__tests__/disconnect.test.ts +13 -87
  53. package/src/cli/commands/oauth/__tests__/mode.test.ts +22 -69
  54. package/src/cli/commands/oauth/__tests__/ping.test.ts +20 -79
  55. package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +574 -0
  56. package/src/cli/commands/oauth/__tests__/providers-update.test.ts +416 -0
  57. package/src/cli/commands/oauth/__tests__/status.test.ts +12 -40
  58. package/src/cli/commands/oauth/__tests__/token.test.ts +3 -50
  59. package/src/cli/commands/oauth/apps.ts +63 -44
  60. package/src/cli/commands/oauth/connect.ts +187 -155
  61. package/src/cli/commands/oauth/disconnect.ts +27 -75
  62. package/src/cli/commands/oauth/index.ts +36 -46
  63. package/src/cli/commands/oauth/mode.ts +22 -34
  64. package/src/cli/commands/oauth/ping.ts +19 -45
  65. package/src/cli/commands/oauth/providers.ts +569 -62
  66. package/src/cli/commands/oauth/request.ts +36 -48
  67. package/src/cli/commands/oauth/shared.ts +1 -19
  68. package/src/cli/commands/oauth/status.ts +14 -25
  69. package/src/cli/commands/oauth/token.ts +25 -34
  70. package/src/cli/commands/platform/__tests__/connect.test.ts +224 -0
  71. package/src/cli/commands/platform/__tests__/disconnect.test.ts +237 -0
  72. package/src/cli/commands/platform/__tests__/status.test.ts +246 -0
  73. package/src/cli/commands/platform/connect.ts +104 -0
  74. package/src/cli/commands/platform/disconnect.ts +118 -0
  75. package/src/cli/commands/{platform.ts → platform/index.ts} +108 -38
  76. package/src/cli/commands/sequence.ts +5 -4
  77. package/src/cli/commands/shotgun.ts +16 -0
  78. package/src/cli/commands/skills.ts +173 -41
  79. package/src/cli/commands/usage.ts +5 -11
  80. package/src/cli/lib/daemon-credential-client.ts +22 -38
  81. package/src/cli/program.ts +1 -1
  82. package/src/config/assistant-feature-flags.ts +3 -7
  83. package/src/config/bundled-skills/contacts/tools/google-contacts.ts +1 -1
  84. package/src/config/bundled-skills/conversations/SKILL.md +20 -0
  85. package/src/config/bundled-skills/conversations/TOOLS.json +23 -0
  86. package/src/config/bundled-skills/conversations/tools/rename-conversation.ts +66 -0
  87. package/src/config/bundled-skills/gmail/SKILL.md +13 -13
  88. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +3 -3
  89. package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +2 -2
  90. package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +1 -1
  91. package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +1 -1
  92. package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +1 -1
  93. package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +1 -1
  94. package/src/config/bundled-skills/gmail/tools/gmail-label.ts +2 -2
  95. package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +1 -1
  96. package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +1 -1
  97. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +1 -1
  98. package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +1 -1
  99. package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +1 -1
  100. package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +1 -1
  101. package/src/config/bundled-skills/google-calendar/SKILL.md +10 -4
  102. package/src/config/bundled-skills/google-calendar/tools/shared.ts +1 -1
  103. package/src/config/bundled-skills/messaging/SKILL.md +7 -7
  104. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +5 -2
  105. package/src/config/bundled-skills/messaging/tools/shared.ts +5 -6
  106. package/src/config/bundled-skills/settings/TOOLS.json +5 -3
  107. package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +4 -2
  108. package/src/config/bundled-tool-registry.ts +5 -0
  109. package/src/config/feature-flag-registry.json +2 -2
  110. package/src/credential-execution/client.ts +15 -3
  111. package/src/daemon/conversation-agent-loop.ts +2 -0
  112. package/src/daemon/conversation-error.ts +36 -6
  113. package/src/daemon/conversation-messaging.ts +9 -0
  114. package/src/daemon/conversation-runtime-assembly.ts +33 -0
  115. package/src/daemon/conversation-surfaces.ts +120 -14
  116. package/src/daemon/conversation.ts +5 -0
  117. package/src/daemon/first-greeting.ts +6 -1
  118. package/src/daemon/handlers/skills.ts +148 -3
  119. package/src/daemon/host-bash-proxy.ts +16 -0
  120. package/src/daemon/host-cu-proxy.ts +16 -0
  121. package/src/daemon/host-file-proxy.ts +16 -0
  122. package/src/daemon/lifecycle.ts +56 -5
  123. package/src/daemon/message-types/conversations.ts +1 -0
  124. package/src/daemon/message-types/guardian-actions.ts +2 -0
  125. package/src/daemon/message-types/host-bash.ts +6 -1
  126. package/src/daemon/message-types/host-cu.ts +6 -1
  127. package/src/daemon/message-types/host-file.ts +6 -1
  128. package/src/daemon/message-types/integrations.ts +0 -1
  129. package/src/daemon/server.ts +29 -2
  130. package/src/hooks/cli.ts +74 -0
  131. package/src/inbound/platform-callback-registration.ts +7 -12
  132. package/src/index.ts +0 -12
  133. package/src/mcp/client.ts +6 -1
  134. package/src/mcp/manager.ts +2 -1
  135. package/src/memory/conversation-crud.ts +92 -3
  136. package/src/memory/conversation-key-store.ts +26 -0
  137. package/src/memory/conversation-queries.ts +6 -6
  138. package/src/memory/db-init.ts +16 -0
  139. package/src/memory/journal-memory.ts +8 -2
  140. package/src/memory/migrations/196-messages-conversation-created-at-index.ts +9 -0
  141. package/src/memory/migrations/196-strip-integration-prefix-from-provider-keys.ts +186 -0
  142. package/src/memory/migrations/197-oauth-providers-behavior-columns.ts +29 -0
  143. package/src/memory/migrations/198-drop-setup-skill-id-column.ts +11 -0
  144. package/src/memory/migrations/index.ts +4 -0
  145. package/src/memory/migrations/registry.ts +8 -0
  146. package/src/memory/schema/oauth.ts +11 -0
  147. package/src/messaging/provider.ts +13 -12
  148. package/src/messaging/providers/gmail/adapter.ts +44 -35
  149. package/src/messaging/providers/slack/adapter.ts +63 -33
  150. package/src/messaging/providers/telegram-bot/adapter.ts +6 -8
  151. package/src/messaging/providers/whatsapp/adapter.ts +6 -8
  152. package/src/notifications/adapters/telegram.ts +78 -2
  153. package/src/oauth/__tests__/identity-verifier.test.ts +464 -0
  154. package/src/oauth/byo-connection.test.ts +22 -24
  155. package/src/oauth/connect-orchestrator.ts +37 -76
  156. package/src/oauth/connect-types.ts +7 -65
  157. package/src/oauth/connection-resolver.test.ts +13 -13
  158. package/src/oauth/connection-resolver.ts +3 -4
  159. package/src/oauth/identity-verifier.ts +177 -0
  160. package/src/oauth/oauth-store.ts +228 -3
  161. package/src/oauth/platform-connection.test.ts +56 -6
  162. package/src/oauth/platform-connection.ts +8 -1
  163. package/src/oauth/seed-providers.ts +247 -34
  164. package/src/permissions/checker.ts +127 -1
  165. package/src/prompts/journal-context.ts +4 -1
  166. package/src/prompts/system-prompt.ts +54 -9
  167. package/src/prompts/templates/BOOTSTRAP.md +16 -5
  168. package/src/providers/anthropic/client.ts +2 -33
  169. package/src/runtime/guardian-action-service.ts +7 -2
  170. package/src/runtime/http-server.ts +12 -18
  171. package/src/runtime/http-types.ts +8 -1
  172. package/src/runtime/migrations/rebind-secrets-screen.ts +2 -2
  173. package/src/runtime/routes/conversation-management-routes.ts +31 -0
  174. package/src/runtime/routes/conversation-routes.ts +79 -4
  175. package/src/runtime/routes/guardian-action-routes.ts +15 -2
  176. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +21 -8
  177. package/src/runtime/routes/integrations/slack/share.ts +1 -1
  178. package/src/runtime/routes/oauth-apps.ts +2 -1
  179. package/src/runtime/routes/secret-routes.ts +45 -15
  180. package/src/runtime/routes/settings-routes.ts +12 -19
  181. package/src/runtime/routes/skills-routes.ts +45 -4
  182. package/src/schedule/integration-status.ts +2 -2
  183. package/src/security/ces-rpc-credential-backend.ts +19 -16
  184. package/src/security/oauth-completion-page.ts +153 -0
  185. package/src/security/oauth2.ts +3 -17
  186. package/src/security/secure-keys.ts +207 -7
  187. package/src/security/token-manager.ts +3 -6
  188. package/src/signals/bash.ts +6 -1
  189. package/src/skills/catalog-cache.ts +44 -0
  190. package/src/skills/catalog-search.ts +18 -0
  191. package/src/tools/browser/browser-manager.ts +2 -2
  192. package/src/tools/credentials/post-connect-hooks.ts +1 -1
  193. package/src/tools/credentials/vault.ts +34 -45
  194. package/src/tools/host-terminal/host-shell.ts +16 -3
  195. package/src/tools/mcp/mcp-tool-factory.ts +2 -1
  196. package/src/tools/skills/sandbox-runner.ts +16 -3
  197. package/src/tools/terminal/shell.ts +16 -3
  198. package/src/util/logger.ts +11 -1
  199. package/src/util/platform.ts +1 -91
  200. package/src/util/sentry-log-stream.ts +51 -0
  201. package/src/watcher/providers/github.ts +2 -2
  202. package/src/watcher/providers/gmail.ts +1 -1
  203. package/src/watcher/providers/google-calendar.ts +1 -1
  204. package/src/watcher/providers/linear.ts +2 -2
  205. package/src/workspace/migrations/011-backfill-installation-id.ts +5 -3
  206. package/src/workspace/migrations/020-rename-oauth-skill-dirs.ts +119 -0
  207. package/src/workspace/migrations/registry.ts +2 -0
  208. package/src/cli/commands/oauth/connections.ts +0 -255
  209. package/src/oauth/provider-behaviors.ts +0 -634
@@ -1,634 +0,0 @@
1
- /**
2
- * OAuth provider behavior registry.
3
- *
4
- * Contains code-side behavioral configuration for well-known OAuth
5
- * providers. Protocol-level fields (authUrl, tokenUrl, scopes, etc.)
6
- * are stored in the `oauth_providers` SQLite table and seeded by
7
- * `seed-providers.ts`. This module contains only fields that require
8
- * code references (functions, templates, skill IDs) and cannot be
9
- * serialised to a DB row.
10
- */
11
-
12
- import type { OAuthProviderBehavior } from "./connect-types.js";
13
-
14
- // ---------------------------------------------------------------------------
15
- // Provider behaviors
16
- // ---------------------------------------------------------------------------
17
-
18
- const PROVIDER_BEHAVIORS: Record<string, OAuthProviderBehavior> = {
19
- "integration:google": {
20
- service: "integration:google",
21
- // Google APIs for Gmail/Calendar/Contacts span multiple hosts; register
22
- // all of them so proxied bash can inject the OAuth bearer token reliably.
23
- injectionTemplates: [
24
- {
25
- hostPattern: "gmail.googleapis.com",
26
- injectionType: "header",
27
- headerName: "Authorization",
28
- valuePrefix: "Bearer ",
29
- },
30
- {
31
- hostPattern: "www.googleapis.com",
32
- injectionType: "header",
33
- headerName: "Authorization",
34
- valuePrefix: "Bearer ",
35
- },
36
- {
37
- hostPattern: "people.googleapis.com",
38
- injectionType: "header",
39
- headerName: "Authorization",
40
- valuePrefix: "Bearer ",
41
- },
42
- ],
43
- setupSkillId: "google-oauth-applescript",
44
- setup: {
45
- displayName: "Google (Gmail & Calendar)",
46
- dashboardUrl: "https://console.cloud.google.com/apis/credentials",
47
- appType: "Desktop app",
48
- requiresClientSecret: true,
49
- },
50
- identityVerifier: async (
51
- accessToken: string,
52
- ): Promise<string | undefined> => {
53
- try {
54
- const resp = await fetch(
55
- "https://www.googleapis.com/oauth2/v2/userinfo",
56
- {
57
- headers: { Authorization: `Bearer ${accessToken}` },
58
- },
59
- );
60
- if (resp.ok) {
61
- const body = (await resp.json()) as {
62
- email?: string;
63
- name?: string;
64
- };
65
- return body.email;
66
- }
67
- } catch {
68
- // Non-fatal — identity verification is best-effort
69
- }
70
- return undefined;
71
- },
72
- },
73
-
74
- "integration:slack": {
75
- service: "integration:slack",
76
- loopbackPort: 17322,
77
- injectionTemplates: [
78
- {
79
- hostPattern: "slack.com",
80
- injectionType: "header",
81
- headerName: "Authorization",
82
- valuePrefix: "Bearer ",
83
- },
84
- ],
85
- setup: {
86
- displayName: "Slack",
87
- dashboardUrl: "https://api.slack.com/apps",
88
- appType: "Slack App",
89
- requiresClientSecret: true,
90
- },
91
- identityVerifier: async (
92
- accessToken: string,
93
- ): Promise<string | undefined> => {
94
- try {
95
- const resp = await fetch("https://slack.com/api/auth.test", {
96
- headers: { Authorization: `Bearer ${accessToken}` },
97
- });
98
- if (resp.ok) {
99
- const body = (await resp.json()) as {
100
- ok: boolean;
101
- user?: string;
102
- team?: string;
103
- };
104
- if (!body.ok) return undefined;
105
- if (body.user && body.team) return `@${body.user} (${body.team})`;
106
- if (body.user) return `@${body.user}`;
107
- }
108
- } catch {
109
- // Non-fatal — identity verification is best-effort
110
- }
111
- return undefined;
112
- },
113
- },
114
-
115
- "integration:notion": {
116
- service: "integration:notion",
117
- loopbackPort: 17323,
118
- injectionTemplates: [
119
- {
120
- hostPattern: "api.notion.com",
121
- injectionType: "header",
122
- headerName: "Authorization",
123
- valuePrefix: "Bearer ",
124
- },
125
- ],
126
- setupSkillId: "notion-oauth-setup",
127
- setup: {
128
- displayName: "Notion",
129
- dashboardUrl: "https://www.notion.so/profile/integrations",
130
- appType: "Public integration",
131
- requiresClientSecret: true,
132
- },
133
- identityVerifier: async (
134
- accessToken: string,
135
- ): Promise<string | undefined> => {
136
- try {
137
- const resp = await fetch("https://api.notion.com/v1/users/me", {
138
- headers: {
139
- Authorization: `Bearer ${accessToken}`,
140
- "Notion-Version": "2022-06-28",
141
- },
142
- });
143
- if (resp.ok) {
144
- const body = (await resp.json()) as {
145
- name?: string;
146
- type?: string;
147
- person?: { email?: string };
148
- };
149
- return body.name ?? body.person?.email;
150
- }
151
- } catch {
152
- // Non-fatal — identity verification is best-effort
153
- }
154
- return undefined;
155
- },
156
- },
157
-
158
- "integration:twitter": {
159
- service: "integration:twitter",
160
- injectionTemplates: [
161
- {
162
- hostPattern: "api.x.com",
163
- injectionType: "header",
164
- headerName: "Authorization",
165
- valuePrefix: "Bearer ",
166
- },
167
- ],
168
- setupSkillId: "twitter-oauth-setup",
169
- setup: {
170
- displayName: "Twitter / X",
171
- dashboardUrl: "https://developer.x.com/en/portal/dashboard",
172
- appType: "App",
173
- requiresClientSecret: true,
174
- },
175
- identityVerifier: async (
176
- accessToken: string,
177
- ): Promise<string | undefined> => {
178
- try {
179
- const resp = await fetch("https://api.x.com/2/users/me", {
180
- headers: { Authorization: `Bearer ${accessToken}` },
181
- });
182
- if (resp.ok) {
183
- const body = (await resp.json()) as { data?: { username?: string } };
184
- return body.data?.username ? `@${body.data.username}` : undefined;
185
- }
186
- } catch {
187
- // Non-fatal — identity verification is best-effort
188
- }
189
- return undefined;
190
- },
191
- },
192
- "integration:github": {
193
- service: "integration:github",
194
- loopbackPort: 17332,
195
- injectionTemplates: [
196
- {
197
- hostPattern: "api.github.com",
198
- injectionType: "header",
199
- headerName: "Authorization",
200
- valuePrefix: "Bearer ",
201
- },
202
- ],
203
- setupSkillId: "github-oauth-setup",
204
- setup: {
205
- displayName: "GitHub",
206
- dashboardUrl: "https://github.com/settings/developers",
207
- appType: "OAuth App",
208
- requiresClientSecret: true,
209
- },
210
- identityVerifier: async (
211
- accessToken: string,
212
- ): Promise<string | undefined> => {
213
- try {
214
- const resp = await fetch("https://api.github.com/user", {
215
- headers: { Authorization: `Bearer ${accessToken}` },
216
- });
217
- if (resp.ok) {
218
- const body = (await resp.json()) as { login?: string };
219
- return body.login ? `@${body.login}` : undefined;
220
- }
221
- } catch {
222
- // Non-fatal — identity verification is best-effort
223
- }
224
- return undefined;
225
- },
226
- },
227
-
228
- "integration:linear": {
229
- service: "integration:linear",
230
- loopbackPort: 17324,
231
- injectionTemplates: [
232
- {
233
- hostPattern: "api.linear.app",
234
- injectionType: "header",
235
- headerName: "Authorization",
236
- valuePrefix: "Bearer ",
237
- },
238
- ],
239
- setupSkillId: "linear-oauth-setup",
240
- setup: {
241
- displayName: "Linear",
242
- dashboardUrl: "https://linear.app/settings/api",
243
- appType: "OAuth application",
244
- requiresClientSecret: true,
245
- },
246
- identityVerifier: async (
247
- accessToken: string,
248
- ): Promise<string | undefined> => {
249
- try {
250
- const resp = await fetch("https://api.linear.app/graphql", {
251
- method: "POST",
252
- headers: {
253
- Authorization: `Bearer ${accessToken}`,
254
- "Content-Type": "application/json",
255
- },
256
- body: JSON.stringify({ query: "{ viewer { email name } }" }),
257
- });
258
- if (resp.ok) {
259
- const body = (await resp.json()) as {
260
- data?: { viewer?: { email?: string; name?: string } };
261
- };
262
- return body.data?.viewer?.email ?? body.data?.viewer?.name;
263
- }
264
- } catch {
265
- // Non-fatal — identity verification is best-effort
266
- }
267
- return undefined;
268
- },
269
- },
270
-
271
- "integration:spotify": {
272
- service: "integration:spotify",
273
- loopbackPort: 17333,
274
- injectionTemplates: [
275
- {
276
- hostPattern: "api.spotify.com",
277
- injectionType: "header",
278
- headerName: "Authorization",
279
- valuePrefix: "Bearer ",
280
- },
281
- ],
282
- setupSkillId: "spotify-oauth-setup",
283
- setup: {
284
- displayName: "Spotify",
285
- dashboardUrl: "https://developer.spotify.com/dashboard",
286
- appType: "App",
287
- requiresClientSecret: true,
288
- },
289
- identityVerifier: async (
290
- accessToken: string,
291
- ): Promise<string | undefined> => {
292
- try {
293
- const resp = await fetch("https://api.spotify.com/v1/me", {
294
- headers: { Authorization: `Bearer ${accessToken}` },
295
- });
296
- if (resp.ok) {
297
- const body = (await resp.json()) as {
298
- display_name?: string;
299
- email?: string;
300
- };
301
- return body.display_name ?? body.email;
302
- }
303
- } catch {
304
- // Non-fatal — identity verification is best-effort
305
- }
306
- return undefined;
307
- },
308
- },
309
-
310
- "integration:todoist": {
311
- service: "integration:todoist",
312
- loopbackPort: 17325,
313
- injectionTemplates: [
314
- {
315
- hostPattern: "api.todoist.com",
316
- injectionType: "header",
317
- headerName: "Authorization",
318
- valuePrefix: "Bearer ",
319
- },
320
- ],
321
- setupSkillId: "todoist-oauth-setup",
322
- setup: {
323
- displayName: "Todoist",
324
- dashboardUrl: "https://developer.todoist.com/appconsole.html",
325
- appType: "App",
326
- requiresClientSecret: true,
327
- },
328
- identityVerifier: async (
329
- accessToken: string,
330
- ): Promise<string | undefined> => {
331
- try {
332
- const resp = await fetch("https://api.todoist.com/sync/v9/sync", {
333
- method: "POST",
334
- headers: {
335
- Authorization: `Bearer ${accessToken}`,
336
- "Content-Type": "application/x-www-form-urlencoded",
337
- },
338
- body: "sync_token=*&resource_types=[%22user%22]",
339
- });
340
- if (resp.ok) {
341
- const body = (await resp.json()) as {
342
- user?: { email?: string; full_name?: string };
343
- };
344
- return body.user?.full_name ?? body.user?.email;
345
- }
346
- } catch {
347
- // Non-fatal — identity verification is best-effort
348
- }
349
- return undefined;
350
- },
351
- },
352
-
353
- "integration:discord": {
354
- service: "integration:discord",
355
- loopbackPort: 17326,
356
- injectionTemplates: [
357
- {
358
- hostPattern: "discord.com",
359
- injectionType: "header",
360
- headerName: "Authorization",
361
- valuePrefix: "Bearer ",
362
- },
363
- ],
364
- setupSkillId: "discord-oauth-setup",
365
- setup: {
366
- displayName: "Discord",
367
- dashboardUrl: "https://discord.com/developers/applications",
368
- appType: "Application",
369
- requiresClientSecret: true,
370
- },
371
- identityVerifier: async (
372
- accessToken: string,
373
- ): Promise<string | undefined> => {
374
- try {
375
- const resp = await fetch("https://discord.com/api/v10/users/@me", {
376
- headers: { Authorization: `Bearer ${accessToken}` },
377
- });
378
- if (resp.ok) {
379
- const body = (await resp.json()) as {
380
- username?: string;
381
- global_name?: string;
382
- };
383
- return body.global_name ?? body.username;
384
- }
385
- } catch {
386
- // Non-fatal — identity verification is best-effort
387
- }
388
- return undefined;
389
- },
390
- },
391
-
392
- "integration:dropbox": {
393
- service: "integration:dropbox",
394
- loopbackPort: 17327,
395
- injectionTemplates: [
396
- {
397
- hostPattern: "api.dropboxapi.com",
398
- injectionType: "header",
399
- headerName: "Authorization",
400
- valuePrefix: "Bearer ",
401
- },
402
- {
403
- hostPattern: "content.dropboxapi.com",
404
- injectionType: "header",
405
- headerName: "Authorization",
406
- valuePrefix: "Bearer ",
407
- },
408
- ],
409
- setupSkillId: "dropbox-oauth-setup",
410
- setup: {
411
- displayName: "Dropbox",
412
- dashboardUrl: "https://www.dropbox.com/developers/apps",
413
- appType: "Scoped access app",
414
- requiresClientSecret: true,
415
- },
416
- identityVerifier: async (
417
- accessToken: string,
418
- ): Promise<string | undefined> => {
419
- try {
420
- const resp = await fetch(
421
- "https://api.dropboxapi.com/2/users/get_current_account",
422
- {
423
- method: "POST",
424
- headers: { Authorization: `Bearer ${accessToken}` },
425
- },
426
- );
427
- if (resp.ok) {
428
- const body = (await resp.json()) as {
429
- name?: { display_name?: string };
430
- email?: string;
431
- };
432
- return body.name?.display_name ?? body.email;
433
- }
434
- } catch {
435
- // Non-fatal — identity verification is best-effort
436
- }
437
- return undefined;
438
- },
439
- },
440
-
441
- "integration:asana": {
442
- service: "integration:asana",
443
- loopbackPort: 17328,
444
- injectionTemplates: [
445
- {
446
- hostPattern: "app.asana.com",
447
- injectionType: "header",
448
- headerName: "Authorization",
449
- valuePrefix: "Bearer ",
450
- },
451
- ],
452
- setupSkillId: "asana-oauth-setup",
453
- setup: {
454
- displayName: "Asana",
455
- dashboardUrl: "https://app.asana.com/0/my-apps",
456
- appType: "App",
457
- requiresClientSecret: true,
458
- },
459
- identityVerifier: async (
460
- accessToken: string,
461
- ): Promise<string | undefined> => {
462
- try {
463
- const resp = await fetch("https://app.asana.com/api/1.0/users/me", {
464
- headers: { Authorization: `Bearer ${accessToken}` },
465
- });
466
- if (resp.ok) {
467
- const body = (await resp.json()) as {
468
- data?: { name?: string; email?: string };
469
- };
470
- return body.data?.name ?? body.data?.email;
471
- }
472
- } catch {
473
- // Non-fatal — identity verification is best-effort
474
- }
475
- return undefined;
476
- },
477
- },
478
-
479
- "integration:airtable": {
480
- service: "integration:airtable",
481
- loopbackPort: 17329,
482
- injectionTemplates: [
483
- {
484
- hostPattern: "api.airtable.com",
485
- injectionType: "header",
486
- headerName: "Authorization",
487
- valuePrefix: "Bearer ",
488
- },
489
- ],
490
- setupSkillId: "airtable-oauth-setup",
491
- setup: {
492
- displayName: "Airtable",
493
- dashboardUrl: "https://airtable.com/create/oauth",
494
- appType: "OAuth integration",
495
- requiresClientSecret: true,
496
- },
497
- identityVerifier: async (
498
- accessToken: string,
499
- ): Promise<string | undefined> => {
500
- try {
501
- const resp = await fetch("https://api.airtable.com/v0/meta/whoami", {
502
- headers: { Authorization: `Bearer ${accessToken}` },
503
- });
504
- if (resp.ok) {
505
- const body = (await resp.json()) as { email?: string };
506
- return body.email;
507
- }
508
- } catch {
509
- // Non-fatal — identity verification is best-effort
510
- }
511
- return undefined;
512
- },
513
- },
514
-
515
- "integration:hubspot": {
516
- service: "integration:hubspot",
517
- loopbackPort: 17330,
518
- injectionTemplates: [
519
- {
520
- hostPattern: "api.hubapi.com",
521
- injectionType: "header",
522
- headerName: "Authorization",
523
- valuePrefix: "Bearer ",
524
- },
525
- ],
526
- setupSkillId: "hubspot-oauth-setup",
527
- setup: {
528
- displayName: "HubSpot",
529
- dashboardUrl: "https://app.hubspot.com/developer",
530
- appType: "App",
531
- requiresClientSecret: true,
532
- },
533
- identityVerifier: async (
534
- accessToken: string,
535
- ): Promise<string | undefined> => {
536
- try {
537
- const resp = await fetch(
538
- "https://api.hubapi.com/oauth/v1/access-tokens/" + accessToken,
539
- );
540
- if (resp.ok) {
541
- const body = (await resp.json()) as {
542
- user?: string;
543
- hub_domain?: string;
544
- };
545
- return body.user ?? body.hub_domain;
546
- }
547
- } catch {
548
- // Non-fatal — identity verification is best-effort
549
- }
550
- return undefined;
551
- },
552
- },
553
-
554
- "integration:figma": {
555
- service: "integration:figma",
556
- loopbackPort: 17331,
557
- injectionTemplates: [
558
- {
559
- hostPattern: "api.figma.com",
560
- injectionType: "header",
561
- headerName: "Authorization",
562
- valuePrefix: "Bearer ",
563
- },
564
- ],
565
- setupSkillId: "figma-oauth-setup",
566
- setup: {
567
- displayName: "Figma",
568
- dashboardUrl: "https://www.figma.com/developers/apps",
569
- appType: "App",
570
- requiresClientSecret: true,
571
- },
572
- identityVerifier: async (
573
- accessToken: string,
574
- ): Promise<string | undefined> => {
575
- try {
576
- const resp = await fetch("https://api.figma.com/v1/me", {
577
- headers: { Authorization: `Bearer ${accessToken}` },
578
- });
579
- if (resp.ok) {
580
- const body = (await resp.json()) as {
581
- handle?: string;
582
- email?: string;
583
- };
584
- return body.handle ?? body.email;
585
- }
586
- } catch {
587
- // Non-fatal — identity verification is best-effort
588
- }
589
- return undefined;
590
- },
591
- },
592
- };
593
-
594
- // ---------------------------------------------------------------------------
595
- // Aliases & resolution
596
- // ---------------------------------------------------------------------------
597
-
598
- /** Map shorthand aliases to canonical service names. */
599
- export const SERVICE_ALIASES: Record<string, string> = {
600
- gmail: "integration:google",
601
- google: "integration:google",
602
- slack: "integration:slack",
603
- notion: "integration:notion",
604
- twitter: "integration:twitter",
605
- github: "integration:github",
606
- linear: "integration:linear",
607
- spotify: "integration:spotify",
608
- todoist: "integration:todoist",
609
- discord: "integration:discord",
610
- dropbox: "integration:dropbox",
611
- asana: "integration:asana",
612
- airtable: "integration:airtable",
613
- hubspot: "integration:hubspot",
614
- figma: "integration:figma",
615
- };
616
-
617
- /**
618
- * Resolve a service name through aliases, then fall back to `integration:`
619
- * prefix for providers registered in PROVIDER_BEHAVIORS without a
620
- * SERVICE_ALIASES entry.
621
- */
622
- export function resolveService(service: string): string {
623
- if (SERVICE_ALIASES[service]) return SERVICE_ALIASES[service];
624
- if (!service.includes(":") && PROVIDER_BEHAVIORS[`integration:${service}`])
625
- return `integration:${service}`;
626
- return service;
627
- }
628
-
629
- /** Look up a provider behavior by canonical service name. */
630
- export function getProviderBehavior(
631
- service: string,
632
- ): OAuthProviderBehavior | undefined {
633
- return PROVIDER_BEHAVIORS[service];
634
- }