@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
@@ -6,16 +6,15 @@ import { seedProviders } from "./oauth-store.js";
6
6
  * These values are upserted into the `oauth_providers` SQLite table on
7
7
  * every startup. Only Vellum implementation fields (authUrl, tokenUrl,
8
8
  * tokenEndpointAuthMethod, userinfoUrl, extraParams, callbackTransport,
9
- * pingUrl, pingMethod, pingHeaders, pingBody, managedServiceConfigKey)
9
+ * pingUrl, pingMethod, pingHeaders, pingBody, managedServiceConfigKey,
10
+ * loopbackPort, injectionTemplates, appType, setupNotes,
11
+ * identityUrl, identityMethod, identityHeaders, identityBody,
12
+ * identityResponsePaths, identityFormat, identityOkField)
10
13
  * and display metadata (displayName,
11
14
  * description, dashboardUrl, clientIdPlaceholder, requiresClientSecret)
12
15
  * are overwritten on subsequent startups — user-customizable
13
- * fields (defaultScopes, scopePolicy, baseUrl) are only
16
+ * fields (defaultScopes, scopePolicy) are only
14
17
  * written on initial insert and preserved across restarts.
15
- *
16
- * Code-side behavioral fields (identityVerifier, injectionTemplates,
17
- * setup, etc.) live in `provider-behaviors.ts` and are never persisted
18
- * to the DB.
19
18
  */
20
19
  const PROVIDER_SEED_DATA: Record<
21
20
  string,
@@ -44,10 +43,26 @@ const PROVIDER_SEED_DATA: Record<
44
43
  dashboardUrl: string | null;
45
44
  clientIdPlaceholder: string | null;
46
45
  requiresClientSecret?: boolean;
46
+ loopbackPort?: number;
47
+ injectionTemplates?: Array<{
48
+ hostPattern: string;
49
+ injectionType: string;
50
+ headerName: string;
51
+ valuePrefix: string;
52
+ }>;
53
+ appType?: string;
54
+ setupNotes?: string[];
55
+ identityUrl?: string;
56
+ identityMethod?: string;
57
+ identityHeaders?: Record<string, string>;
58
+ identityBody?: unknown;
59
+ identityResponsePaths?: string[];
60
+ identityFormat?: string;
61
+ identityOkField?: string;
47
62
  }
48
63
  > = {
49
- "integration:google": {
50
- providerKey: "integration:google",
64
+ google: {
65
+ providerKey: "google",
51
66
  authUrl: "https://accounts.google.com/o/oauth2/v2/auth",
52
67
  tokenUrl: "https://oauth2.googleapis.com/token",
53
68
  userinfoUrl: "https://www.googleapis.com/oauth2/v2/userinfo",
@@ -77,10 +92,33 @@ const PROVIDER_SEED_DATA: Record<
77
92
  extraParams: { access_type: "offline", prompt: "consent" },
78
93
  callbackTransport: "loopback",
79
94
  managedServiceConfigKey: "google-oauth",
95
+ injectionTemplates: [
96
+ {
97
+ hostPattern: "gmail.googleapis.com",
98
+ injectionType: "header",
99
+ headerName: "Authorization",
100
+ valuePrefix: "Bearer ",
101
+ },
102
+ {
103
+ hostPattern: "www.googleapis.com",
104
+ injectionType: "header",
105
+ headerName: "Authorization",
106
+ valuePrefix: "Bearer ",
107
+ },
108
+ {
109
+ hostPattern: "people.googleapis.com",
110
+ injectionType: "header",
111
+ headerName: "Authorization",
112
+ valuePrefix: "Bearer ",
113
+ },
114
+ ],
115
+ appType: "Desktop app",
116
+ identityUrl: "https://www.googleapis.com/oauth2/v2/userinfo",
117
+ identityResponsePaths: ["email"],
80
118
  },
81
119
 
82
- "integration:slack": {
83
- providerKey: "integration:slack",
120
+ slack: {
121
+ providerKey: "slack",
84
122
  authUrl: "https://slack.com/oauth/v2/authorize",
85
123
  tokenUrl: "https://slack.com/api/oauth.v2.access",
86
124
  pingUrl: "https://slack.com/api/auth.test",
@@ -114,10 +152,24 @@ const PROVIDER_SEED_DATA: Record<
114
152
  "channels:read,channels:history,groups:read,groups:history,im:read,im:history,im:write,mpim:read,mpim:history,users:read,chat:write,search:read,reactions:write",
115
153
  },
116
154
  callbackTransport: "loopback",
155
+ loopbackPort: 17322,
156
+ injectionTemplates: [
157
+ {
158
+ hostPattern: "slack.com",
159
+ injectionType: "header",
160
+ headerName: "Authorization",
161
+ valuePrefix: "Bearer ",
162
+ },
163
+ ],
164
+ appType: "Slack App",
165
+ identityUrl: "https://slack.com/api/auth.test",
166
+ identityOkField: "ok",
167
+ identityResponsePaths: ["user", "team"],
168
+ identityFormat: "@${user} (${team})",
117
169
  },
118
170
 
119
- "integration:notion": {
120
- providerKey: "integration:notion",
171
+ notion: {
172
+ providerKey: "notion",
121
173
  authUrl: "https://api.notion.com/v1/oauth/authorize",
122
174
  tokenUrl: "https://api.notion.com/v1/oauth/token",
123
175
  pingUrl: "https://api.notion.com/v1/users/me",
@@ -136,10 +188,23 @@ const PROVIDER_SEED_DATA: Record<
136
188
  extraParams: { owner: "user" },
137
189
  tokenEndpointAuthMethod: "client_secret_basic",
138
190
  callbackTransport: "loopback",
191
+ loopbackPort: 17323,
192
+ injectionTemplates: [
193
+ {
194
+ hostPattern: "api.notion.com",
195
+ injectionType: "header",
196
+ headerName: "Authorization",
197
+ valuePrefix: "Bearer ",
198
+ },
199
+ ],
200
+ appType: "Public integration",
201
+ identityUrl: "https://api.notion.com/v1/users/me",
202
+ identityHeaders: { "Notion-Version": "2022-06-28" },
203
+ identityResponsePaths: ["name", "person.email"],
139
204
  },
140
205
 
141
- "integration:twitter": {
142
- providerKey: "integration:twitter",
206
+ twitter: {
207
+ providerKey: "twitter",
143
208
  authUrl: "https://twitter.com/i/oauth2/authorize",
144
209
  tokenUrl: "https://api.x.com/2/oauth2/token",
145
210
  pingUrl: "https://api.x.com/2/users/me",
@@ -161,10 +226,22 @@ const PROVIDER_SEED_DATA: Record<
161
226
  },
162
227
  tokenEndpointAuthMethod: "client_secret_basic",
163
228
  callbackTransport: "gateway",
229
+ injectionTemplates: [
230
+ {
231
+ hostPattern: "api.x.com",
232
+ injectionType: "header",
233
+ headerName: "Authorization",
234
+ valuePrefix: "Bearer ",
235
+ },
236
+ ],
237
+ appType: "App",
238
+ identityUrl: "https://api.x.com/2/users/me",
239
+ identityResponsePaths: ["data.username"],
240
+ identityFormat: "@${data.username}",
164
241
  },
165
242
 
166
- "integration:github": {
167
- providerKey: "integration:github",
243
+ github: {
244
+ providerKey: "github",
168
245
  authUrl: "https://github.com/login/oauth/authorize",
169
246
  tokenUrl: "https://github.com/login/oauth/access_token",
170
247
  pingUrl: "https://api.github.com/user",
@@ -185,10 +262,23 @@ const PROVIDER_SEED_DATA: Record<
185
262
  forbiddenScopes: ["delete_repo", "admin:org"],
186
263
  },
187
264
  callbackTransport: "loopback",
265
+ loopbackPort: 17332,
266
+ injectionTemplates: [
267
+ {
268
+ hostPattern: "api.github.com",
269
+ injectionType: "header",
270
+ headerName: "Authorization",
271
+ valuePrefix: "Bearer ",
272
+ },
273
+ ],
274
+ appType: "OAuth App",
275
+ identityUrl: "https://api.github.com/user",
276
+ identityResponsePaths: ["login"],
277
+ identityFormat: "@${login}",
188
278
  },
189
279
 
190
- "integration:linear": {
191
- providerKey: "integration:linear",
280
+ linear: {
281
+ providerKey: "linear",
192
282
  authUrl: "https://linear.app/oauth/authorize",
193
283
  tokenUrl: "https://api.linear.app/oauth/token",
194
284
  pingUrl: "https://api.linear.app/graphql",
@@ -208,10 +298,25 @@ const PROVIDER_SEED_DATA: Record<
208
298
  },
209
299
  extraParams: { prompt: "consent" },
210
300
  callbackTransport: "loopback",
301
+ loopbackPort: 17324,
302
+ injectionTemplates: [
303
+ {
304
+ hostPattern: "api.linear.app",
305
+ injectionType: "header",
306
+ headerName: "Authorization",
307
+ valuePrefix: "Bearer ",
308
+ },
309
+ ],
310
+ appType: "OAuth application",
311
+ identityUrl: "https://api.linear.app/graphql",
312
+ identityMethod: "POST",
313
+ identityHeaders: { "Content-Type": "application/json" },
314
+ identityBody: { query: "{ viewer { email name } }" },
315
+ identityResponsePaths: ["data.viewer.email", "data.viewer.name"],
211
316
  },
212
317
 
213
- "integration:spotify": {
214
- providerKey: "integration:spotify",
318
+ spotify: {
319
+ providerKey: "spotify",
215
320
  authUrl: "https://accounts.spotify.com/authorize",
216
321
  tokenUrl: "https://accounts.spotify.com/api/token",
217
322
  pingUrl: "https://api.spotify.com/v1/me",
@@ -238,10 +343,22 @@ const PROVIDER_SEED_DATA: Record<
238
343
  },
239
344
  tokenEndpointAuthMethod: "client_secret_basic",
240
345
  callbackTransport: "loopback",
346
+ loopbackPort: 17333,
347
+ injectionTemplates: [
348
+ {
349
+ hostPattern: "api.spotify.com",
350
+ injectionType: "header",
351
+ headerName: "Authorization",
352
+ valuePrefix: "Bearer ",
353
+ },
354
+ ],
355
+ appType: "App",
356
+ identityUrl: "https://api.spotify.com/v1/me",
357
+ identityResponsePaths: ["display_name", "email"],
241
358
  },
242
359
 
243
- "integration:todoist": {
244
- providerKey: "integration:todoist",
360
+ todoist: {
361
+ providerKey: "todoist",
245
362
  authUrl: "https://todoist.com/oauth/authorize",
246
363
  tokenUrl: "https://todoist.com/oauth/access_token",
247
364
  pingUrl: "https://api.todoist.com/rest/v2/projects",
@@ -257,10 +374,25 @@ const PROVIDER_SEED_DATA: Record<
257
374
  forbiddenScopes: ["data:delete"],
258
375
  },
259
376
  callbackTransport: "loopback",
377
+ loopbackPort: 17325,
378
+ injectionTemplates: [
379
+ {
380
+ hostPattern: "api.todoist.com",
381
+ injectionType: "header",
382
+ headerName: "Authorization",
383
+ valuePrefix: "Bearer ",
384
+ },
385
+ ],
386
+ appType: "App",
387
+ identityUrl: "https://api.todoist.com/sync/v9/sync",
388
+ identityMethod: "POST",
389
+ identityHeaders: { "Content-Type": "application/x-www-form-urlencoded" },
390
+ identityBody: "sync_token=*&resource_types=[%22user%22]",
391
+ identityResponsePaths: ["user.full_name", "user.email"],
260
392
  },
261
393
 
262
- "integration:discord": {
263
- providerKey: "integration:discord",
394
+ discord: {
395
+ providerKey: "discord",
264
396
  authUrl: "https://discord.com/oauth2/authorize",
265
397
  tokenUrl: "https://discord.com/api/v10/oauth2/token",
266
398
  pingUrl: "https://discord.com/api/v10/users/@me",
@@ -281,10 +413,22 @@ const PROVIDER_SEED_DATA: Record<
281
413
  forbiddenScopes: [],
282
414
  },
283
415
  callbackTransport: "loopback",
416
+ loopbackPort: 17326,
417
+ injectionTemplates: [
418
+ {
419
+ hostPattern: "discord.com",
420
+ injectionType: "header",
421
+ headerName: "Authorization",
422
+ valuePrefix: "Bearer ",
423
+ },
424
+ ],
425
+ appType: "Application",
426
+ identityUrl: "https://discord.com/api/v10/users/@me",
427
+ identityResponsePaths: ["global_name", "username"],
284
428
  },
285
429
 
286
- "integration:dropbox": {
287
- providerKey: "integration:dropbox",
430
+ dropbox: {
431
+ providerKey: "dropbox",
288
432
  authUrl: "https://www.dropbox.com/oauth2/authorize",
289
433
  tokenUrl: "https://api.dropboxapi.com/oauth2/token",
290
434
  pingUrl: "https://api.dropboxapi.com/2/users/get_current_account",
@@ -307,10 +451,29 @@ const PROVIDER_SEED_DATA: Record<
307
451
  },
308
452
  extraParams: { token_access_type: "offline" },
309
453
  callbackTransport: "loopback",
454
+ loopbackPort: 17327,
455
+ injectionTemplates: [
456
+ {
457
+ hostPattern: "api.dropboxapi.com",
458
+ injectionType: "header",
459
+ headerName: "Authorization",
460
+ valuePrefix: "Bearer ",
461
+ },
462
+ {
463
+ hostPattern: "content.dropboxapi.com",
464
+ injectionType: "header",
465
+ headerName: "Authorization",
466
+ valuePrefix: "Bearer ",
467
+ },
468
+ ],
469
+ appType: "Scoped access app",
470
+ identityUrl: "https://api.dropboxapi.com/2/users/get_current_account",
471
+ identityMethod: "POST",
472
+ identityResponsePaths: ["name.display_name", "email"],
310
473
  },
311
474
 
312
- "integration:asana": {
313
- providerKey: "integration:asana",
475
+ asana: {
476
+ providerKey: "asana",
314
477
  authUrl: "https://app.asana.com/-/oauth_authorize",
315
478
  tokenUrl: "https://app.asana.com/-/oauth_token",
316
479
  pingUrl: "https://app.asana.com/api/1.0/users/me",
@@ -326,10 +489,22 @@ const PROVIDER_SEED_DATA: Record<
326
489
  forbiddenScopes: [],
327
490
  },
328
491
  callbackTransport: "loopback",
492
+ loopbackPort: 17328,
493
+ injectionTemplates: [
494
+ {
495
+ hostPattern: "app.asana.com",
496
+ injectionType: "header",
497
+ headerName: "Authorization",
498
+ valuePrefix: "Bearer ",
499
+ },
500
+ ],
501
+ appType: "App",
502
+ identityUrl: "https://app.asana.com/api/1.0/users/me",
503
+ identityResponsePaths: ["data.name", "data.email"],
329
504
  },
330
505
 
331
- "integration:airtable": {
332
- providerKey: "integration:airtable",
506
+ airtable: {
507
+ providerKey: "airtable",
333
508
  authUrl: "https://airtable.com/oauth2/v1/authorize",
334
509
  tokenUrl: "https://airtable.com/oauth2/v1/token",
335
510
  pingUrl: "https://api.airtable.com/v0/meta/whoami",
@@ -350,10 +525,22 @@ const PROVIDER_SEED_DATA: Record<
350
525
  },
351
526
  tokenEndpointAuthMethod: "client_secret_basic",
352
527
  callbackTransport: "loopback",
528
+ loopbackPort: 17329,
529
+ injectionTemplates: [
530
+ {
531
+ hostPattern: "api.airtable.com",
532
+ injectionType: "header",
533
+ headerName: "Authorization",
534
+ valuePrefix: "Bearer ",
535
+ },
536
+ ],
537
+ appType: "OAuth integration",
538
+ identityUrl: "https://api.airtable.com/v0/meta/whoami",
539
+ identityResponsePaths: ["email"],
353
540
  },
354
541
 
355
- "integration:hubspot": {
356
- providerKey: "integration:hubspot",
542
+ hubspot: {
543
+ providerKey: "hubspot",
357
544
  authUrl: "https://app.hubspot.com/oauth/authorize",
358
545
  tokenUrl: "https://api.hubapi.com/oauth/v1/token",
359
546
  pingUrl: "https://api.hubapi.com/crm/v3/objects/contacts?limit=1",
@@ -378,10 +565,22 @@ const PROVIDER_SEED_DATA: Record<
378
565
  forbiddenScopes: [],
379
566
  },
380
567
  callbackTransport: "loopback",
568
+ loopbackPort: 17330,
569
+ injectionTemplates: [
570
+ {
571
+ hostPattern: "api.hubapi.com",
572
+ injectionType: "header",
573
+ headerName: "Authorization",
574
+ valuePrefix: "Bearer ",
575
+ },
576
+ ],
577
+ appType: "App",
578
+ identityUrl: "https://api.hubapi.com/oauth/v1/access-tokens/${accessToken}",
579
+ identityResponsePaths: ["user", "hub_domain"],
381
580
  },
382
581
 
383
- "integration:figma": {
384
- providerKey: "integration:figma",
582
+ figma: {
583
+ providerKey: "figma",
385
584
  authUrl: "https://www.figma.com/oauth",
386
585
  tokenUrl: "https://api.figma.com/v1/oauth/token",
387
586
  pingUrl: "https://api.figma.com/v1/me",
@@ -398,6 +597,18 @@ const PROVIDER_SEED_DATA: Record<
398
597
  },
399
598
  tokenEndpointAuthMethod: "client_secret_basic",
400
599
  callbackTransport: "loopback",
600
+ loopbackPort: 17331,
601
+ injectionTemplates: [
602
+ {
603
+ hostPattern: "api.figma.com",
604
+ injectionType: "header",
605
+ headerName: "Authorization",
606
+ valuePrefix: "Bearer ",
607
+ },
608
+ ],
609
+ appType: "App",
610
+ identityUrl: "https://api.figma.com/v1/me",
611
+ identityResponsePaths: ["handle", "email"],
401
612
  },
402
613
 
403
614
  // Manual-token providers: these don't use OAuth2 flows but need provider
@@ -441,6 +652,8 @@ const PROVIDER_SEED_DATA: Record<
441
652
  },
442
653
  };
443
654
 
655
+ export const SEEDED_PROVIDER_KEYS = new Set(Object.keys(PROVIDER_SEED_DATA));
656
+
444
657
  /**
445
658
  * Seed the oauth_providers table with well-known provider configurations.
446
659
  * Uses INSERT … ON CONFLICT DO UPDATE so seed-data corrections propagate
@@ -146,7 +146,6 @@ const LOW_RISK_PROGRAMS = new Set([
146
146
  "tree",
147
147
  "du",
148
148
  "df",
149
- "assistant",
150
149
  ]);
151
150
 
152
151
  // High-risk shell programs / patterns
@@ -200,6 +199,46 @@ const LOW_RISK_GIT_SUBCOMMANDS = new Set([
200
199
  "reflog",
201
200
  ]);
202
201
 
202
+ /**
203
+ * Classify risk for `assistant` CLI subcommands. Multi-word subcommands
204
+ * (e.g. `assistant oauth token`) are matched by walking the positional args.
205
+ */
206
+ function classifyAssistantSubcommand(args: string[]): RiskLevel {
207
+ // `--help` on any subcommand is read-only, always Low risk.
208
+ // Only check args before `--` (option terminator) — after `--`, tokens
209
+ // are positional arguments, not flags.
210
+ const ddIndex = args.indexOf("--");
211
+ const flagArgs = ddIndex === -1 ? args : args.slice(0, ddIndex);
212
+ if (flagArgs.some((a) => a === "--help" || a === "-h")) return RiskLevel.Low;
213
+
214
+ const sub = firstPositionalArg(args);
215
+ if (!sub) return RiskLevel.Low;
216
+
217
+ if (sub === "oauth") {
218
+ const oauthSub = firstPositionalArg(args.slice(args.indexOf(sub) + 1));
219
+ if (oauthSub === "token") return RiskLevel.High;
220
+ if (oauthSub === "mode") {
221
+ // `oauth mode --set` is high risk; bare `oauth mode` (read) is low.
222
+ // Match both `--set value` (two tokens) and `--set=value` (one token).
223
+ if (args.some((a) => a === "--set" || a.startsWith("--set=")))
224
+ return RiskLevel.High;
225
+ return RiskLevel.Low;
226
+ }
227
+ if (oauthSub === "request") return RiskLevel.Medium;
228
+ if (oauthSub === "connect" || oauthSub === "disconnect")
229
+ return RiskLevel.Medium;
230
+ return RiskLevel.Low;
231
+ }
232
+
233
+ if (sub === "credentials") {
234
+ const credSub = firstPositionalArg(args.slice(args.indexOf(sub) + 1));
235
+ if (credSub === "reveal") return RiskLevel.High;
236
+ return RiskLevel.Low;
237
+ }
238
+
239
+ return RiskLevel.Low;
240
+ }
241
+
203
242
  // Commands that wrap another program — the real program appears as the first
204
243
  // non-flag argument. When one of these is the segment program we look through
205
244
  // its args to find the effective program (e.g. `env curl …` → curl).
@@ -222,6 +261,14 @@ const WRAPPER_PROGRAMS = new Set([
222
261
  // value of -u) as the wrapped program instead of `echo`.
223
262
  const ENV_VALUE_FLAGS = new Set(["-u", "--unset", "-C", "--chdir"]);
224
263
 
264
+ // `timeout` flags that consume the next positional argument as their value.
265
+ const TIMEOUT_VALUE_FLAGS = new Set(["-s", "--signal", "-k", "--kill-after"]);
266
+
267
+ // Wrapper programs where the first non-flag positional argument is a
268
+ // configuration value (duration, CPU mask), not the wrapped program name.
269
+ // For these wrappers, the second non-flag positional is the real program.
270
+ const WRAPPER_SKIP_FIRST_POSITIONAL = new Set(["timeout", "taskset"]);
271
+
225
272
  // `git` global flags that consume the next positional argument as their value.
226
273
  // Without this, `git -C status commit` would incorrectly identify `status`
227
274
  // (the directory path) as the subcommand instead of `commit`.
@@ -280,24 +327,66 @@ function isRmOfKnownSafeFile(args: string[]): boolean {
280
327
  *
281
328
  * Handles `env` specially: skips `VAR=value` pairs and value-taking flags
282
329
  * like `-u NAME` and `-C DIR`.
330
+ *
331
+ * Handles `timeout` and `taskset` specially: their first non-flag positional
332
+ * argument is a duration or CPU mask, not the wrapped program. The second
333
+ * non-flag positional is the real program.
283
334
  */
284
335
  function getWrappedProgram(seg: {
285
336
  program: string;
286
337
  args: string[];
287
338
  }): string | undefined {
288
339
  const isEnv = seg.program === "env";
340
+ const isTimeout = seg.program === "timeout";
341
+ const skipFirst = WRAPPER_SKIP_FIRST_POSITIONAL.has(seg.program);
342
+ let skippedFirstPositional = false;
289
343
  for (let i = 0; i < seg.args.length; i++) {
290
344
  const arg = seg.args[i];
291
345
  if (arg.startsWith("-")) {
292
346
  if (isEnv && ENV_VALUE_FLAGS.has(arg)) i++; // skip the value argument
347
+ if (isTimeout && TIMEOUT_VALUE_FLAGS.has(arg)) i++; // skip the value argument
293
348
  continue;
294
349
  }
295
350
  if (isEnv && arg.includes("=")) continue; // skip env VAR=value pairs
351
+ if (skipFirst && !skippedFirstPositional) {
352
+ skippedFirstPositional = true;
353
+ continue; // skip the duration/CPU mask
354
+ }
296
355
  return arg;
297
356
  }
298
357
  return undefined;
299
358
  }
300
359
 
360
+ /**
361
+ * Like `getWrappedProgram`, but also returns the remaining args after the
362
+ * wrapped program name. This allows callers to propagate subcommand-aware
363
+ * classification (e.g. `env assistant oauth token` → classify `oauth token`).
364
+ */
365
+ function getWrappedProgramWithArgs(seg: {
366
+ program: string;
367
+ args: string[];
368
+ }): { program: string; args: string[] } | undefined {
369
+ const isEnv = seg.program === "env";
370
+ const isTimeout = seg.program === "timeout";
371
+ const skipFirst = WRAPPER_SKIP_FIRST_POSITIONAL.has(seg.program);
372
+ let skippedFirstPositional = false;
373
+ for (let i = 0; i < seg.args.length; i++) {
374
+ const arg = seg.args[i];
375
+ if (arg.startsWith("-")) {
376
+ if (isEnv && ENV_VALUE_FLAGS.has(arg)) i++;
377
+ if (isTimeout && TIMEOUT_VALUE_FLAGS.has(arg)) i++;
378
+ continue;
379
+ }
380
+ if (isEnv && arg.includes("=")) continue;
381
+ if (skipFirst && !skippedFirstPositional) {
382
+ skippedFirstPositional = true;
383
+ continue; // skip the duration/CPU mask
384
+ }
385
+ return { program: arg, args: seg.args.slice(i + 1) };
386
+ }
387
+ return undefined;
388
+ }
389
+
301
390
  function getStringField(
302
391
  input: Record<string, unknown>,
303
392
  ...keys: string[]
@@ -731,6 +820,34 @@ async function classifyRiskUncached(
731
820
  maxRisk = RiskLevel.Medium;
732
821
  continue;
733
822
  }
823
+ // Propagate subcommand-aware classification for wrapped git/assistant
824
+ if (wrapped === "git") {
825
+ const wrappedWithArgs = getWrappedProgramWithArgs(seg);
826
+ if (wrappedWithArgs) {
827
+ const subcommand = firstPositionalArg(
828
+ wrappedWithArgs.args,
829
+ GIT_VALUE_FLAGS,
830
+ );
831
+ if (subcommand && LOW_RISK_GIT_SUBCOMMANDS.has(subcommand)) {
832
+ continue;
833
+ }
834
+ maxRisk = RiskLevel.Medium;
835
+ continue;
836
+ }
837
+ }
838
+ if (wrapped === "assistant") {
839
+ const wrappedWithArgs = getWrappedProgramWithArgs(seg);
840
+ if (wrappedWithArgs) {
841
+ const assistantRisk = classifyAssistantSubcommand(
842
+ wrappedWithArgs.args,
843
+ );
844
+ if (assistantRisk === RiskLevel.High) return RiskLevel.High;
845
+ if (assistantRisk === RiskLevel.Medium) {
846
+ maxRisk = RiskLevel.Medium;
847
+ }
848
+ continue;
849
+ }
850
+ }
734
851
  }
735
852
 
736
853
  if (prog === "git") {
@@ -744,6 +861,15 @@ async function classifyRiskUncached(
744
861
  continue;
745
862
  }
746
863
 
864
+ if (prog === "assistant") {
865
+ const assistantRisk = classifyAssistantSubcommand(seg.args);
866
+ if (assistantRisk === RiskLevel.High) return RiskLevel.High;
867
+ if (assistantRisk === RiskLevel.Medium) {
868
+ maxRisk = RiskLevel.Medium;
869
+ }
870
+ continue;
871
+ }
872
+
747
873
  if (!LOW_RISK_PROGRAMS.has(prog)) {
748
874
  // Unknown program → medium
749
875
  if (maxRisk === RiskLevel.Low) {
@@ -78,7 +78,10 @@ export function buildJournalContext(
78
78
 
79
79
  // Filter for .md files, excluding README.md (case-insensitive)
80
80
  const mdFiles = files.filter(
81
- (f) => f.endsWith(".md") && f.toLowerCase() !== "readme.md",
81
+ (f) =>
82
+ f.endsWith(".md") &&
83
+ !f.startsWith(".") &&
84
+ f.toLowerCase() !== "readme.md",
82
85
  );
83
86
 
84
87
  // Collect file info with birthtime (creation time), skipping unreadable entries