@vellumai/assistant 0.5.10 → 0.5.11

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 (263) hide show
  1. package/AGENTS.md +8 -0
  2. package/ARCHITECTURE.md +43 -43
  3. package/Dockerfile +2 -0
  4. package/docs/architecture/integrations.md +3 -10
  5. package/docs/architecture/memory.md +7 -12
  6. package/docs/credential-execution-service.md +9 -9
  7. package/docs/skills.md +1 -1
  8. package/node_modules/@vellumai/credential-storage/src/index.ts +2 -2
  9. package/node_modules/@vellumai/credential-storage/src/static-credentials.ts +1 -1
  10. package/openapi.yaml +7130 -0
  11. package/package.json +2 -1
  12. package/scripts/generate-openapi.ts +562 -0
  13. package/src/__tests__/acp-session.test.ts +239 -44
  14. package/src/__tests__/assistant-feature-flag-guard.test.ts +8 -8
  15. package/src/__tests__/assistant-feature-flag-guardrails.test.ts +5 -86
  16. package/src/__tests__/assistant-feature-flags-integration.test.ts +7 -14
  17. package/src/__tests__/browser-skill-endstate.test.ts +1 -1
  18. package/src/__tests__/btw-routes.test.ts +8 -0
  19. package/src/__tests__/bundled-skill-retrieval-guard.test.ts +10 -10
  20. package/src/__tests__/channel-approvals.test.ts +7 -7
  21. package/src/__tests__/channel-readiness-service.test.ts +41 -0
  22. package/src/__tests__/config-schema.test.ts +10 -2
  23. package/src/__tests__/context-memory-e2e.test.ts +2 -6
  24. package/src/__tests__/conversation-skill-tools.test.ts +1 -3
  25. package/src/__tests__/conversation-title-service.test.ts +2 -15
  26. package/src/__tests__/credential-execution-feature-gates.test.ts +4 -8
  27. package/src/__tests__/credential-execution-managed-contract.test.ts +8 -8
  28. package/src/__tests__/credential-security-e2e.test.ts +4 -4
  29. package/src/__tests__/credential-security-invariants.test.ts +3 -3
  30. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +1 -1
  31. package/src/__tests__/gateway-only-guard.test.ts +3 -0
  32. package/src/__tests__/heartbeat-service.test.ts +35 -0
  33. package/src/__tests__/host-shell-tool.test.ts +1 -1
  34. package/src/__tests__/inline-skill-load-permissions.test.ts +3 -3
  35. package/src/__tests__/llm-request-log-turn-query.test.ts +64 -0
  36. package/src/__tests__/log-export-workspace.test.ts +1 -1
  37. package/src/__tests__/mcp-client-auth.test.ts +1 -1
  38. package/src/__tests__/memory-lifecycle-e2e.test.ts +2 -2
  39. package/src/__tests__/memory-recall-log-store.test.ts +182 -0
  40. package/src/__tests__/memory-recall-quality.test.ts +6 -8
  41. package/src/__tests__/memory-regressions.test.ts +53 -42
  42. package/src/__tests__/memory-retrieval.benchmark.test.ts +5 -9
  43. package/src/__tests__/messaging-skill-split.test.ts +2 -17
  44. package/src/__tests__/oauth-cli.test.ts +98 -551
  45. package/src/__tests__/platform-callback-registration.test.ts +119 -0
  46. package/src/__tests__/secret-ingress-channel.test.ts +261 -0
  47. package/src/__tests__/secret-ingress-cli.test.ts +201 -0
  48. package/src/__tests__/secret-ingress-http.test.ts +312 -0
  49. package/src/__tests__/secret-ingress.test.ts +283 -0
  50. package/src/__tests__/secret-onetime-send.test.ts +4 -4
  51. package/src/__tests__/skill-feature-flags-integration.test.ts +4 -4
  52. package/src/__tests__/skill-feature-flags.test.ts +11 -19
  53. package/src/__tests__/skill-load-feature-flag.test.ts +1 -1
  54. package/src/__tests__/skill-load-inline-command.test.ts +3 -3
  55. package/src/__tests__/skill-load-inline-includes.test.ts +2 -2
  56. package/src/__tests__/skill-memory.test.ts +2 -4
  57. package/src/__tests__/skill-projection-feature-flag.test.ts +2 -4
  58. package/src/__tests__/skill-projection.benchmark.test.ts +1 -3
  59. package/src/__tests__/skills.test.ts +16 -2
  60. package/src/__tests__/slack-channel-config.test.ts +1 -1
  61. package/src/__tests__/slack-skill.test.ts +5 -69
  62. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +1 -1
  63. package/src/__tests__/workspace-migration-018-rekey-compound-credential-keys.test.ts +181 -0
  64. package/src/acp/client-handler.ts +113 -31
  65. package/src/acp/session-manager.ts +29 -27
  66. package/src/approvals/guardian-request-resolvers.ts +1 -1
  67. package/src/cli/AGENTS.md +73 -0
  68. package/src/cli/commands/autonomy.ts +3 -5
  69. package/src/cli/commands/credential-execution.ts +1 -2
  70. package/src/cli/commands/memory.ts +2 -3
  71. package/src/cli/commands/oauth/__tests__/connect.test.ts +785 -0
  72. package/src/cli/commands/oauth/__tests__/disconnect.test.ts +760 -0
  73. package/src/cli/commands/oauth/__tests__/mode.test.ts +672 -0
  74. package/src/cli/commands/oauth/__tests__/ping.test.ts +690 -0
  75. package/src/cli/commands/oauth/__tests__/status.test.ts +579 -0
  76. package/src/cli/commands/oauth/__tests__/token.test.ts +467 -0
  77. package/src/cli/commands/oauth/apps.ts +26 -8
  78. package/src/cli/commands/oauth/connect.ts +373 -0
  79. package/src/cli/commands/oauth/connections.ts +14 -493
  80. package/src/cli/commands/oauth/disconnect.ts +333 -0
  81. package/src/cli/commands/oauth/index.ts +62 -10
  82. package/src/cli/commands/oauth/mode.ts +263 -0
  83. package/src/cli/commands/oauth/ping.ts +222 -0
  84. package/src/cli/commands/oauth/providers.ts +30 -3
  85. package/src/cli/commands/oauth/request.ts +576 -0
  86. package/src/cli/commands/oauth/shared.ts +132 -0
  87. package/src/cli/commands/oauth/status.ts +202 -0
  88. package/src/cli/commands/oauth/token.ts +159 -0
  89. package/src/cli/commands/platform.ts +20 -14
  90. package/src/cli.ts +82 -17
  91. package/src/config/assistant-feature-flags.ts +74 -11
  92. package/src/config/bundled-skills/_shared/CLI_RETRIEVAL_PATTERN.md +1 -1
  93. package/src/config/bundled-skills/app-builder/tools/app-create.ts +1 -1
  94. package/src/config/bundled-skills/messaging/SKILL.md +13 -36
  95. package/src/config/bundled-skills/messaging/TOOLS.json +9 -9
  96. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +1 -1
  97. package/src/config/bundled-skills/notifications/SKILL.md +1 -1
  98. package/src/config/bundled-skills/schedule/SKILL.md +2 -2
  99. package/src/config/bundled-skills/settings/SKILL.md +5 -3
  100. package/src/config/bundled-skills/settings/TOOLS.json +17 -0
  101. package/src/config/bundled-skills/settings/tools/avatar-get.ts +50 -0
  102. package/src/config/bundled-skills/settings/tools/avatar-remove.ts +7 -0
  103. package/src/config/bundled-skills/settings/tools/avatar-update.ts +6 -1
  104. package/src/config/bundled-skills/settings/tools/identity-avatar.ts +55 -0
  105. package/src/config/bundled-skills/skills-catalog/SKILL.md +3 -3
  106. package/src/config/bundled-skills/slack/SKILL.md +58 -44
  107. package/src/config/bundled-tool-registry.ts +2 -19
  108. package/src/config/env.ts +5 -1
  109. package/src/config/feature-flag-registry.json +57 -41
  110. package/src/config/loader.ts +4 -0
  111. package/src/config/schemas/platform.ts +0 -8
  112. package/src/config/schemas/security.ts +9 -1
  113. package/src/config/schemas/services.ts +1 -1
  114. package/src/config/skill-state.ts +1 -3
  115. package/src/config/skills.ts +2 -4
  116. package/src/credential-execution/feature-gates.ts +9 -16
  117. package/src/credential-execution/process-manager.ts +12 -0
  118. package/src/daemon/config-watcher.ts +4 -0
  119. package/src/daemon/conversation-agent-loop-handlers.ts +10 -0
  120. package/src/daemon/conversation-agent-loop.ts +49 -2
  121. package/src/daemon/conversation-memory.ts +0 -1
  122. package/src/daemon/handlers/config-slack-channel.ts +43 -1
  123. package/src/daemon/handlers/conversations.ts +41 -33
  124. package/src/daemon/lifecycle.ts +26 -2
  125. package/src/daemon/message-types/acp.ts +0 -15
  126. package/src/daemon/message-types/memory.ts +0 -1
  127. package/src/daemon/message-types/messages.ts +9 -1
  128. package/src/daemon/message-types/schedules.ts +9 -0
  129. package/src/daemon/server.ts +19 -7
  130. package/src/email/feature-gate.ts +3 -3
  131. package/src/heartbeat/heartbeat-service.ts +48 -0
  132. package/src/inbound/platform-callback-registration.ts +61 -7
  133. package/src/mcp/mcp-oauth-provider.ts +3 -3
  134. package/src/memory/app-store.ts +3 -3
  135. package/src/memory/conversation-crud.ts +124 -0
  136. package/src/memory/conversation-title-service.ts +7 -17
  137. package/src/memory/db-init.ts +8 -0
  138. package/src/memory/embedding-local.ts +47 -2
  139. package/src/memory/indexer.ts +13 -10
  140. package/src/memory/items-extractor.ts +12 -4
  141. package/src/memory/job-utils.ts +5 -0
  142. package/src/memory/jobs-store.ts +10 -2
  143. package/src/memory/journal-memory.ts +6 -2
  144. package/src/memory/llm-request-log-store.ts +88 -21
  145. package/src/memory/memory-recall-log-store.ts +128 -0
  146. package/src/memory/migrations/194-memory-recall-logs.ts +50 -0
  147. package/src/memory/migrations/195-oauth-providers-ping-config.ts +23 -0
  148. package/src/memory/migrations/index.ts +2 -0
  149. package/src/memory/retriever.test.ts +4 -5
  150. package/src/memory/schema/infrastructure.ts +31 -0
  151. package/src/memory/schema/oauth.ts +3 -0
  152. package/src/messaging/providers/telegram-bot/adapter.ts +1 -1
  153. package/src/oauth/connect-orchestrator.ts +54 -0
  154. package/src/oauth/manual-token-connection.ts +5 -5
  155. package/src/oauth/oauth-store.ts +26 -5
  156. package/src/oauth/seed-providers.ts +10 -1
  157. package/src/permissions/checker.ts +2 -2
  158. package/src/permissions/trust-client.ts +2 -2
  159. package/src/platform/client.ts +2 -2
  160. package/src/prompts/journal-context.ts +6 -1
  161. package/src/providers/anthropic/client.ts +143 -1
  162. package/src/runtime/auth/__tests__/middleware.test.ts +19 -0
  163. package/src/runtime/auth/route-policy.ts +0 -1
  164. package/src/runtime/btw-sidechain.ts +7 -1
  165. package/src/runtime/channel-approvals.ts +2 -2
  166. package/src/runtime/channel-readiness-service.ts +30 -7
  167. package/src/runtime/http-router.ts +31 -0
  168. package/src/runtime/http-server.ts +21 -4
  169. package/src/runtime/http-types.ts +2 -0
  170. package/src/runtime/pending-interactions.ts +21 -3
  171. package/src/runtime/routes/acp-routes.ts +46 -28
  172. package/src/runtime/routes/app-management-routes.ts +123 -0
  173. package/src/runtime/routes/app-routes.ts +31 -0
  174. package/src/runtime/routes/approval-routes.ts +108 -3
  175. package/src/runtime/routes/attachment-routes.ts +45 -0
  176. package/src/runtime/routes/avatar-routes.ts +16 -0
  177. package/src/runtime/routes/brain-graph-routes.ts +18 -0
  178. package/src/runtime/routes/btw-routes.ts +20 -0
  179. package/src/runtime/routes/call-routes.ts +81 -0
  180. package/src/runtime/routes/channel-readiness-routes.ts +48 -7
  181. package/src/runtime/routes/channel-routes.ts +18 -0
  182. package/src/runtime/routes/channel-verification-routes.ts +49 -1
  183. package/src/runtime/routes/contact-routes.ts +77 -0
  184. package/src/runtime/routes/conversation-attention-routes.ts +37 -0
  185. package/src/runtime/routes/conversation-management-routes.ts +94 -0
  186. package/src/runtime/routes/conversation-query-routes.ts +78 -0
  187. package/src/runtime/routes/conversation-routes.ts +115 -38
  188. package/src/runtime/routes/conversation-starter-routes.ts +29 -0
  189. package/src/runtime/routes/debug-routes.ts +23 -0
  190. package/src/runtime/routes/diagnostics-routes.ts +30 -0
  191. package/src/runtime/routes/documents-routes.ts +42 -0
  192. package/src/runtime/routes/events-routes.ts +10 -0
  193. package/src/runtime/routes/global-search-routes.ts +35 -0
  194. package/src/runtime/routes/guardian-action-routes.ts +47 -2
  195. package/src/runtime/routes/guardian-approval-prompt.ts +77 -2
  196. package/src/runtime/routes/heartbeat-routes.ts +278 -0
  197. package/src/runtime/routes/host-bash-routes.ts +16 -1
  198. package/src/runtime/routes/host-cu-routes.ts +23 -1
  199. package/src/runtime/routes/host-file-routes.ts +18 -1
  200. package/src/runtime/routes/identity-routes.ts +35 -0
  201. package/src/runtime/routes/inbound-message-handler.ts +46 -25
  202. package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +30 -2
  203. package/src/runtime/routes/inbound-stages/transcribe-audio.ts +1 -2
  204. package/src/runtime/routes/integrations/twilio.ts +32 -22
  205. package/src/runtime/routes/invite-routes.ts +83 -0
  206. package/src/runtime/routes/log-export-routes.ts +14 -0
  207. package/src/runtime/routes/memory-item-routes.ts +99 -1
  208. package/src/runtime/routes/migration-rollback-routes.ts +25 -0
  209. package/src/runtime/routes/migration-routes.ts +40 -0
  210. package/src/runtime/routes/notification-routes.ts +20 -0
  211. package/src/runtime/routes/oauth-apps.ts +11 -3
  212. package/src/runtime/routes/pairing-routes.ts +15 -0
  213. package/src/runtime/routes/recording-routes.ts +72 -0
  214. package/src/runtime/routes/schedule-routes.ts +77 -5
  215. package/src/runtime/routes/secret-routes.ts +63 -1
  216. package/src/runtime/routes/settings-routes.ts +90 -0
  217. package/src/runtime/routes/skills-routes.ts +98 -16
  218. package/src/runtime/routes/subagents-routes.ts +38 -3
  219. package/src/runtime/routes/surface-action-routes.ts +66 -24
  220. package/src/runtime/routes/surface-content-routes.ts +20 -0
  221. package/src/runtime/routes/telemetry-routes.ts +12 -0
  222. package/src/runtime/routes/trace-event-routes.ts +25 -0
  223. package/src/runtime/routes/trust-rules-routes.ts +46 -0
  224. package/src/runtime/routes/tts-routes.ts +15 -4
  225. package/src/runtime/routes/upgrade-broadcast-routes.ts +38 -0
  226. package/src/runtime/routes/usage-routes.ts +59 -0
  227. package/src/runtime/routes/watch-routes.ts +28 -0
  228. package/src/runtime/routes/work-items-routes.ts +59 -0
  229. package/src/runtime/routes/workspace-commit-routes.ts +12 -0
  230. package/src/runtime/routes/workspace-routes.ts +102 -0
  231. package/src/schedule/scheduler.ts +7 -1
  232. package/src/security/AGENTS.md +7 -0
  233. package/src/security/credential-backend.ts +1 -1
  234. package/src/security/encrypted-store.ts +3 -3
  235. package/src/security/oauth2.ts +55 -0
  236. package/src/security/secret-ingress.ts +174 -0
  237. package/src/security/secret-patterns.ts +133 -0
  238. package/src/security/secret-scanner.ts +28 -117
  239. package/src/signals/confirm.ts +12 -8
  240. package/src/signals/user-message.ts +18 -3
  241. package/src/skills/skill-memory.ts +1 -2
  242. package/src/tasks/task-runner.ts +7 -1
  243. package/src/tools/credentials/broker.ts +1 -1
  244. package/src/tools/credentials/metadata-store.ts +1 -1
  245. package/src/tools/credentials/vault.ts +2 -3
  246. package/src/tools/memory/definitions.ts +1 -1
  247. package/src/tools/memory/handlers.test.ts +2 -4
  248. package/src/tools/skills/load.ts +1 -1
  249. package/src/tools/terminal/safe-env.ts +7 -0
  250. package/src/tools/tool-manifest.ts +1 -1
  251. package/src/util/log-redact.ts +9 -34
  252. package/docs/architecture/keychain-broker.md +0 -68
  253. package/src/cli/commands/oauth/platform.ts +0 -525
  254. package/src/config/bundled-skills/slack/TOOLS.json +0 -272
  255. package/src/config/bundled-skills/slack/tools/shared.ts +0 -34
  256. package/src/config/bundled-skills/slack/tools/slack-add-reaction.ts +0 -27
  257. package/src/config/bundled-skills/slack/tools/slack-channel-details.ts +0 -38
  258. package/src/config/bundled-skills/slack/tools/slack-channel-permissions.ts +0 -146
  259. package/src/config/bundled-skills/slack/tools/slack-configure-channels.ts +0 -105
  260. package/src/config/bundled-skills/slack/tools/slack-delete-message.ts +0 -26
  261. package/src/config/bundled-skills/slack/tools/slack-edit-message.ts +0 -27
  262. package/src/config/bundled-skills/slack/tools/slack-leave-channel.ts +0 -25
  263. package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +0 -372
@@ -5,51 +5,14 @@ import {
5
5
  fetchManagedCatalog,
6
6
  type ManagedCredentialDescriptor,
7
7
  } from "../../../credential-execution/managed-catalog.js";
8
- import { orchestrateOAuthConnect } from "../../../oauth/connect-orchestrator.js";
9
8
  import {
10
- disconnectOAuthProvider,
11
- getAppByProviderAndClientId,
12
9
  getConnection,
13
10
  getConnectionByProvider,
14
- getMostRecentAppByProvider,
15
- getProvider,
16
11
  listConnections,
17
12
  } from "../../../oauth/oauth-store.js";
18
- import {
19
- getProviderBehavior,
20
- resolveService,
21
- } from "../../../oauth/provider-behaviors.js";
22
- import { withValidToken } from "../../../security/token-manager.js";
23
- import {
24
- assertMetadataWritable,
25
- deleteCredentialMetadata,
26
- } from "../../../tools/credentials/metadata-store.js";
27
- import { openInBrowser } from "../../../util/browser.js";
28
- import {
29
- deleteSecureKeyViaDaemon,
30
- getSecureKeyViaDaemon,
31
- } from "../../lib/daemon-credential-client.js";
32
13
  import { getCliLogger } from "../../logger.js";
33
14
  import { shouldOutputJson, writeOutput } from "../../output.js";
34
15
 
35
- // ---------------------------------------------------------------------------
36
- // CES shell lockdown guard
37
- // ---------------------------------------------------------------------------
38
-
39
- /**
40
- * Returns true when the current process is running inside an untrusted shell
41
- * (CES shell lockdown active). CLI commands that reveal raw tokens must
42
- * check this and fail deterministically.
43
- */
44
- function isUntrustedShell(): boolean {
45
- return process.env.VELLUM_UNTRUSTED_SHELL === "1";
46
- }
47
-
48
- /** Error message for commands blocked by CES shell lockdown. */
49
- const UNTRUSTED_SHELL_ERROR =
50
- "This command is not available in untrusted shell mode. " +
51
- "Raw token access is restricted when running under CES shell lockdown.";
52
-
53
16
  const log = getCliLogger("cli");
54
17
 
55
18
  /**
@@ -137,12 +100,7 @@ Examples:
137
100
  $ assistant oauth connections list --client-id abc123
138
101
  $ assistant oauth connections get --id <uuid>
139
102
  $ assistant oauth connections get --provider integration:google
140
- $ assistant oauth connections get --provider integration:google --client-id abc123
141
- $ assistant oauth connections token integration:twitter
142
- $ assistant oauth connections ping integration:google
143
- $ assistant oauth connections connect integration:google
144
- $ assistant oauth connections connect integration:google --open-browser
145
- $ assistant oauth connections disconnect integration:google`,
103
+ $ assistant oauth connections get --provider integration:google --client-id abc123`,
146
104
  );
147
105
 
148
106
  // ---------------------------------------------------------------------------
@@ -226,14 +184,17 @@ Examples:
226
184
  connections
227
185
  .command("get")
228
186
  .description("Look up an OAuth connection by ID or provider")
229
- .option("--id <id>", "Connection ID (UUID)")
187
+ .option(
188
+ "--id <id>",
189
+ "Connection ID (UUID) from 'assistant oauth connections list' or 'assistant oauth status <provider>'",
190
+ )
230
191
  .option(
231
192
  "--provider <key>",
232
- "Provider key (returns most recent active connection)",
193
+ "Provider key (e.g. integration:google) from 'assistant oauth providers list'. Returns most recent active connection.",
233
194
  )
234
195
  .option(
235
196
  "--client-id <id>",
236
- "Filter by OAuth client ID (used with --provider)",
197
+ "Filter by OAuth client ID (used with --provider). Find IDs via 'assistant oauth apps list'.",
237
198
  )
238
199
  .addHelpText(
239
200
  "after",
@@ -264,466 +225,26 @@ At least --id or --provider must be specified.`,
264
225
  } else {
265
226
  writeOutput(cmd, {
266
227
  ok: false,
267
- error: "Provide --id or --provider",
228
+ error:
229
+ "Provide --id or --provider. Run 'assistant oauth connections list' to see all connections, or 'assistant oauth status <provider>' to find connection IDs for a specific provider.",
268
230
  });
269
231
  process.exitCode = 1;
270
232
  return;
271
233
  }
272
234
 
273
235
  if (!row) {
274
- writeOutput(cmd, { ok: false, error: "Connection not found" });
275
- process.exitCode = 1;
276
- return;
277
- }
278
-
279
- writeOutput(cmd, formatConnectionRow(row));
280
- } catch (err) {
281
- const message = err instanceof Error ? err.message : String(err);
282
- writeOutput(cmd, { ok: false, error: message });
283
- process.exitCode = 1;
284
- }
285
- },
286
- );
287
-
288
- // ---------------------------------------------------------------------------
289
- // connections token <provider-key>
290
- // ---------------------------------------------------------------------------
291
-
292
- connections
293
- .command("token <provider-key>")
294
- .description(
295
- "Print a valid OAuth access token for a provider, refreshing if expired",
296
- )
297
- .option(
298
- "--client-id <id>",
299
- "Filter by OAuth client ID when multiple apps exist for the provider",
300
- )
301
- .addHelpText(
302
- "after",
303
- `
304
- Arguments:
305
- provider-key Provider key (e.g. integration:google, integration:twitter)
306
-
307
- Returns a valid OAuth access token for the given provider. If the stored token
308
- is expired or near-expiry, it is refreshed automatically before being returned.
309
-
310
- In human mode, prints the bare token to stdout (suitable for shell substitution).
311
- In JSON mode (--json), prints {"ok": true, "token": "..."}.
312
-
313
- Exits with code 1 if no access token exists or refresh fails.
314
-
315
- Examples:
316
- $ assistant oauth connections token integration:twitter
317
- $ assistant oauth connections token integration:google --json
318
- $ assistant oauth connections token integration:google --client-id abc123`,
319
- )
320
- .action(
321
- async (
322
- providerKey: string,
323
- opts: { clientId?: string },
324
- cmd: Command,
325
- ) => {
326
- try {
327
- // CES shell lockdown: deny raw token reveal in untrusted shells.
328
- if (isUntrustedShell()) {
329
- writeOutput(cmd, { ok: false, error: UNTRUSTED_SHELL_ERROR });
330
- process.exitCode = 1;
331
- return;
332
- }
333
-
334
- const token = await withValidToken(
335
- providerKey,
336
- async (t) => t,
337
- opts.clientId,
338
- );
339
- if (shouldOutputJson(cmd)) {
340
- writeOutput(cmd, { ok: true, token });
341
- } else {
342
- process.stdout.write(token + "\n");
343
- }
344
- } catch (err) {
345
- const message = err instanceof Error ? err.message : String(err);
346
- writeOutput(cmd, { ok: false, error: message });
347
- process.exitCode = 1;
348
- }
349
- },
350
- );
351
-
352
- // ---------------------------------------------------------------------------
353
- // connections ping <provider-key>
354
- // ---------------------------------------------------------------------------
355
-
356
- connections
357
- .command("ping <provider-key>")
358
- .description(
359
- "Verify that a stored OAuth token is still valid by hitting the provider's health-check endpoint",
360
- )
361
- .option(
362
- "--client-id <id>",
363
- "Filter by OAuth client ID when multiple apps exist for the provider",
364
- )
365
- .addHelpText(
366
- "after",
367
- `
368
- Arguments:
369
- provider-key Provider key (e.g. integration:google, integration:twitter)
370
-
371
- Fetches a valid access token (refreshing if needed) and sends a GET request
372
- to the provider's configured ping URL. Reports success (HTTP 2xx) or failure.
373
-
374
- The ping URL is set per-provider in seed data or via "providers register --ping-url".
375
- If no ping URL is configured for the provider, exits with an error.
376
-
377
- Examples:
378
- $ assistant oauth connections ping integration:google
379
- $ assistant oauth connections ping integration:twitter --json
380
- $ assistant oauth connections ping integration:google --client-id abc123`,
381
- )
382
- .action(
383
- async (
384
- providerKey: string,
385
- opts: { clientId?: string },
386
- cmd: Command,
387
- ) => {
388
- try {
389
- const provider = getProvider(providerKey);
390
- if (!provider) {
236
+ const source = opts.id
237
+ ? `--id ${opts.id}`
238
+ : `--provider ${opts.provider}`;
391
239
  writeOutput(cmd, {
392
240
  ok: false,
393
- error: `Provider not found: ${providerKey}`,
241
+ error: `No connection found for ${source}. Run 'assistant oauth connections list' to see all connections, or 'assistant oauth connect <provider>' to create a new connection.`,
394
242
  });
395
243
  process.exitCode = 1;
396
244
  return;
397
245
  }
398
246
 
399
- if (!provider.pingUrl) {
400
- writeOutput(cmd, {
401
- ok: false,
402
- error: `No ping URL configured for "${providerKey}"`,
403
- });
404
- process.exitCode = 1;
405
- return;
406
- }
407
-
408
- const pingUrl = provider.pingUrl;
409
-
410
- const PING_TIMEOUT_MS = 15_000;
411
-
412
- const result = await withValidToken(
413
- providerKey,
414
- async (token) => {
415
- const controller = new AbortController();
416
- const timer = setTimeout(
417
- () => controller.abort(),
418
- PING_TIMEOUT_MS,
419
- );
420
- try {
421
- const res = await fetch(pingUrl, {
422
- method: "GET",
423
- headers: { Authorization: `Bearer ${token}` },
424
- signal: controller.signal,
425
- });
426
-
427
- if (res.status === 401) {
428
- const err = new Error(
429
- `Ping returned HTTP 401 from ${pingUrl}`,
430
- );
431
- (err as unknown as { status: number }).status = 401;
432
- throw err;
433
- }
434
-
435
- return { status: res.status, ok: res.ok };
436
- } finally {
437
- clearTimeout(timer);
438
- }
439
- },
440
- opts.clientId,
441
- );
442
-
443
- if (result.ok) {
444
- if (shouldOutputJson(cmd)) {
445
- writeOutput(cmd, {
446
- ok: true,
447
- provider: providerKey,
448
- status: result.status,
449
- });
450
- } else {
451
- log.info(`${providerKey}: OK (HTTP ${result.status})`);
452
- writeOutput(cmd, {
453
- ok: true,
454
- provider: providerKey,
455
- status: result.status,
456
- });
457
- }
458
- } else {
459
- writeOutput(cmd, {
460
- ok: false,
461
- provider: providerKey,
462
- status: result.status,
463
- error: `Ping failed with HTTP ${result.status}`,
464
- });
465
- process.exitCode = 1;
466
- }
467
- } catch (err) {
468
- const message = err instanceof Error ? err.message : String(err);
469
- writeOutput(cmd, { ok: false, error: message });
470
- process.exitCode = 1;
471
- }
472
- },
473
- );
474
-
475
- // ---------------------------------------------------------------------------
476
- // connections disconnect <provider-key>
477
- // ---------------------------------------------------------------------------
478
-
479
- connections
480
- .command("disconnect <provider-key>")
481
- .description(
482
- "Disconnect an OAuth integration and remove all associated credentials",
483
- )
484
- .option(
485
- "--client-id <id>",
486
- "Filter by OAuth client ID when multiple apps exist for the provider",
487
- )
488
- .addHelpText(
489
- "after",
490
- `
491
- Arguments:
492
- provider-key The full provider key (e.g. integration:google, integration:slack)
493
-
494
- Removes the OAuth connection, tokens, and any legacy credential metadata for
495
- the provider. The <provider-key> argument is the full provider key as-is — it
496
- is not parsed through service:field splitting.
497
-
498
- Legacy credential keys for common fields (access_token, refresh_token,
499
- client_id, client_secret) are also cleaned up if present.
500
-
501
- Examples:
502
- $ assistant oauth connections disconnect integration:google
503
- $ assistant oauth connections disconnect integration:slack
504
- $ assistant oauth connections disconnect integration:google --client-id abc123`,
505
- )
506
- .action(
507
- async (
508
- providerKey: string,
509
- opts: { clientId?: string },
510
- cmd: Command,
511
- ) => {
512
- try {
513
- assertMetadataWritable();
514
-
515
- let cleanedUp = false;
516
-
517
- // 1. Disconnect the OAuth connection (new-format keys + connection row)
518
- const oauthResult = await disconnectOAuthProvider(
519
- providerKey,
520
- opts.clientId,
521
- );
522
- if (oauthResult === "error") {
523
- writeOutput(cmd, {
524
- ok: false,
525
- error: `Failed to disconnect OAuth provider "${providerKey}" — please try again`,
526
- });
527
- process.exitCode = 1;
528
- return;
529
- }
530
- if (oauthResult === "disconnected") cleanedUp = true;
531
-
532
- // 2. Clean up legacy credential keys for common fields
533
- const legacyFields = [
534
- "access_token",
535
- "refresh_token",
536
- "client_id",
537
- "client_secret",
538
- ];
539
- for (const field of legacyFields) {
540
- const result = await deleteSecureKeyViaDaemon(
541
- "credential",
542
- `${providerKey}:${field}`,
543
- );
544
- if (result === "deleted") cleanedUp = true;
545
-
546
- const metaDeleted = deleteCredentialMetadata(providerKey, field);
547
- if (metaDeleted) cleanedUp = true;
548
- }
549
-
550
- if (!cleanedUp) {
551
- writeOutput(cmd, {
552
- ok: false,
553
- error: `No OAuth connection or credentials found for "${providerKey}"`,
554
- });
555
- process.exitCode = 1;
556
- return;
557
- }
558
-
559
- writeOutput(cmd, { ok: true, service: providerKey });
560
-
561
- if (!shouldOutputJson(cmd)) {
562
- log.info(`Disconnected ${providerKey}`);
563
- }
564
- } catch (err) {
565
- const message = err instanceof Error ? err.message : String(err);
566
- writeOutput(cmd, { ok: false, error: message });
567
- process.exitCode = 1;
568
- }
569
- },
570
- );
571
-
572
- // ---------------------------------------------------------------------------
573
- // connections connect <provider-key>
574
- // ---------------------------------------------------------------------------
575
-
576
- connections
577
- .command("connect <provider-key>")
578
- .description("Initiate an OAuth2 authorization flow for a provider")
579
- .option(
580
- "--client-id <id>",
581
- "Filter by OAuth client ID when multiple apps exist for the provider",
582
- )
583
- .option(
584
- "--scopes <scopes...>",
585
- "Additional scopes beyond the provider's defaults",
586
- )
587
- .option(
588
- "--open-browser",
589
- "Open the auth URL in the browser and wait for completion",
590
- )
591
- .addHelpText(
592
- "after",
593
- `
594
- Arguments:
595
- provider-key Provider key (e.g. integration:google) or alias (e.g. gmail)
596
-
597
- Initiates an OAuth2 authorization flow for the given provider. By default,
598
- prints the authorization URL to stdout — useful for headless/remote sessions.
599
- The token exchange completes in the background when the user authorizes.
600
-
601
- With --open-browser, opens the authorization URL in your browser and waits
602
- for completion.
603
-
604
- Client credentials are resolved from the OAuth app store. Use --client-id
605
- to select a specific app when multiple apps exist for the same provider.
606
-
607
- Examples:
608
- $ assistant oauth connections connect integration:google
609
- $ assistant oauth connections connect gmail --open-browser
610
- $ assistant oauth connections connect integration:slack --client-id abc123
611
- $ assistant oauth connections connect integration:google --scopes calendar.readonly --json`,
612
- )
613
- .action(
614
- async (
615
- providerKey: string,
616
- opts: {
617
- clientId?: string;
618
- scopes?: string[];
619
- openBrowser?: boolean;
620
- },
621
- cmd: Command,
622
- ) => {
623
- try {
624
- // a. Resolve service alias
625
- const resolvedServiceKey = resolveService(providerKey);
626
-
627
- // b. Resolve client credentials from the DB
628
- const dbApp = opts.clientId
629
- ? getAppByProviderAndClientId(resolvedServiceKey, opts.clientId)
630
- : getMostRecentAppByProvider(resolvedServiceKey);
631
-
632
- let clientId = opts.clientId;
633
- let clientSecret: string | undefined;
634
-
635
- if (dbApp) {
636
- if (!clientId) clientId = dbApp.clientId;
637
- const storedSecret = await getSecureKeyViaDaemon(
638
- dbApp.clientSecretCredentialPath,
639
- );
640
- if (storedSecret) clientSecret = storedSecret;
641
- } else if (opts.clientId) {
642
- // --client-id was explicitly provided but no matching app exists
643
- writeOutput(cmd, {
644
- ok: false,
645
- error: `No registered app found for "${resolvedServiceKey}" with client ID "${opts.clientId}". Register it first with 'assistant oauth apps upsert --provider ${resolvedServiceKey} --client-id ${opts.clientId}'.`,
646
- });
647
- process.exitCode = 1;
648
- return;
649
- }
650
-
651
- // c. Validate client_id
652
- if (!clientId) {
653
- writeOutput(cmd, {
654
- ok: false,
655
- error:
656
- "No client_id found. Provide --client-id or register an app first with 'assistant oauth apps upsert'.",
657
- });
658
- process.exitCode = 1;
659
- return;
660
- }
661
-
662
- // d. Check if client_secret is required but missing
663
- if (clientSecret === undefined) {
664
- const providerRow = getProvider(resolvedServiceKey);
665
- const behavior = getProviderBehavior(resolvedServiceKey);
666
-
667
- const requiresSecret =
668
- behavior?.setup?.requiresClientSecret ??
669
- !!(
670
- providerRow?.tokenEndpointAuthMethod || providerRow?.extraParams
671
- );
672
-
673
- if (requiresSecret) {
674
- writeOutput(cmd, {
675
- ok: false,
676
- error: `client_secret is required for ${resolvedServiceKey} but not found. Store it first with 'assistant oauth apps upsert --client-secret'.`,
677
- });
678
- process.exitCode = 1;
679
- return;
680
- }
681
- }
682
-
683
- // e. Call the orchestrator
684
- const result = await orchestrateOAuthConnect({
685
- service: providerKey,
686
- clientId,
687
- clientSecret,
688
- isInteractive: !!opts.openBrowser,
689
- openUrl: opts.openBrowser ? openInBrowser : undefined,
690
- ...(opts.scopes ? { requestedScopes: opts.scopes } : {}),
691
- });
692
-
693
- // f. Handle results
694
- if (!result.success) {
695
- writeOutput(cmd, { ok: false, error: result.error });
696
- process.exitCode = 1;
697
- return;
698
- }
699
-
700
- if (result.deferred) {
701
- if (shouldOutputJson(cmd)) {
702
- writeOutput(cmd, {
703
- ok: true,
704
- deferred: true,
705
- authUrl: result.authUrl,
706
- service: result.service,
707
- });
708
- } else {
709
- process.stdout.write(
710
- `Open this URL to authorize:\n\n${result.authUrl}\n\nThe connection will complete automatically once you authorize.\n`,
711
- );
712
- }
713
- return;
714
- }
715
-
716
- // Interactive mode completed
717
- if (shouldOutputJson(cmd)) {
718
- writeOutput(cmd, {
719
- ok: true,
720
- grantedScopes: result.grantedScopes,
721
- accountInfo: result.accountInfo,
722
- });
723
- } else {
724
- const msg = `Connected to ${resolvedServiceKey}${result.accountInfo ? ` as ${result.accountInfo}` : ""}`;
725
- process.stdout.write(msg + "\n");
726
- }
247
+ writeOutput(cmd, formatConnectionRow(row));
727
248
  } catch (err) {
728
249
  const message = err instanceof Error ? err.message : String(err);
729
250
  writeOutput(cmd, { ok: false, error: message });