@vellumai/assistant 0.4.48 → 0.4.49

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 (252) hide show
  1. package/ARCHITECTURE.md +2 -2
  2. package/README.md +2 -23
  3. package/docs/architecture/integrations.md +45 -41
  4. package/docs/architecture/keychain-broker.md +3 -3
  5. package/docs/runbook-trusted-contacts.md +3 -8
  6. package/hook-templates/debug-prompt-logger/hook.json +1 -1
  7. package/hook-templates/debug-prompt-logger/run.sh +1 -3
  8. package/package.json +1 -1
  9. package/src/__tests__/actor-token-service.test.ts +0 -1
  10. package/src/__tests__/anthropic-provider.test.ts +156 -0
  11. package/src/__tests__/approval-cascade.test.ts +810 -0
  12. package/src/__tests__/approval-primitive.test.ts +0 -1
  13. package/src/__tests__/approval-routes-http.test.ts +2 -0
  14. package/src/__tests__/assistant-attachments.test.ts +12 -34
  15. package/src/__tests__/assistant-feature-flag-guardrails.test.ts +76 -0
  16. package/src/__tests__/assistant-feature-flags-integration.test.ts +0 -1
  17. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +2 -2
  18. package/src/__tests__/channel-guardian.test.ts +0 -2
  19. package/src/__tests__/channel-readiness-routes.test.ts +15 -6
  20. package/src/__tests__/channel-readiness-service.test.ts +10 -9
  21. package/src/__tests__/checker.test.ts +9 -29
  22. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +1 -1
  23. package/src/__tests__/computer-use-tools.test.ts +2 -19
  24. package/src/__tests__/config-watcher.test.ts +0 -1
  25. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +0 -1
  26. package/src/__tests__/context-image-dimensions.test.ts +332 -0
  27. package/src/__tests__/context-token-estimator.test.ts +196 -13
  28. package/src/__tests__/conversation-attention-store.test.ts +0 -1
  29. package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
  30. package/src/__tests__/conversation-routes-guardian-reply.test.ts +144 -0
  31. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
  32. package/src/__tests__/credential-metadata-store.test.ts +64 -73
  33. package/src/__tests__/credential-security-invariants.test.ts +13 -7
  34. package/src/__tests__/credential-vault-unit.test.ts +280 -49
  35. package/src/__tests__/credential-vault.test.ts +138 -16
  36. package/src/__tests__/credentials-cli.test.ts +71 -0
  37. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
  38. package/src/__tests__/ephemeral-permissions.test.ts +3 -3
  39. package/src/__tests__/gateway-only-guard.test.ts +0 -1
  40. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -1
  41. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +0 -1
  42. package/src/__tests__/guardian-routing-invariants.test.ts +0 -1
  43. package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -1
  44. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +0 -39
  45. package/src/__tests__/heartbeat-service.test.ts +0 -1
  46. package/src/__tests__/host-cu-proxy.test.ts +629 -0
  47. package/src/__tests__/host-shell-tool.test.ts +27 -15
  48. package/src/__tests__/http-user-message-parity.test.ts +1 -0
  49. package/src/__tests__/ingress-url-consistency.test.ts +14 -21
  50. package/src/__tests__/integration-status.test.ts +32 -51
  51. package/src/__tests__/intent-routing.test.ts +0 -1
  52. package/src/__tests__/invite-routes-http.test.ts +10 -9
  53. package/src/__tests__/keychain-broker-client.test.ts +11 -43
  54. package/src/__tests__/notification-routing-intent.test.ts +0 -1
  55. package/src/__tests__/oauth-cli.test.ts +373 -14
  56. package/src/__tests__/oauth-provider-profiles.test.ts +9 -9
  57. package/src/__tests__/oauth-scope-policy.test.ts +4 -6
  58. package/src/__tests__/oauth-store.test.ts +756 -0
  59. package/src/__tests__/onboarding-starter-tasks.test.ts +0 -1
  60. package/src/__tests__/provider-error-scenarios.test.ts +0 -1
  61. package/src/__tests__/provider-streaming.benchmark.test.ts +0 -1
  62. package/src/__tests__/public-ingress-urls.test.ts +15 -21
  63. package/src/__tests__/recording-handler.test.ts +3 -4
  64. package/src/__tests__/registry.test.ts +2 -2
  65. package/src/__tests__/runtime-events-sse.test.ts +55 -7
  66. package/src/__tests__/schedule-store.test.ts +0 -1
  67. package/src/__tests__/scheduler-recurrence.test.ts +0 -1
  68. package/src/__tests__/scoped-approval-grants.test.ts +0 -1
  69. package/src/__tests__/scoped-grant-security-matrix.test.ts +0 -1
  70. package/src/__tests__/secret-ingress-handler.test.ts +0 -1
  71. package/src/__tests__/send-endpoint-busy.test.ts +21 -6
  72. package/src/__tests__/sequence-store.test.ts +0 -1
  73. package/src/__tests__/session-init.benchmark.test.ts +4 -5
  74. package/src/__tests__/skill-include-graph.test.ts +66 -0
  75. package/src/__tests__/skill-load-feature-flag.test.ts +0 -1
  76. package/src/__tests__/skill-load-tool.test.ts +149 -1
  77. package/src/__tests__/skill-projection-feature-flag.test.ts +0 -1
  78. package/src/__tests__/skills-uninstall.test.ts +1 -1
  79. package/src/__tests__/skills.test.ts +3 -3
  80. package/src/__tests__/slack-channel-config.test.ts +67 -3
  81. package/src/__tests__/slack-share-routes.test.ts +17 -19
  82. package/src/__tests__/system-prompt.test.ts +0 -1
  83. package/src/__tests__/telegram-invite-adapter.test.ts +18 -22
  84. package/src/__tests__/terminal-tools.test.ts +4 -3
  85. package/src/__tests__/test-support/computer-use-skill-harness.ts +3 -2
  86. package/src/__tests__/tool-approval-handler.test.ts +0 -1
  87. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -1
  88. package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -1
  89. package/src/__tests__/tool-executor-shell-integration.test.ts +0 -1
  90. package/src/__tests__/tool-executor.test.ts +0 -1
  91. package/src/__tests__/tool-grant-request-escalation.test.ts +0 -1
  92. package/src/__tests__/trust-store-pattern-matches.test.ts +29 -0
  93. package/src/__tests__/trust-store.test.ts +1 -22
  94. package/src/__tests__/trusted-contact-approval-notifier.test.ts +0 -1
  95. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +0 -1
  96. package/src/__tests__/twilio-routes.test.ts +0 -16
  97. package/src/__tests__/verification-control-plane-policy.test.ts +0 -1
  98. package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -1
  99. package/src/agent/ax-tree-compaction.test.ts +235 -0
  100. package/src/agent/loop.ts +76 -130
  101. package/src/calls/call-domain.ts +1 -6
  102. package/src/calls/relay-server.ts +9 -13
  103. package/src/calls/twilio-config.ts +2 -7
  104. package/src/calls/twilio-routes.ts +1 -2
  105. package/src/calls/voice-ingress-preflight.ts +1 -1
  106. package/src/cli/commands/browser-relay.ts +18 -12
  107. package/src/cli/commands/completions.ts +0 -3
  108. package/src/cli/commands/credentials.ts +101 -15
  109. package/src/cli/commands/oauth/apps.ts +255 -0
  110. package/src/cli/commands/oauth/connections.ts +299 -0
  111. package/src/cli/commands/oauth/index.ts +52 -0
  112. package/src/cli/commands/oauth/providers.ts +242 -0
  113. package/src/cli/commands/skills.ts +4 -338
  114. package/src/cli/program.ts +1 -5
  115. package/src/cli/reference.ts +1 -3
  116. package/src/config/assistant-feature-flags.ts +0 -3
  117. package/src/config/bundled-skills/_shared/CLI_RETRIEVAL_PATTERN.md +1 -1
  118. package/src/config/bundled-skills/computer-use/SKILL.md +3 -6
  119. package/src/config/bundled-skills/computer-use/TOOLS.json +22 -4
  120. package/src/config/bundled-skills/google-calendar/calendar-client.ts +21 -16
  121. package/src/config/bundled-skills/messaging/tools/shared.ts +1 -4
  122. package/src/config/bundled-skills/settings/SKILL.md +1 -1
  123. package/src/config/bundled-skills/settings/TOOLS.json +2 -8
  124. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +5 -33
  125. package/src/config/env-registry.ts +14 -83
  126. package/src/config/env.ts +11 -50
  127. package/src/config/feature-flag-registry.json +16 -16
  128. package/src/config/loader.ts +0 -6
  129. package/src/config/schema.ts +3 -1
  130. package/src/config/skills.ts +21 -2
  131. package/src/context/image-dimensions.ts +229 -0
  132. package/src/context/token-estimator.ts +75 -12
  133. package/src/context/window-manager.ts +49 -10
  134. package/src/daemon/assistant-attachments.ts +1 -13
  135. package/src/daemon/handlers/config-ingress.ts +8 -33
  136. package/src/daemon/handlers/config-slack-channel.ts +49 -46
  137. package/src/daemon/handlers/config-telegram.ts +32 -16
  138. package/src/daemon/handlers/sessions.ts +10 -24
  139. package/src/daemon/handlers/shared.ts +0 -130
  140. package/src/daemon/host-cu-proxy.ts +401 -0
  141. package/src/daemon/lifecycle.ts +36 -68
  142. package/src/daemon/message-protocol.ts +3 -0
  143. package/src/daemon/message-types/computer-use.ts +2 -119
  144. package/src/daemon/message-types/host-cu.ts +19 -0
  145. package/src/daemon/message-types/messages.ts +3 -0
  146. package/src/daemon/server.ts +14 -21
  147. package/src/daemon/session-agent-loop-handlers.ts +2 -0
  148. package/src/daemon/session-attachments.ts +1 -2
  149. package/src/daemon/session-slash.ts +1 -1
  150. package/src/daemon/session-surfaces.ts +40 -28
  151. package/src/daemon/session-tool-setup.ts +2 -9
  152. package/src/daemon/session.ts +138 -15
  153. package/src/daemon/tool-side-effects.ts +2 -8
  154. package/src/daemon/watch-handler.ts +2 -2
  155. package/src/events/tool-metrics-listener.ts +2 -2
  156. package/src/hooks/manager.ts +1 -4
  157. package/src/inbound/public-ingress-urls.ts +7 -7
  158. package/src/logfire.ts +16 -5
  159. package/src/memory/conversation-key-store.ts +21 -0
  160. package/src/memory/db-init.ts +4 -0
  161. package/src/memory/migrations/149-oauth-tables.ts +60 -0
  162. package/src/memory/migrations/index.ts +1 -0
  163. package/src/memory/schema/index.ts +1 -0
  164. package/src/memory/schema/oauth.ts +65 -0
  165. package/src/messaging/provider.ts +4 -4
  166. package/src/messaging/providers/gmail/client.ts +82 -2
  167. package/src/messaging/providers/gmail/people-client.ts +10 -10
  168. package/src/messaging/providers/telegram-bot/adapter.ts +17 -17
  169. package/src/messaging/providers/whatsapp/adapter.ts +11 -8
  170. package/src/messaging/registry.ts +2 -32
  171. package/src/notifications/copy-composer.ts +0 -5
  172. package/src/notifications/signal.ts +4 -5
  173. package/src/oauth/byo-connection.test.ts +126 -25
  174. package/src/oauth/byo-connection.ts +22 -6
  175. package/src/oauth/connect-orchestrator.ts +113 -57
  176. package/src/oauth/connect-types.ts +17 -23
  177. package/src/oauth/connection-resolver.ts +35 -11
  178. package/src/oauth/connection.ts +1 -1
  179. package/src/oauth/manual-token-connection.ts +104 -0
  180. package/src/oauth/oauth-store.ts +496 -0
  181. package/src/oauth/platform-connection.test.ts +29 -0
  182. package/src/oauth/platform-connection.ts +6 -5
  183. package/src/oauth/provider-behaviors.ts +124 -0
  184. package/src/oauth/scope-policy.ts +9 -2
  185. package/src/oauth/seed-providers.ts +161 -0
  186. package/src/oauth/token-persistence.ts +74 -78
  187. package/src/permissions/checker.ts +3 -3
  188. package/src/permissions/defaults.ts +0 -1
  189. package/src/permissions/prompter.ts +10 -1
  190. package/src/permissions/trust-store.ts +13 -0
  191. package/src/prompts/__tests__/build-cli-reference-section.test.ts +3 -1
  192. package/src/prompts/system-prompt.ts +28 -40
  193. package/src/providers/anthropic/client.ts +133 -24
  194. package/src/providers/retry.ts +1 -27
  195. package/src/runtime/auth/route-policy.ts +0 -3
  196. package/src/runtime/channel-reply-delivery.ts +0 -40
  197. package/src/runtime/gateway-client.ts +0 -7
  198. package/src/runtime/http-server.ts +8 -6
  199. package/src/runtime/http-types.ts +2 -2
  200. package/src/runtime/middleware/twilio-validation.ts +1 -11
  201. package/src/runtime/pending-interactions.ts +14 -12
  202. package/src/runtime/routes/channel-delivery-routes.ts +0 -1
  203. package/src/runtime/routes/conversation-routes.ts +73 -19
  204. package/src/runtime/routes/events-routes.ts +21 -11
  205. package/src/runtime/routes/host-cu-routes.ts +97 -0
  206. package/src/runtime/routes/inbound-stages/background-dispatch.ts +12 -111
  207. package/src/runtime/routes/integrations/slack/share.ts +6 -7
  208. package/src/runtime/routes/log-export-routes.ts +126 -8
  209. package/src/runtime/routes/settings-routes.ts +55 -48
  210. package/src/runtime/routes/surface-action-routes.ts +1 -1
  211. package/src/runtime/routes/watch-routes.ts +128 -0
  212. package/src/schedule/integration-status.ts +10 -9
  213. package/src/security/credential-key.ts +0 -156
  214. package/src/security/keychain-broker-client.ts +5 -6
  215. package/src/security/oauth2.ts +1 -1
  216. package/src/security/token-manager.ts +119 -46
  217. package/src/skills/catalog-install.ts +358 -0
  218. package/src/skills/include-graph.ts +32 -0
  219. package/src/telegram/bot-username.ts +2 -3
  220. package/src/tools/browser/network-recorder.ts +1 -1
  221. package/src/tools/browser/network-recording-types.ts +1 -1
  222. package/src/tools/computer-use/definitions.ts +46 -11
  223. package/src/tools/computer-use/registry.ts +4 -5
  224. package/src/tools/credentials/broker.ts +1 -2
  225. package/src/tools/credentials/metadata-store.ts +17 -121
  226. package/src/tools/credentials/vault.ts +94 -167
  227. package/src/tools/registry.ts +2 -7
  228. package/src/tools/skills/load.ts +62 -3
  229. package/src/tools/watch/watch-state.ts +0 -12
  230. package/src/util/logger.ts +7 -41
  231. package/src/util/platform.ts +9 -28
  232. package/src/watcher/providers/google-calendar.ts +2 -1
  233. package/src/__tests__/computer-use-session-compaction.test.ts +0 -143
  234. package/src/__tests__/computer-use-session-lifecycle.test.ts +0 -322
  235. package/src/__tests__/computer-use-session-working-dir.test.ts +0 -166
  236. package/src/__tests__/computer-use-skill-baseline.test.ts +0 -78
  237. package/src/__tests__/computer-use-skill-endstate.test.ts +0 -105
  238. package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +0 -249
  239. package/src/__tests__/ride-shotgun-handler.test.ts +0 -452
  240. package/src/cli/commands/dev.ts +0 -129
  241. package/src/cli/commands/map.ts +0 -391
  242. package/src/cli/commands/oauth.ts +0 -77
  243. package/src/config/bundled-skills/computer-use/tools/computer-use-request-control.ts +0 -16
  244. package/src/daemon/computer-use-session.ts +0 -1026
  245. package/src/daemon/ride-shotgun-handler.ts +0 -569
  246. package/src/oauth/provider-base-urls.ts +0 -21
  247. package/src/oauth/provider-profiles.ts +0 -192
  248. package/src/prompts/computer-use-prompt.ts +0 -98
  249. package/src/runtime/routes/computer-use-routes.ts +0 -641
  250. package/src/runtime/telegram-streaming-delivery.test.ts +0 -729
  251. package/src/runtime/telegram-streaming-delivery.ts +0 -393
  252. package/src/tools/computer-use/request-computer-control.ts +0 -56
@@ -0,0 +1,299 @@
1
+ import type { Command } from "commander";
2
+
3
+ import {
4
+ disconnectOAuthProvider,
5
+ getConnection,
6
+ getConnectionByProvider,
7
+ listConnections,
8
+ } from "../../../oauth/oauth-store.js";
9
+ import { credentialKey } from "../../../security/credential-key.js";
10
+ import { deleteSecureKeyAsync } from "../../../security/secure-keys.js";
11
+ import { withValidToken } from "../../../security/token-manager.js";
12
+ import {
13
+ assertMetadataWritable,
14
+ deleteCredentialMetadata,
15
+ } from "../../../tools/credentials/metadata-store.js";
16
+ import { getCliLogger } from "../../logger.js";
17
+ import { shouldOutputJson, writeOutput } from "../../output.js";
18
+
19
+ const log = getCliLogger("cli");
20
+
21
+ /**
22
+ * Keys that may contain secrets in an OAuth token endpoint response.
23
+ * These are stripped from the `metadata` field before CLI output to prevent
24
+ * token leakage via shell history, logs, or agent transcript capture.
25
+ */
26
+ const REDACTED_METADATA_KEYS = new Set([
27
+ "access_token",
28
+ "refresh_token",
29
+ "id_token",
30
+ ]);
31
+
32
+ /** Recursively strip secret-bearing keys from a parsed metadata object. */
33
+ function redactMetadata(obj: Record<string, unknown>): Record<string, unknown> {
34
+ const result: Record<string, unknown> = {};
35
+ for (const [key, value] of Object.entries(obj)) {
36
+ if (REDACTED_METADATA_KEYS.has(key)) {
37
+ result[key] = "[REDACTED]";
38
+ } else if (value && typeof value === "object" && !Array.isArray(value)) {
39
+ result[key] = redactMetadata(value as Record<string, unknown>);
40
+ } else {
41
+ result[key] = value;
42
+ }
43
+ }
44
+ return result;
45
+ }
46
+
47
+ /** Parse stored JSON string fields and convert timestamps for a connection row. */
48
+ function formatConnectionRow(row: ReturnType<typeof getConnection>) {
49
+ if (!row) return row;
50
+ const parsed = row.metadata ? JSON.parse(row.metadata) : null;
51
+ return {
52
+ ...row,
53
+ grantedScopes: row.grantedScopes ? JSON.parse(row.grantedScopes) : [],
54
+ metadata: parsed ? redactMetadata(parsed) : null,
55
+ hasRefreshToken: row.hasRefreshToken === 1,
56
+ createdAt: new Date(row.createdAt).toISOString(),
57
+ updatedAt: new Date(row.updatedAt).toISOString(),
58
+ expiresAt: row.expiresAt ? new Date(row.expiresAt).toISOString() : null,
59
+ };
60
+ }
61
+
62
+ export function registerConnectionCommands(oauth: Command): void {
63
+ const connections = oauth
64
+ .command("connections")
65
+ .description("Manage OAuth connections (active tokens and refresh state)");
66
+
67
+ connections.addHelpText(
68
+ "after",
69
+ `
70
+ Connections represent active OAuth sessions — an access token bound to a
71
+ provider through an app registration. Each connection tracks granted scopes,
72
+ token expiry, refresh token availability, account info, and status.
73
+
74
+ Examples:
75
+ $ assistant oauth connections list
76
+ $ assistant oauth connections list --provider integration:gmail
77
+ $ assistant oauth connections get --id <uuid>
78
+ $ assistant oauth connections get --provider integration:gmail
79
+ $ assistant oauth connections token integration:twitter`,
80
+ );
81
+
82
+ // ---------------------------------------------------------------------------
83
+ // connections list
84
+ // ---------------------------------------------------------------------------
85
+
86
+ connections
87
+ .command("list")
88
+ .description("List all OAuth connections")
89
+ .option(
90
+ "--provider <key>",
91
+ "Filter by provider key (e.g. integration:gmail)",
92
+ )
93
+ .addHelpText(
94
+ "after",
95
+ `
96
+ Lists all OAuth connections, optionally filtered by provider key.
97
+
98
+ Each connection shows its ID, provider, account info, granted scopes, token
99
+ expiry, refresh token availability, and status.
100
+
101
+ Examples:
102
+ $ assistant oauth connections list
103
+ $ assistant oauth connections list --provider integration:gmail`,
104
+ )
105
+ .action((opts: { provider?: string }, cmd: Command) => {
106
+ try {
107
+ const rows = listConnections(opts.provider).map(formatConnectionRow);
108
+
109
+ if (!shouldOutputJson(cmd)) {
110
+ log.info(`Found ${rows.length} connection(s)`);
111
+ }
112
+
113
+ writeOutput(cmd, rows);
114
+ } catch (err) {
115
+ const message = err instanceof Error ? err.message : String(err);
116
+ writeOutput(cmd, { ok: false, error: message });
117
+ process.exitCode = 1;
118
+ }
119
+ });
120
+
121
+ // ---------------------------------------------------------------------------
122
+ // connections get
123
+ // ---------------------------------------------------------------------------
124
+
125
+ connections
126
+ .command("get")
127
+ .description("Look up an OAuth connection by ID or provider")
128
+ .option("--id <id>", "Connection ID (UUID)")
129
+ .option(
130
+ "--provider <key>",
131
+ "Provider key (returns most recent active connection)",
132
+ )
133
+ .addHelpText(
134
+ "after",
135
+ `
136
+ Two lookup modes are supported:
137
+
138
+ 1. By connection ID:
139
+ $ assistant oauth connections get --id <uuid>
140
+
141
+ 2. By provider (returns the most recent active connection):
142
+ $ assistant oauth connections get --provider integration:gmail
143
+
144
+ At least --id or --provider must be specified.`,
145
+ )
146
+ .action((opts: { id?: string; provider?: string }, cmd: Command) => {
147
+ try {
148
+ let row;
149
+
150
+ if (opts.id) {
151
+ row = getConnection(opts.id);
152
+ } else if (opts.provider) {
153
+ row = getConnectionByProvider(opts.provider);
154
+ } else {
155
+ writeOutput(cmd, {
156
+ ok: false,
157
+ error: "Provide --id or --provider",
158
+ });
159
+ process.exitCode = 1;
160
+ return;
161
+ }
162
+
163
+ if (!row) {
164
+ writeOutput(cmd, { ok: false, error: "Connection not found" });
165
+ process.exitCode = 1;
166
+ return;
167
+ }
168
+
169
+ writeOutput(cmd, formatConnectionRow(row));
170
+ } catch (err) {
171
+ const message = err instanceof Error ? err.message : String(err);
172
+ writeOutput(cmd, { ok: false, error: message });
173
+ process.exitCode = 1;
174
+ }
175
+ });
176
+
177
+ // ---------------------------------------------------------------------------
178
+ // connections token <provider-key>
179
+ // ---------------------------------------------------------------------------
180
+
181
+ connections
182
+ .command("token <provider-key>")
183
+ .description(
184
+ "Print a valid OAuth access token for a provider, refreshing if expired",
185
+ )
186
+ .addHelpText(
187
+ "after",
188
+ `
189
+ Arguments:
190
+ provider-key Provider key (e.g. integration:gmail, integration:twitter)
191
+
192
+ Returns a valid OAuth access token for the given provider. If the stored token
193
+ is expired or near-expiry, it is refreshed automatically before being returned.
194
+
195
+ In human mode, prints the bare token to stdout (suitable for shell substitution).
196
+ In JSON mode (--json), prints {"ok": true, "token": "..."}.
197
+
198
+ Exits with code 1 if no access token exists or refresh fails.
199
+
200
+ Examples:
201
+ $ assistant oauth connections token integration:twitter
202
+ $ assistant oauth connections token integration:gmail --json`,
203
+ )
204
+ .action(async (providerKey: string, _opts: unknown, cmd: Command) => {
205
+ try {
206
+ const token = await withValidToken(providerKey, async (t) => t);
207
+ if (shouldOutputJson(cmd)) {
208
+ writeOutput(cmd, { ok: true, token });
209
+ } else {
210
+ process.stdout.write(token + "\n");
211
+ }
212
+ } catch (err) {
213
+ const message = err instanceof Error ? err.message : String(err);
214
+ writeOutput(cmd, { ok: false, error: message });
215
+ process.exitCode = 1;
216
+ }
217
+ });
218
+
219
+ // ---------------------------------------------------------------------------
220
+ // connections disconnect <provider-key>
221
+ // ---------------------------------------------------------------------------
222
+
223
+ connections
224
+ .command("disconnect <provider-key>")
225
+ .description(
226
+ "Disconnect an OAuth integration and remove all associated credentials",
227
+ )
228
+ .addHelpText(
229
+ "after",
230
+ `
231
+ Arguments:
232
+ provider-key The full provider key (e.g. integration:gmail, integration:slack)
233
+
234
+ Removes the OAuth connection, tokens, and any legacy credential metadata for
235
+ the provider. The <provider-key> argument is the full provider key as-is — it
236
+ is not parsed through service:field splitting.
237
+
238
+ Legacy credential keys for common fields (access_token, refresh_token,
239
+ client_id, client_secret) are also cleaned up if present.
240
+
241
+ Examples:
242
+ $ assistant oauth connections disconnect integration:gmail
243
+ $ assistant oauth connections disconnect integration:slack`,
244
+ )
245
+ .action(async (providerKey: string, _opts: unknown, cmd: Command) => {
246
+ try {
247
+ assertMetadataWritable();
248
+
249
+ let cleanedUp = false;
250
+
251
+ // 1. Disconnect the OAuth connection (new-format keys + connection row)
252
+ const oauthResult = await disconnectOAuthProvider(providerKey);
253
+ if (oauthResult === "error") {
254
+ writeOutput(cmd, {
255
+ ok: false,
256
+ error: `Failed to disconnect OAuth provider "${providerKey}" — please try again`,
257
+ });
258
+ process.exitCode = 1;
259
+ return;
260
+ }
261
+ if (oauthResult === "disconnected") cleanedUp = true;
262
+
263
+ // 2. Clean up legacy credential keys for common fields
264
+ const legacyFields = [
265
+ "access_token",
266
+ "refresh_token",
267
+ "client_id",
268
+ "client_secret",
269
+ ];
270
+ for (const field of legacyFields) {
271
+ const key = credentialKey(providerKey, field);
272
+ const result = await deleteSecureKeyAsync(key);
273
+ if (result === "deleted") cleanedUp = true;
274
+
275
+ const metaDeleted = deleteCredentialMetadata(providerKey, field);
276
+ if (metaDeleted) cleanedUp = true;
277
+ }
278
+
279
+ if (!cleanedUp) {
280
+ writeOutput(cmd, {
281
+ ok: false,
282
+ error: `No OAuth connection or credentials found for "${providerKey}"`,
283
+ });
284
+ process.exitCode = 1;
285
+ return;
286
+ }
287
+
288
+ writeOutput(cmd, { ok: true, service: providerKey });
289
+
290
+ if (!shouldOutputJson(cmd)) {
291
+ log.info(`Disconnected ${providerKey}`);
292
+ }
293
+ } catch (err) {
294
+ const message = err instanceof Error ? err.message : String(err);
295
+ writeOutput(cmd, { ok: false, error: message });
296
+ process.exitCode = 1;
297
+ }
298
+ });
299
+ }
@@ -0,0 +1,52 @@
1
+ import type { Command } from "commander";
2
+
3
+ import { registerAppCommands } from "./apps.js";
4
+ import { registerConnectionCommands } from "./connections.js";
5
+ import { registerProviderCommands } from "./providers.js";
6
+
7
+ export function registerOAuthCommand(program: Command): void {
8
+ const oauth = program
9
+ .command("oauth")
10
+ .description("Manage OAuth providers, apps, connections, and tokens")
11
+ .option("--json", "Machine-readable compact JSON output");
12
+
13
+ oauth.addHelpText(
14
+ "after",
15
+ `
16
+ The oauth command group manages the full OAuth lifecycle:
17
+
18
+ providers Protocol-level configurations (auth URLs, scopes, endpoints)
19
+ apps Client credentials (client ID / secret pairs)
20
+ connections Active token grants per provider (list, get, token)
21
+
22
+ Providers are seeded on startup for built-in integrations. Apps and connections
23
+ are created during the OAuth authorization flow or can be managed manually via
24
+ their respective subcommands.
25
+
26
+ Examples:
27
+ $ assistant oauth connections token integration:twitter
28
+ $ assistant oauth connections list
29
+ $ assistant oauth connections get --provider integration:gmail
30
+ $ assistant oauth providers list
31
+ $ assistant oauth providers get integration:gmail
32
+ $ assistant oauth providers register --provider-key custom:myapi --auth-url https://example.com/auth --token-url https://example.com/token`,
33
+ );
34
+
35
+ // ---------------------------------------------------------------------------
36
+ // providers — subcommand group
37
+ // ---------------------------------------------------------------------------
38
+
39
+ registerProviderCommands(oauth);
40
+
41
+ // ---------------------------------------------------------------------------
42
+ // apps — subcommand group
43
+ // ---------------------------------------------------------------------------
44
+
45
+ registerAppCommands(oauth);
46
+
47
+ // ---------------------------------------------------------------------------
48
+ // connections — subcommand group (includes token)
49
+ // ---------------------------------------------------------------------------
50
+
51
+ registerConnectionCommands(oauth);
52
+ }
@@ -0,0 +1,242 @@
1
+ import { type Command, InvalidArgumentError } from "commander";
2
+
3
+ import {
4
+ getProvider,
5
+ listProviders,
6
+ registerProvider,
7
+ } from "../../../oauth/oauth-store.js";
8
+ import { getCliLogger } from "../../logger.js";
9
+ import { shouldOutputJson, writeOutput } from "../../output.js";
10
+
11
+ const log = getCliLogger("cli");
12
+
13
+ /** Parse stored JSON string fields into their native types. */
14
+ function parseProviderRow(row: ReturnType<typeof getProvider>) {
15
+ if (!row) return row;
16
+ return {
17
+ ...row,
18
+ defaultScopes: row.defaultScopes ? JSON.parse(row.defaultScopes) : [],
19
+ scopePolicy: row.scopePolicy ? JSON.parse(row.scopePolicy) : {},
20
+ extraParams: row.extraParams ? JSON.parse(row.extraParams) : null,
21
+ createdAt: new Date(row.createdAt).toISOString(),
22
+ updatedAt: new Date(row.updatedAt).toISOString(),
23
+ };
24
+ }
25
+
26
+ export function registerProviderCommands(oauth: Command): void {
27
+ const providers = oauth
28
+ .command("providers")
29
+ .description(
30
+ "Manage OAuth provider configurations (auth URLs, scopes, endpoints)",
31
+ );
32
+
33
+ providers.addHelpText(
34
+ "after",
35
+ `
36
+ Providers define the protocol-level configuration for an OAuth integration:
37
+ authorization URL, token URL, default scopes, and other endpoint details.
38
+
39
+ They are seeded on startup for built-in integrations (e.g. Gmail, Twitter,
40
+ Slack) but can also be registered dynamically via the "register" subcommand.
41
+
42
+ Each provider is identified by a provider key (e.g. "integration:gmail").`,
43
+ );
44
+
45
+ // ---------------------------------------------------------------------------
46
+ // providers list
47
+ // ---------------------------------------------------------------------------
48
+
49
+ providers
50
+ .command("list")
51
+ .description("List all registered OAuth providers")
52
+ .option(
53
+ "--provider-key <key>",
54
+ 'Filter by provider key substring (case-insensitive). Comma-separated values are OR\'d (e.g. "gmail,google")',
55
+ )
56
+ .addHelpText(
57
+ "after",
58
+ `
59
+ Returns registered OAuth providers, including both built-in providers
60
+ seeded at startup and any dynamically registered via "providers register".
61
+
62
+ When --provider-key is specified, only providers whose key contains the
63
+ given substring (case-insensitive) are returned. Multiple substrings can
64
+ be OR'd together using commas (e.g. "gmail,google" matches any provider
65
+ whose key contains "gmail" OR "google"). Without the flag, all providers
66
+ are listed.
67
+
68
+ Each provider row includes its key, auth URL, token URL, default scopes,
69
+ and configuration timestamps.
70
+
71
+ Examples:
72
+ $ assistant oauth providers list
73
+ $ assistant oauth providers list --provider-key gmail
74
+ $ assistant oauth providers list --provider-key "gmail,google"
75
+ $ assistant oauth providers list --provider-key slack --json`,
76
+ )
77
+ .action((opts: { providerKey?: string }, cmd: Command) => {
78
+ try {
79
+ let rows = listProviders().map(parseProviderRow);
80
+
81
+ if (opts.providerKey) {
82
+ const needles = opts.providerKey
83
+ .split(",")
84
+ .map((n) => n.trim().toLowerCase())
85
+ .filter(Boolean);
86
+ rows = rows.filter(
87
+ (r) =>
88
+ r &&
89
+ needles.some((needle) =>
90
+ r.providerKey.toLowerCase().includes(needle),
91
+ ),
92
+ );
93
+ }
94
+
95
+ if (!shouldOutputJson(cmd)) {
96
+ log.info(`Found ${rows.length} provider(s)`);
97
+ }
98
+
99
+ writeOutput(cmd, rows);
100
+ } catch (err) {
101
+ const message = err instanceof Error ? err.message : String(err);
102
+ writeOutput(cmd, { ok: false, error: message });
103
+ process.exitCode = 1;
104
+ }
105
+ });
106
+
107
+ // ---------------------------------------------------------------------------
108
+ // providers get <provider-key>
109
+ // ---------------------------------------------------------------------------
110
+
111
+ providers
112
+ .command("get <provider-key>")
113
+ .description("Show details of a specific OAuth provider")
114
+ .addHelpText(
115
+ "after",
116
+ `
117
+ Arguments:
118
+ provider-key The full provider key (e.g. "integration:gmail").
119
+ Must match the key used during registration or seeding.
120
+
121
+ Returns the full provider configuration including auth URL, token URL,
122
+ default scopes, scope policy, and extra parameters. Exits with code 1
123
+ if the provider key is not found.
124
+
125
+ Examples:
126
+ $ assistant oauth providers get integration:gmail
127
+ $ assistant oauth providers get integration:twitter --json`,
128
+ )
129
+ .action((providerKey: string, _opts: unknown, cmd: Command) => {
130
+ try {
131
+ const row = getProvider(providerKey);
132
+
133
+ if (!row) {
134
+ writeOutput(cmd, {
135
+ ok: false,
136
+ error: `Provider not found: ${providerKey}`,
137
+ });
138
+ process.exitCode = 1;
139
+ return;
140
+ }
141
+
142
+ writeOutput(cmd, parseProviderRow(row));
143
+ } catch (err) {
144
+ const message = err instanceof Error ? err.message : String(err);
145
+ writeOutput(cmd, { ok: false, error: message });
146
+ process.exitCode = 1;
147
+ }
148
+ });
149
+
150
+ // ---------------------------------------------------------------------------
151
+ // providers register
152
+ // ---------------------------------------------------------------------------
153
+
154
+ providers
155
+ .command("register")
156
+ .description("Register a new OAuth provider configuration")
157
+ .requiredOption("--provider-key <key>", "Provider key")
158
+ .requiredOption("--auth-url <url>", "Authorization endpoint URL")
159
+ .requiredOption("--token-url <url>", "Token endpoint URL")
160
+ .option("--base-url <url>", "API base URL")
161
+ .option("--userinfo-url <url>", "Userinfo endpoint URL")
162
+ .option("--scopes <scopes>", "Comma-separated default scopes")
163
+ .option("--token-auth-method <method>", "Token endpoint auth method")
164
+ .option("--callback-transport <transport>", "Callback transport")
165
+ .option("--loopback-port <port>", "Loopback port", (value: string) => {
166
+ const port = parseInt(value, 10);
167
+ if (isNaN(port) || port <= 0 || port > 65535) {
168
+ throw new InvalidArgumentError(
169
+ "Port must be a number between 1 and 65535",
170
+ );
171
+ }
172
+ return port;
173
+ })
174
+ .addHelpText(
175
+ "after",
176
+ `
177
+ Arguments (via options):
178
+ --provider-key Unique identifier for this provider (e.g. "integration:custom-service").
179
+ Must not collide with an existing provider key.
180
+ --auth-url The OAuth authorization endpoint URL.
181
+ --token-url The OAuth token endpoint URL.
182
+ --base-url Optional API base URL for the service.
183
+ --userinfo-url Optional OpenID Connect userinfo endpoint.
184
+ --scopes Comma-separated list of default scopes (e.g. "read,write,profile").
185
+ --token-auth-method How the client authenticates at the token endpoint
186
+ (e.g. "client_secret_post", "client_secret_basic").
187
+ --callback-transport Transport method for the OAuth callback.
188
+ --loopback-port Port number for the local loopback callback server (1-65535).
189
+
190
+ Registers a new OAuth provider configuration in the local store. This is
191
+ used for custom integrations not covered by the built-in provider seeds.
192
+ On success, returns the full provider row including generated timestamps.
193
+
194
+ Examples:
195
+ $ assistant oauth providers register \\
196
+ --provider-key integration:custom-api \\
197
+ --auth-url https://custom-api.example.com/oauth/authorize \\
198
+ --token-url https://custom-api.example.com/oauth/token
199
+ $ assistant oauth providers register \\
200
+ --provider-key integration:my-service \\
201
+ --auth-url https://my-service.com/auth \\
202
+ --token-url https://my-service.com/token \\
203
+ --scopes read,write --json`,
204
+ )
205
+ .action(
206
+ (
207
+ opts: {
208
+ providerKey: string;
209
+ authUrl: string;
210
+ tokenUrl: string;
211
+ baseUrl?: string;
212
+ userinfoUrl?: string;
213
+ scopes?: string;
214
+ tokenAuthMethod?: string;
215
+ callbackTransport?: string;
216
+ loopbackPort?: number;
217
+ },
218
+ cmd: Command,
219
+ ) => {
220
+ try {
221
+ const row = registerProvider({
222
+ providerKey: opts.providerKey,
223
+ authUrl: opts.authUrl,
224
+ tokenUrl: opts.tokenUrl,
225
+ baseUrl: opts.baseUrl,
226
+ userinfoUrl: opts.userinfoUrl,
227
+ defaultScopes: opts.scopes ? opts.scopes.split(",") : [],
228
+ scopePolicy: {},
229
+ tokenEndpointAuthMethod: opts.tokenAuthMethod,
230
+ callbackTransport: opts.callbackTransport,
231
+ loopbackPort: opts.loopbackPort,
232
+ });
233
+
234
+ writeOutput(cmd, parseProviderRow(row));
235
+ } catch (err) {
236
+ const message = err instanceof Error ? err.message : String(err);
237
+ writeOutput(cmd, { ok: false, error: message });
238
+ process.exitCode = 1;
239
+ }
240
+ },
241
+ );
242
+ }