@vellumai/assistant 0.4.49 → 0.4.51

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 (353) hide show
  1. package/ARCHITECTURE.md +24 -33
  2. package/README.md +3 -3
  3. package/docs/architecture/integrations.md +2 -2
  4. package/docs/architecture/keychain-broker.md +6 -6
  5. package/docs/architecture/memory.md +180 -119
  6. package/knip.json +32 -0
  7. package/package.json +3 -2
  8. package/src/__tests__/agent-loop.test.ts +3 -1
  9. package/src/__tests__/anthropic-provider.test.ts +114 -23
  10. package/src/__tests__/approval-cascade.test.ts +1 -15
  11. package/src/__tests__/approval-routes-http.test.ts +2 -0
  12. package/src/__tests__/assistant-feature-flag-guard.test.ts +0 -23
  13. package/src/__tests__/btw-routes.test.ts +61 -5
  14. package/src/__tests__/canonical-guardian-store.test.ts +95 -0
  15. package/src/__tests__/checker.test.ts +13 -0
  16. package/src/__tests__/config-schema.test.ts +1 -68
  17. package/src/__tests__/config-watcher.test.ts +8 -0
  18. package/src/__tests__/context-memory-e2e.test.ts +11 -100
  19. package/src/__tests__/conversation-routes-guardian-reply.test.ts +8 -0
  20. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
  21. package/src/__tests__/credential-security-e2e.test.ts +1 -0
  22. package/src/__tests__/credential-security-invariants.test.ts +8 -7
  23. package/src/__tests__/credential-vault-unit.test.ts +23 -18
  24. package/src/__tests__/credential-vault.test.ts +30 -18
  25. package/src/__tests__/credentials-cli.test.ts +257 -82
  26. package/src/__tests__/cu-unified-flow.test.ts +532 -0
  27. package/src/__tests__/date-context.test.ts +93 -77
  28. package/src/__tests__/deterministic-verification-control-plane.test.ts +64 -0
  29. package/src/__tests__/guardian-routing-invariants.test.ts +93 -0
  30. package/src/__tests__/history-repair.test.ts +245 -0
  31. package/src/__tests__/host-cu-proxy.test.ts +165 -3
  32. package/src/__tests__/http-user-message-parity.test.ts +1 -0
  33. package/src/__tests__/inbound-invite-redemption.test.ts +36 -7
  34. package/src/__tests__/integration-status.test.ts +31 -30
  35. package/src/__tests__/invite-redemption-service.test.ts +166 -13
  36. package/src/__tests__/invite-routes-http.test.ts +166 -5
  37. package/src/__tests__/keychain-broker-client.test.ts +4 -4
  38. package/src/__tests__/list-messages-attachments.test.ts +193 -0
  39. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +56 -18
  40. package/src/__tests__/memory-lifecycle-e2e.test.ts +244 -387
  41. package/src/__tests__/memory-recall-quality.test.ts +244 -407
  42. package/src/__tests__/memory-regressions.experimental.test.ts +126 -101
  43. package/src/__tests__/memory-regressions.test.ts +477 -2841
  44. package/src/__tests__/memory-retrieval.benchmark.test.ts +33 -150
  45. package/src/__tests__/memory-upsert-concurrency.test.ts +5 -244
  46. package/src/__tests__/mime-builder.test.ts +28 -0
  47. package/src/__tests__/native-web-search.test.ts +1 -0
  48. package/src/__tests__/oauth-cli.test.ts +824 -31
  49. package/src/__tests__/oauth-provider-profiles.test.ts +1 -1
  50. package/src/__tests__/oauth-store.test.ts +363 -17
  51. package/src/__tests__/qdrant-collection-migration.test.ts +53 -8
  52. package/src/__tests__/registry.test.ts +0 -1
  53. package/src/__tests__/relay-server.test.ts +55 -1
  54. package/src/__tests__/schedule-tools.test.ts +32 -0
  55. package/src/__tests__/script-proxy-certs.test.ts +1 -1
  56. package/src/__tests__/secret-onetime-send.test.ts +1 -0
  57. package/src/__tests__/secret-routes-managed-proxy.test.ts +183 -0
  58. package/src/__tests__/secure-keys.test.ts +78 -18
  59. package/src/__tests__/send-endpoint-busy.test.ts +3 -0
  60. package/src/__tests__/server-history-render.test.ts +2 -2
  61. package/src/__tests__/session-abort-tool-results.test.ts +1 -14
  62. package/src/__tests__/session-agent-loop-overflow.test.ts +1583 -0
  63. package/src/__tests__/session-agent-loop.test.ts +19 -15
  64. package/src/__tests__/session-confirmation-signals.test.ts +1 -15
  65. package/src/__tests__/session-error.test.ts +124 -2
  66. package/src/__tests__/session-history-web-search.test.ts +918 -0
  67. package/src/__tests__/session-pre-run-repair.test.ts +1 -14
  68. package/src/__tests__/session-provider-retry-repair.test.ts +25 -28
  69. package/src/__tests__/session-queue.test.ts +37 -27
  70. package/src/__tests__/session-runtime-assembly.test.ts +54 -0
  71. package/src/__tests__/session-slash-known.test.ts +1 -15
  72. package/src/__tests__/session-slash-queue.test.ts +1 -15
  73. package/src/__tests__/session-slash-unknown.test.ts +1 -15
  74. package/src/__tests__/session-workspace-cache-state.test.ts +3 -33
  75. package/src/__tests__/session-workspace-injection.test.ts +3 -37
  76. package/src/__tests__/session-workspace-tool-tracking.test.ts +3 -37
  77. package/src/__tests__/skills-install-extract.test.ts +93 -0
  78. package/src/__tests__/skills.test.ts +2 -2
  79. package/src/__tests__/skillssh-registry.test.ts +451 -0
  80. package/src/__tests__/slack-channel-config.test.ts +10 -8
  81. package/src/__tests__/trust-store.test.ts +15 -0
  82. package/src/__tests__/twilio-config.test.ts +11 -10
  83. package/src/__tests__/twilio-provider.test.ts +9 -4
  84. package/src/__tests__/voice-invite-redemption.test.ts +85 -5
  85. package/src/agent/ax-tree-compaction.test.ts +51 -0
  86. package/src/agent/loop.ts +39 -12
  87. package/src/approvals/AGENTS.md +1 -1
  88. package/src/approvals/guardian-request-resolvers.ts +14 -2
  89. package/src/bundler/compiler-tools.ts +66 -2
  90. package/src/calls/call-domain.ts +134 -3
  91. package/src/calls/call-store.ts +6 -0
  92. package/src/calls/relay-server.ts +44 -6
  93. package/src/calls/relay-setup-router.ts +17 -1
  94. package/src/calls/twilio-config.ts +5 -4
  95. package/src/calls/twilio-provider.ts +14 -9
  96. package/src/calls/twilio-rest.ts +10 -7
  97. package/src/calls/types.ts +3 -1
  98. package/src/cli/commands/config.ts +14 -9
  99. package/src/cli/commands/contacts.ts +3 -0
  100. package/src/cli/commands/credentials.ts +170 -174
  101. package/src/cli/commands/doctor.ts +11 -8
  102. package/src/cli/commands/keys.ts +9 -9
  103. package/src/cli/commands/mcp.ts +46 -59
  104. package/src/cli/commands/memory.ts +16 -165
  105. package/src/cli/commands/oauth/apps.ts +68 -10
  106. package/src/cli/commands/oauth/connections.ts +475 -105
  107. package/src/cli/commands/oauth/index.ts +3 -3
  108. package/src/cli/commands/oauth/providers.ts +18 -4
  109. package/src/cli/commands/sessions.ts +5 -2
  110. package/src/cli/commands/skills.ts +173 -1
  111. package/src/cli/http-client.ts +0 -20
  112. package/src/cli/main-screen.tsx +2 -2
  113. package/src/cli/program.ts +5 -6
  114. package/src/cli.ts +20 -22
  115. package/src/config/__tests__/feature-flag-registry-bundled.test.ts +39 -0
  116. package/src/config/bundled-skills/computer-use/TOOLS.json +1 -1
  117. package/src/config/bundled-skills/computer-use/tools/computer-use-observe.ts +12 -0
  118. package/src/config/bundled-skills/contacts/SKILL.md +35 -11
  119. package/src/config/bundled-skills/contacts/tools/google-contacts.ts +1 -1
  120. package/src/config/bundled-skills/gmail/SKILL.md +1 -1
  121. package/src/config/bundled-skills/gmail/TOOLS.json +52 -0
  122. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +13 -3
  123. package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +9 -2
  124. package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +5 -1
  125. package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +5 -1
  126. package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +5 -1
  127. package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +5 -1
  128. package/src/config/bundled-skills/gmail/tools/gmail-label.ts +9 -2
  129. package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +5 -1
  130. package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +5 -1
  131. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +5 -1
  132. package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +5 -1
  133. package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +5 -1
  134. package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +5 -1
  135. package/src/config/bundled-skills/google-calendar/TOOLS.json +20 -0
  136. package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +2 -1
  137. package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +2 -1
  138. package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +2 -1
  139. package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +2 -1
  140. package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +2 -1
  141. package/src/config/bundled-skills/google-calendar/tools/shared.ts +8 -2
  142. package/src/config/bundled-skills/messaging/SKILL.md +1 -1
  143. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +2 -2
  144. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +2 -2
  145. package/src/config/bundled-skills/messaging/tools/messaging-auth-test.ts +2 -2
  146. package/src/config/bundled-skills/messaging/tools/messaging-list-conversations.ts +2 -2
  147. package/src/config/bundled-skills/messaging/tools/messaging-mark-read.ts +2 -2
  148. package/src/config/bundled-skills/messaging/tools/messaging-read.ts +2 -2
  149. package/src/config/bundled-skills/messaging/tools/messaging-search.ts +2 -2
  150. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +2 -2
  151. package/src/config/bundled-skills/messaging/tools/messaging-sender-digest.ts +2 -2
  152. package/src/config/bundled-skills/messaging/tools/shared.ts +7 -5
  153. package/src/config/bundled-skills/slack/tools/shared.ts +1 -1
  154. package/src/config/bundled-skills/slack/tools/slack-add-reaction.ts +1 -1
  155. package/src/config/bundled-skills/slack/tools/slack-channel-details.ts +1 -1
  156. package/src/config/bundled-skills/slack/tools/slack-delete-message.ts +1 -1
  157. package/src/config/bundled-skills/slack/tools/slack-edit-message.ts +1 -1
  158. package/src/config/bundled-skills/slack/tools/slack-leave-channel.ts +1 -1
  159. package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +1 -1
  160. package/src/config/bundled-tool-registry.ts +2 -5
  161. package/src/config/loader.ts +6 -42
  162. package/src/config/schema.ts +1 -12
  163. package/src/config/schemas/memory-lifecycle.ts +0 -9
  164. package/src/config/schemas/memory-processing.ts +0 -180
  165. package/src/config/schemas/memory-retrieval.ts +32 -104
  166. package/src/config/schemas/memory.ts +0 -10
  167. package/src/config/types.ts +0 -4
  168. package/src/contacts/contact-store.ts +39 -2
  169. package/src/contacts/contacts-write.ts +9 -0
  170. package/src/context/window-manager.ts +4 -1
  171. package/src/daemon/config-watcher.ts +55 -2
  172. package/src/daemon/daemon-control.ts +1 -1
  173. package/src/daemon/date-context.ts +114 -31
  174. package/src/daemon/handlers/config-ingress.ts +2 -2
  175. package/src/daemon/handlers/config-slack-channel.ts +59 -39
  176. package/src/daemon/handlers/config-telegram.ts +23 -14
  177. package/src/daemon/handlers/session-history.ts +1 -358
  178. package/src/daemon/handlers/sessions.ts +18 -13
  179. package/src/daemon/handlers/shared.ts +3 -17
  180. package/src/daemon/handlers/skills.ts +20 -1
  181. package/src/daemon/history-repair.ts +72 -8
  182. package/src/daemon/host-cu-proxy.ts +55 -26
  183. package/src/daemon/lifecycle.ts +39 -4
  184. package/src/daemon/mcp-reload-service.ts +2 -2
  185. package/src/daemon/message-types/computer-use.ts +1 -12
  186. package/src/daemon/message-types/memory.ts +4 -16
  187. package/src/daemon/message-types/messages.ts +1 -0
  188. package/src/daemon/message-types/sessions.ts +4 -42
  189. package/src/daemon/server.ts +6 -1
  190. package/src/daemon/session-agent-loop-handlers.ts +38 -0
  191. package/src/daemon/session-agent-loop.ts +334 -48
  192. package/src/daemon/session-error.ts +89 -6
  193. package/src/daemon/session-history.ts +17 -7
  194. package/src/daemon/session-media-retry.ts +6 -2
  195. package/src/daemon/session-memory.ts +69 -149
  196. package/src/daemon/session-process.ts +10 -1
  197. package/src/daemon/session-runtime-assembly.ts +49 -19
  198. package/src/daemon/session-slash.ts +3 -5
  199. package/src/daemon/session-surfaces.ts +4 -1
  200. package/src/daemon/session-tool-setup.ts +7 -1
  201. package/src/daemon/session.ts +12 -2
  202. package/src/email/providers/index.ts +2 -2
  203. package/src/instrument.ts +61 -1
  204. package/src/media/avatar-router.ts +1 -1
  205. package/src/memory/admin.ts +2 -191
  206. package/src/memory/canonical-guardian-store.ts +38 -2
  207. package/src/memory/conversation-crud.ts +0 -33
  208. package/src/memory/conversation-queries.ts +25 -83
  209. package/src/memory/db-init.ts +32 -0
  210. package/src/memory/embedding-backend.ts +84 -8
  211. package/src/memory/embedding-types.ts +9 -1
  212. package/src/memory/indexer.ts +7 -46
  213. package/src/memory/invite-store.ts +19 -0
  214. package/src/memory/items-extractor.ts +274 -76
  215. package/src/memory/job-handlers/backfill.ts +2 -127
  216. package/src/memory/job-handlers/cleanup.ts +2 -16
  217. package/src/memory/job-handlers/extraction.ts +2 -138
  218. package/src/memory/job-handlers/index-maintenance.ts +1 -6
  219. package/src/memory/job-handlers/summarization.ts +3 -148
  220. package/src/memory/job-utils.ts +21 -59
  221. package/src/memory/jobs-store.ts +1 -159
  222. package/src/memory/jobs-worker.ts +9 -52
  223. package/src/memory/migrations/104-core-indexes.ts +3 -3
  224. package/src/memory/migrations/149-oauth-tables.ts +2 -0
  225. package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +98 -0
  226. package/src/memory/migrations/151-oauth-providers-ping-url.ts +11 -0
  227. package/src/memory/migrations/152-memory-item-supersession.ts +44 -0
  228. package/src/memory/migrations/153-drop-entity-tables.ts +15 -0
  229. package/src/memory/migrations/154-drop-fts.ts +20 -0
  230. package/src/memory/migrations/155-drop-conflicts.ts +7 -0
  231. package/src/memory/migrations/156-call-session-invite-metadata.ts +24 -0
  232. package/src/memory/migrations/157-invite-contact-id.ts +104 -0
  233. package/src/memory/migrations/index.ts +8 -0
  234. package/src/memory/migrations/registry.ts +6 -0
  235. package/src/memory/qdrant-client.ts +148 -51
  236. package/src/memory/raw-query.ts +1 -1
  237. package/src/memory/retriever.test.ts +294 -273
  238. package/src/memory/retriever.ts +421 -645
  239. package/src/memory/schema/calls.ts +2 -0
  240. package/src/memory/schema/contacts.ts +1 -0
  241. package/src/memory/schema/memory-core.ts +3 -48
  242. package/src/memory/schema/oauth.ts +2 -0
  243. package/src/memory/search/formatting.ts +263 -176
  244. package/src/memory/search/lexical.ts +1 -254
  245. package/src/memory/search/ranking.ts +0 -455
  246. package/src/memory/search/semantic.ts +100 -14
  247. package/src/memory/search/staleness.ts +47 -0
  248. package/src/memory/search/tier-classifier.ts +21 -0
  249. package/src/memory/search/types.ts +15 -77
  250. package/src/memory/task-memory-cleanup.ts +4 -6
  251. package/src/messaging/provider.ts +1 -1
  252. package/src/messaging/providers/gmail/adapter.ts +1 -1
  253. package/src/messaging/providers/gmail/mime-builder.ts +17 -7
  254. package/src/messaging/providers/telegram-bot/adapter.ts +17 -8
  255. package/src/messaging/providers/whatsapp/adapter.ts +13 -9
  256. package/src/messaging/registry.ts +9 -5
  257. package/src/oauth/byo-connection.test.ts +40 -25
  258. package/src/oauth/connect-orchestrator.ts +4 -10
  259. package/src/oauth/connection-resolver.ts +20 -6
  260. package/src/oauth/manual-token-connection.ts +5 -5
  261. package/src/oauth/oauth-store.ts +183 -31
  262. package/src/oauth/platform-connection.test.ts +1 -1
  263. package/src/oauth/provider-behaviors.ts +503 -4
  264. package/src/oauth/seed-providers.ts +214 -8
  265. package/src/oauth/token-persistence.ts +31 -16
  266. package/src/permissions/defaults.ts +1 -0
  267. package/src/permissions/trust-store.ts +23 -1
  268. package/src/playbooks/playbook-compiler.ts +1 -1
  269. package/src/prompts/system-prompt.ts +18 -2
  270. package/src/providers/anthropic/client.ts +56 -126
  271. package/src/providers/types.ts +7 -1
  272. package/src/runtime/AGENTS.md +9 -0
  273. package/src/runtime/auth/route-policy.ts +6 -3
  274. package/src/runtime/channel-readiness-service.ts +48 -40
  275. package/src/runtime/guardian-reply-router.ts +24 -22
  276. package/src/runtime/http-server.ts +2 -2
  277. package/src/runtime/http-types.ts +2 -0
  278. package/src/runtime/invite-redemption-service.ts +72 -12
  279. package/src/runtime/invite-service.ts +43 -0
  280. package/src/runtime/middleware/twilio-validation.ts +1 -1
  281. package/src/runtime/pending-interactions.ts +2 -2
  282. package/src/runtime/routes/brain-graph-routes.ts +10 -90
  283. package/src/runtime/routes/btw-routes.ts +10 -5
  284. package/src/runtime/routes/conversation-routes.ts +56 -11
  285. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +21 -12
  286. package/src/runtime/routes/integrations/slack/channel.ts +2 -2
  287. package/src/runtime/routes/integrations/telegram.ts +2 -2
  288. package/src/runtime/routes/integrations/twilio.ts +17 -17
  289. package/src/runtime/routes/invite-routes.ts +29 -4
  290. package/src/runtime/routes/memory-item-routes.test.ts +754 -0
  291. package/src/runtime/routes/memory-item-routes.ts +503 -0
  292. package/src/runtime/routes/secret-routes.ts +17 -0
  293. package/src/runtime/routes/session-management-routes.ts +3 -3
  294. package/src/runtime/routes/settings-routes.ts +3 -3
  295. package/src/runtime/routes/trust-rules-routes.ts +14 -0
  296. package/src/runtime/routes/workspace-routes.ts +9 -4
  297. package/src/runtime/routes/workspace-utils.ts +8 -2
  298. package/src/schedule/integration-status.ts +26 -19
  299. package/src/security/keychain-broker-client.ts +17 -4
  300. package/src/security/oauth2.ts +6 -7
  301. package/src/security/secure-keys.ts +44 -19
  302. package/src/security/token-manager.ts +46 -39
  303. package/src/services/vercel-deploy.ts +0 -24
  304. package/src/signals/confirm.ts +78 -0
  305. package/src/signals/mcp-reload.ts +18 -0
  306. package/src/skills/catalog-install.ts +74 -18
  307. package/src/skills/skillssh-registry.ts +503 -0
  308. package/src/tools/assets/search.ts +5 -1
  309. package/src/tools/computer-use/definitions.ts +0 -10
  310. package/src/tools/computer-use/registry.ts +1 -1
  311. package/src/tools/credentials/vault.ts +22 -7
  312. package/src/tools/memory/definitions.ts +4 -13
  313. package/src/tools/memory/handlers.test.ts +83 -103
  314. package/src/tools/memory/handlers.ts +50 -85
  315. package/src/tools/network/script-proxy/session-manager.ts +8 -8
  316. package/src/tools/schedule/create.ts +10 -3
  317. package/src/tools/schedule/update.ts +8 -1
  318. package/src/tools/skills/load.ts +25 -2
  319. package/src/watcher/provider-types.ts +1 -1
  320. package/src/watcher/providers/github.ts +1 -1
  321. package/src/watcher/providers/gmail.ts +3 -3
  322. package/src/watcher/providers/google-calendar.ts +3 -3
  323. package/src/watcher/providers/linear.ts +1 -1
  324. package/src/__tests__/clarification-resolver.test.ts +0 -193
  325. package/src/__tests__/conflict-intent-tokenization.test.ts +0 -160
  326. package/src/__tests__/conflict-policy.test.ts +0 -269
  327. package/src/__tests__/conflict-store.test.ts +0 -372
  328. package/src/__tests__/contradiction-checker.test.ts +0 -361
  329. package/src/__tests__/entity-extractor.test.ts +0 -211
  330. package/src/__tests__/entity-search.test.ts +0 -1117
  331. package/src/__tests__/profile-compiler.test.ts +0 -392
  332. package/src/__tests__/session-conflict-gate.test.ts +0 -1228
  333. package/src/__tests__/session-profile-injection.test.ts +0 -557
  334. package/src/config/bundled-skills/knowledge-graph/SKILL.md +0 -25
  335. package/src/config/bundled-skills/knowledge-graph/TOOLS.json +0 -66
  336. package/src/config/bundled-skills/knowledge-graph/tools/graph-query.ts +0 -211
  337. package/src/daemon/session-conflict-gate.ts +0 -167
  338. package/src/daemon/session-dynamic-profile.ts +0 -77
  339. package/src/memory/clarification-resolver.ts +0 -417
  340. package/src/memory/conflict-intent.ts +0 -205
  341. package/src/memory/conflict-policy.ts +0 -127
  342. package/src/memory/conflict-store.ts +0 -410
  343. package/src/memory/contradiction-checker.ts +0 -508
  344. package/src/memory/entity-extractor.ts +0 -535
  345. package/src/memory/format-recall.ts +0 -47
  346. package/src/memory/fts-reconciler.ts +0 -165
  347. package/src/memory/job-handlers/conflict.ts +0 -200
  348. package/src/memory/profile-compiler.ts +0 -195
  349. package/src/memory/recall-cache.ts +0 -117
  350. package/src/memory/search/entity.ts +0 -535
  351. package/src/memory/search/query-expansion.test.ts +0 -70
  352. package/src/memory/search/query-expansion.ts +0 -118
  353. package/src/runtime/routes/mcp-routes.ts +0 -20
@@ -1,18 +1,30 @@
1
1
  import type { Command } from "commander";
2
2
 
3
+ import { orchestrateOAuthConnect } from "../../../oauth/connect-orchestrator.js";
3
4
  import {
4
5
  disconnectOAuthProvider,
6
+ getAppByProviderAndClientId,
5
7
  getConnection,
6
8
  getConnectionByProvider,
9
+ getMostRecentAppByProvider,
10
+ getProvider,
7
11
  listConnections,
8
12
  } from "../../../oauth/oauth-store.js";
13
+ import {
14
+ getProviderBehavior,
15
+ resolveService,
16
+ } from "../../../oauth/provider-behaviors.js";
9
17
  import { credentialKey } from "../../../security/credential-key.js";
10
- import { deleteSecureKeyAsync } from "../../../security/secure-keys.js";
18
+ import {
19
+ deleteSecureKeyAsync,
20
+ getSecureKey,
21
+ } from "../../../security/secure-keys.js";
11
22
  import { withValidToken } from "../../../security/token-manager.js";
12
23
  import {
13
24
  assertMetadataWritable,
14
25
  deleteCredentialMetadata,
15
26
  } from "../../../tools/credentials/metadata-store.js";
27
+ import { isLinux, isMacOS } from "../../../util/platform.js";
16
28
  import { getCliLogger } from "../../logger.js";
17
29
  import { shouldOutputJson, writeOutput } from "../../output.js";
18
30
 
@@ -35,7 +47,13 @@ function redactMetadata(obj: Record<string, unknown>): Record<string, unknown> {
35
47
  for (const [key, value] of Object.entries(obj)) {
36
48
  if (REDACTED_METADATA_KEYS.has(key)) {
37
49
  result[key] = "[REDACTED]";
38
- } else if (value && typeof value === "object" && !Array.isArray(value)) {
50
+ } else if (Array.isArray(value)) {
51
+ result[key] = value.map((item) =>
52
+ item && typeof item === "object" && !Array.isArray(item)
53
+ ? redactMetadata(item as Record<string, unknown>)
54
+ : item,
55
+ );
56
+ } else if (value && typeof value === "object") {
39
57
  result[key] = redactMetadata(value as Record<string, unknown>);
40
58
  } else {
41
59
  result[key] = value;
@@ -73,10 +91,16 @@ token expiry, refresh token availability, account info, and status.
73
91
 
74
92
  Examples:
75
93
  $ assistant oauth connections list
76
- $ assistant oauth connections list --provider integration:gmail
94
+ $ assistant oauth connections list --provider integration:google
95
+ $ assistant oauth connections list --client-id abc123
77
96
  $ assistant oauth connections get --id <uuid>
78
- $ assistant oauth connections get --provider integration:gmail
79
- $ assistant oauth connections token integration:twitter`,
97
+ $ assistant oauth connections get --provider integration:google
98
+ $ assistant oauth connections get --provider integration:google --client-id abc123
99
+ $ assistant oauth connections token integration:twitter
100
+ $ assistant oauth connections ping integration:google
101
+ $ assistant oauth connections connect integration:google
102
+ $ assistant oauth connections connect integration:google --open-browser
103
+ $ assistant oauth connections disconnect integration:google`,
80
104
  );
81
105
 
82
106
  // ---------------------------------------------------------------------------
@@ -88,23 +112,27 @@ Examples:
88
112
  .description("List all OAuth connections")
89
113
  .option(
90
114
  "--provider <key>",
91
- "Filter by provider key (e.g. integration:gmail)",
115
+ "Filter by provider key (e.g. integration:google)",
92
116
  )
117
+ .option("--client-id <id>", "Filter by OAuth client ID")
93
118
  .addHelpText(
94
119
  "after",
95
120
  `
96
- Lists all OAuth connections, optionally filtered by provider key.
121
+ Lists all OAuth connections, optionally filtered by provider key and/or client ID.
97
122
 
98
123
  Each connection shows its ID, provider, account info, granted scopes, token
99
124
  expiry, refresh token availability, and status.
100
125
 
101
126
  Examples:
102
127
  $ assistant oauth connections list
103
- $ assistant oauth connections list --provider integration:gmail`,
128
+ $ assistant oauth connections list --provider integration:google
129
+ $ assistant oauth connections list --client-id abc123`,
104
130
  )
105
- .action((opts: { provider?: string }, cmd: Command) => {
131
+ .action((opts: { provider?: string; clientId?: string }, cmd: Command) => {
106
132
  try {
107
- const rows = listConnections(opts.provider).map(formatConnectionRow);
133
+ const rows = listConnections(opts.provider, opts.clientId).map(
134
+ formatConnectionRow,
135
+ );
108
136
 
109
137
  if (!shouldOutputJson(cmd)) {
110
138
  log.info(`Found ${rows.length} connection(s)`);
@@ -130,6 +158,10 @@ Examples:
130
158
  "--provider <key>",
131
159
  "Provider key (returns most recent active connection)",
132
160
  )
161
+ .option(
162
+ "--client-id <id>",
163
+ "Filter by OAuth client ID (used with --provider)",
164
+ )
133
165
  .addHelpText(
134
166
  "after",
135
167
  `
@@ -139,40 +171,46 @@ Two lookup modes are supported:
139
171
  $ assistant oauth connections get --id <uuid>
140
172
 
141
173
  2. By provider (returns the most recent active connection):
142
- $ assistant oauth connections get --provider integration:gmail
174
+ $ assistant oauth connections get --provider integration:google
175
+ $ assistant oauth connections get --provider integration:google --client-id abc123
143
176
 
144
177
  At least --id or --provider must be specified.`,
145
178
  )
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
- });
179
+ .action(
180
+ (
181
+ opts: { id?: string; provider?: string; clientId?: string },
182
+ cmd: Command,
183
+ ) => {
184
+ try {
185
+ let row;
186
+
187
+ if (opts.id) {
188
+ row = getConnection(opts.id);
189
+ } else if (opts.provider) {
190
+ row = getConnectionByProvider(opts.provider, opts.clientId);
191
+ } else {
192
+ writeOutput(cmd, {
193
+ ok: false,
194
+ error: "Provide --id or --provider",
195
+ });
196
+ process.exitCode = 1;
197
+ return;
198
+ }
199
+
200
+ if (!row) {
201
+ writeOutput(cmd, { ok: false, error: "Connection not found" });
202
+ process.exitCode = 1;
203
+ return;
204
+ }
205
+
206
+ writeOutput(cmd, formatConnectionRow(row));
207
+ } catch (err) {
208
+ const message = err instanceof Error ? err.message : String(err);
209
+ writeOutput(cmd, { ok: false, error: message });
159
210
  process.exitCode = 1;
160
- return;
161
211
  }
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
- });
212
+ },
213
+ );
176
214
 
177
215
  // ---------------------------------------------------------------------------
178
216
  // connections token <provider-key>
@@ -183,11 +221,15 @@ At least --id or --provider must be specified.`,
183
221
  .description(
184
222
  "Print a valid OAuth access token for a provider, refreshing if expired",
185
223
  )
224
+ .option(
225
+ "--client-id <id>",
226
+ "Filter by OAuth client ID when multiple apps exist for the provider",
227
+ )
186
228
  .addHelpText(
187
229
  "after",
188
230
  `
189
231
  Arguments:
190
- provider-key Provider key (e.g. integration:gmail, integration:twitter)
232
+ provider-key Provider key (e.g. integration:google, integration:twitter)
191
233
 
192
234
  Returns a valid OAuth access token for the given provider. If the stored token
193
235
  is expired or near-expiry, it is refreshed automatically before being returned.
@@ -199,22 +241,156 @@ Exits with code 1 if no access token exists or refresh fails.
199
241
 
200
242
  Examples:
201
243
  $ assistant oauth connections token integration:twitter
202
- $ assistant oauth connections token integration:gmail --json`,
244
+ $ assistant oauth connections token integration:google --json
245
+ $ assistant oauth connections token integration:google --client-id abc123`,
203
246
  )
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");
247
+ .action(
248
+ async (
249
+ providerKey: string,
250
+ opts: { clientId?: string },
251
+ cmd: Command,
252
+ ) => {
253
+ try {
254
+ const token = await withValidToken(
255
+ providerKey,
256
+ async (t) => t,
257
+ opts.clientId,
258
+ );
259
+ if (shouldOutputJson(cmd)) {
260
+ writeOutput(cmd, { ok: true, token });
261
+ } else {
262
+ process.stdout.write(token + "\n");
263
+ }
264
+ } catch (err) {
265
+ const message = err instanceof Error ? err.message : String(err);
266
+ writeOutput(cmd, { ok: false, error: message });
267
+ process.exitCode = 1;
211
268
  }
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
- });
269
+ },
270
+ );
271
+
272
+ // ---------------------------------------------------------------------------
273
+ // connections ping <provider-key>
274
+ // ---------------------------------------------------------------------------
275
+
276
+ connections
277
+ .command("ping <provider-key>")
278
+ .description(
279
+ "Verify that a stored OAuth token is still valid by hitting the provider's health-check endpoint",
280
+ )
281
+ .option(
282
+ "--client-id <id>",
283
+ "Filter by OAuth client ID when multiple apps exist for the provider",
284
+ )
285
+ .addHelpText(
286
+ "after",
287
+ `
288
+ Arguments:
289
+ provider-key Provider key (e.g. integration:google, integration:twitter)
290
+
291
+ Fetches a valid access token (refreshing if needed) and sends a GET request
292
+ to the provider's configured ping URL. Reports success (HTTP 2xx) or failure.
293
+
294
+ The ping URL is set per-provider in seed data or via "providers register --ping-url".
295
+ If no ping URL is configured for the provider, exits with an error.
296
+
297
+ Examples:
298
+ $ assistant oauth connections ping integration:google
299
+ $ assistant oauth connections ping integration:twitter --json
300
+ $ assistant oauth connections ping integration:google --client-id abc123`,
301
+ )
302
+ .action(
303
+ async (
304
+ providerKey: string,
305
+ opts: { clientId?: string },
306
+ cmd: Command,
307
+ ) => {
308
+ try {
309
+ const provider = getProvider(providerKey);
310
+ if (!provider) {
311
+ writeOutput(cmd, {
312
+ ok: false,
313
+ error: `Provider not found: ${providerKey}`,
314
+ });
315
+ process.exitCode = 1;
316
+ return;
317
+ }
318
+
319
+ if (!provider.pingUrl) {
320
+ writeOutput(cmd, {
321
+ ok: false,
322
+ error: `No ping URL configured for "${providerKey}"`,
323
+ });
324
+ process.exitCode = 1;
325
+ return;
326
+ }
327
+
328
+ const pingUrl = provider.pingUrl;
329
+
330
+ const PING_TIMEOUT_MS = 15_000;
331
+
332
+ const result = await withValidToken(
333
+ providerKey,
334
+ async (token) => {
335
+ const controller = new AbortController();
336
+ const timer = setTimeout(
337
+ () => controller.abort(),
338
+ PING_TIMEOUT_MS,
339
+ );
340
+ try {
341
+ const res = await fetch(pingUrl, {
342
+ method: "GET",
343
+ headers: { Authorization: `Bearer ${token}` },
344
+ signal: controller.signal,
345
+ });
346
+
347
+ if (res.status === 401) {
348
+ const err = new Error(
349
+ `Ping returned HTTP 401 from ${pingUrl}`,
350
+ );
351
+ (err as unknown as { status: number }).status = 401;
352
+ throw err;
353
+ }
354
+
355
+ return { status: res.status, ok: res.ok };
356
+ } finally {
357
+ clearTimeout(timer);
358
+ }
359
+ },
360
+ opts.clientId,
361
+ );
362
+
363
+ if (result.ok) {
364
+ if (shouldOutputJson(cmd)) {
365
+ writeOutput(cmd, {
366
+ ok: true,
367
+ provider: providerKey,
368
+ status: result.status,
369
+ });
370
+ } else {
371
+ log.info(`${providerKey}: OK (HTTP ${result.status})`);
372
+ writeOutput(cmd, {
373
+ ok: true,
374
+ provider: providerKey,
375
+ status: result.status,
376
+ });
377
+ }
378
+ } else {
379
+ writeOutput(cmd, {
380
+ ok: false,
381
+ provider: providerKey,
382
+ status: result.status,
383
+ error: `Ping failed with HTTP ${result.status}`,
384
+ });
385
+ process.exitCode = 1;
386
+ }
387
+ } catch (err) {
388
+ const message = err instanceof Error ? err.message : String(err);
389
+ writeOutput(cmd, { ok: false, error: message });
390
+ process.exitCode = 1;
391
+ }
392
+ },
393
+ );
218
394
 
219
395
  // ---------------------------------------------------------------------------
220
396
  // connections disconnect <provider-key>
@@ -225,11 +401,15 @@ Examples:
225
401
  .description(
226
402
  "Disconnect an OAuth integration and remove all associated credentials",
227
403
  )
404
+ .option(
405
+ "--client-id <id>",
406
+ "Filter by OAuth client ID when multiple apps exist for the provider",
407
+ )
228
408
  .addHelpText(
229
409
  "after",
230
410
  `
231
411
  Arguments:
232
- provider-key The full provider key (e.g. integration:gmail, integration:slack)
412
+ provider-key The full provider key (e.g. integration:google, integration:slack)
233
413
 
234
414
  Removes the OAuth connection, tokens, and any legacy credential metadata for
235
415
  the provider. The <provider-key> argument is the full provider key as-is — it
@@ -239,61 +419,251 @@ Legacy credential keys for common fields (access_token, refresh_token,
239
419
  client_id, client_secret) are also cleaned up if present.
240
420
 
241
421
  Examples:
242
- $ assistant oauth connections disconnect integration:gmail
243
- $ assistant oauth connections disconnect integration:slack`,
422
+ $ assistant oauth connections disconnect integration:google
423
+ $ assistant oauth connections disconnect integration:slack
424
+ $ assistant oauth connections disconnect integration:google --client-id abc123`,
244
425
  )
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
- });
426
+ .action(
427
+ async (
428
+ providerKey: string,
429
+ opts: { clientId?: string },
430
+ cmd: Command,
431
+ ) => {
432
+ try {
433
+ assertMetadataWritable();
434
+
435
+ let cleanedUp = false;
436
+
437
+ // 1. Disconnect the OAuth connection (new-format keys + connection row)
438
+ const oauthResult = await disconnectOAuthProvider(
439
+ providerKey,
440
+ opts.clientId,
441
+ );
442
+ if (oauthResult === "error") {
443
+ writeOutput(cmd, {
444
+ ok: false,
445
+ error: `Failed to disconnect OAuth provider "${providerKey}" — please try again`,
446
+ });
447
+ process.exitCode = 1;
448
+ return;
449
+ }
450
+ if (oauthResult === "disconnected") cleanedUp = true;
451
+
452
+ // 2. Clean up legacy credential keys for common fields
453
+ const legacyFields = [
454
+ "access_token",
455
+ "refresh_token",
456
+ "client_id",
457
+ "client_secret",
458
+ ];
459
+ for (const field of legacyFields) {
460
+ const key = credentialKey(providerKey, field);
461
+ const result = await deleteSecureKeyAsync(key);
462
+ if (result === "deleted") cleanedUp = true;
463
+
464
+ const metaDeleted = deleteCredentialMetadata(providerKey, field);
465
+ if (metaDeleted) cleanedUp = true;
466
+ }
467
+
468
+ if (!cleanedUp) {
469
+ writeOutput(cmd, {
470
+ ok: false,
471
+ error: `No OAuth connection or credentials found for "${providerKey}"`,
472
+ });
473
+ process.exitCode = 1;
474
+ return;
475
+ }
476
+
477
+ writeOutput(cmd, { ok: true, service: providerKey });
478
+
479
+ if (!shouldOutputJson(cmd)) {
480
+ log.info(`Disconnected ${providerKey}`);
481
+ }
482
+ } catch (err) {
483
+ const message = err instanceof Error ? err.message : String(err);
484
+ writeOutput(cmd, { ok: false, error: message });
258
485
  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
486
  }
487
+ },
488
+ );
278
489
 
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
- }
490
+ // ---------------------------------------------------------------------------
491
+ // connections connect <provider-key>
492
+ // ---------------------------------------------------------------------------
493
+
494
+ connections
495
+ .command("connect <provider-key>")
496
+ .description("Initiate an OAuth2 authorization flow for a provider")
497
+ .option(
498
+ "--client-id <id>",
499
+ "Filter by OAuth client ID when multiple apps exist for the provider",
500
+ )
501
+ .option(
502
+ "--scopes <scopes...>",
503
+ "Additional scopes beyond the provider's defaults",
504
+ )
505
+ .option(
506
+ "--open-browser",
507
+ "Open the auth URL in the browser and wait for completion",
508
+ )
509
+ .addHelpText(
510
+ "after",
511
+ `
512
+ Arguments:
513
+ provider-key Provider key (e.g. integration:google) or alias (e.g. gmail)
287
514
 
288
- writeOutput(cmd, { ok: true, service: providerKey });
515
+ Initiates an OAuth2 authorization flow for the given provider. By default,
516
+ prints the authorization URL to stdout — useful for headless/remote sessions.
517
+ The token exchange completes in the background when the user authorizes.
289
518
 
290
- if (!shouldOutputJson(cmd)) {
291
- log.info(`Disconnected ${providerKey}`);
519
+ With --open-browser, opens the authorization URL in your browser and waits
520
+ for completion.
521
+
522
+ Client credentials are resolved from the OAuth app store. Use --client-id
523
+ to select a specific app when multiple apps exist for the same provider.
524
+
525
+ Examples:
526
+ $ assistant oauth connections connect integration:google
527
+ $ assistant oauth connections connect gmail --open-browser
528
+ $ assistant oauth connections connect integration:slack --client-id abc123
529
+ $ assistant oauth connections connect integration:google --scopes calendar.readonly --json`,
530
+ )
531
+ .action(
532
+ async (
533
+ providerKey: string,
534
+ opts: {
535
+ clientId?: string;
536
+ scopes?: string[];
537
+ openBrowser?: boolean;
538
+ },
539
+ cmd: Command,
540
+ ) => {
541
+ try {
542
+ // a. Resolve service alias
543
+ const resolvedServiceKey = resolveService(providerKey);
544
+
545
+ // b. Resolve client credentials from the DB
546
+ const dbApp = opts.clientId
547
+ ? getAppByProviderAndClientId(resolvedServiceKey, opts.clientId)
548
+ : getMostRecentAppByProvider(resolvedServiceKey);
549
+
550
+ let clientId = opts.clientId;
551
+ let clientSecret: string | undefined;
552
+
553
+ if (dbApp) {
554
+ if (!clientId) clientId = dbApp.clientId;
555
+ const storedSecret = getSecureKey(dbApp.clientSecretCredentialPath);
556
+ if (storedSecret) clientSecret = storedSecret;
557
+ } else if (opts.clientId) {
558
+ // --client-id was explicitly provided but no matching app exists
559
+ writeOutput(cmd, {
560
+ ok: false,
561
+ error: `No registered app found for "${resolvedServiceKey}" with client ID "${opts.clientId}". Register it first with 'assistant oauth apps upsert --client-id ${opts.clientId}'.`,
562
+ });
563
+ process.exitCode = 1;
564
+ return;
565
+ }
566
+
567
+ // c. Validate client_id
568
+ if (!clientId) {
569
+ writeOutput(cmd, {
570
+ ok: false,
571
+ error:
572
+ "No client_id found. Provide --client-id or register an app first with 'assistant oauth apps upsert'.",
573
+ });
574
+ process.exitCode = 1;
575
+ return;
576
+ }
577
+
578
+ // d. Check if client_secret is required but missing
579
+ if (clientSecret === undefined) {
580
+ const providerRow = getProvider(resolvedServiceKey);
581
+ const behavior = getProviderBehavior(resolvedServiceKey);
582
+
583
+ const requiresSecret =
584
+ behavior?.setup?.requiresClientSecret ??
585
+ !!(
586
+ providerRow?.tokenEndpointAuthMethod || providerRow?.extraParams
587
+ );
588
+
589
+ if (requiresSecret) {
590
+ writeOutput(cmd, {
591
+ ok: false,
592
+ error: `client_secret is required for ${resolvedServiceKey} but not found. Store it first with 'assistant oauth apps upsert --client-secret'.`,
593
+ });
594
+ process.exitCode = 1;
595
+ return;
596
+ }
597
+ }
598
+
599
+ // e. Call the orchestrator
600
+ const result = await orchestrateOAuthConnect({
601
+ service: providerKey,
602
+ clientId,
603
+ clientSecret,
604
+ isInteractive: !!opts.openBrowser,
605
+ openUrl: opts.openBrowser
606
+ ? (url) => {
607
+ if (isMacOS()) {
608
+ Bun.spawn(["open", url], {
609
+ stdout: "ignore",
610
+ stderr: "ignore",
611
+ });
612
+ } else if (isLinux()) {
613
+ Bun.spawn(["xdg-open", url], {
614
+ stdout: "ignore",
615
+ stderr: "ignore",
616
+ });
617
+ } else {
618
+ // Fallback: print URL for manual opening (stderr to keep --json stdout clean)
619
+ process.stderr.write(
620
+ `Open this URL to authorize:\n\n${url}\n`,
621
+ );
622
+ }
623
+ }
624
+ : undefined,
625
+ ...(opts.scopes ? { requestedScopes: opts.scopes } : {}),
626
+ });
627
+
628
+ // f. Handle results
629
+ if (!result.success) {
630
+ writeOutput(cmd, { ok: false, error: result.error });
631
+ process.exitCode = 1;
632
+ return;
633
+ }
634
+
635
+ if (result.deferred) {
636
+ if (shouldOutputJson(cmd)) {
637
+ writeOutput(cmd, {
638
+ ok: true,
639
+ deferred: true,
640
+ authUrl: result.authUrl,
641
+ service: result.service,
642
+ });
643
+ } else {
644
+ process.stdout.write(
645
+ `Open this URL to authorize:\n\n${result.authUrl}\n\nThe connection will complete automatically once you authorize.\n`,
646
+ );
647
+ }
648
+ return;
649
+ }
650
+
651
+ // Interactive mode completed
652
+ if (shouldOutputJson(cmd)) {
653
+ writeOutput(cmd, {
654
+ ok: true,
655
+ grantedScopes: result.grantedScopes,
656
+ accountInfo: result.accountInfo,
657
+ });
658
+ } else {
659
+ const msg = `Connected to ${resolvedServiceKey}${result.accountInfo ? ` as ${result.accountInfo}` : ""}`;
660
+ process.stdout.write(msg + "\n");
661
+ }
662
+ } catch (err) {
663
+ const message = err instanceof Error ? err.message : String(err);
664
+ writeOutput(cmd, { ok: false, error: message });
665
+ process.exitCode = 1;
292
666
  }
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
- });
667
+ },
668
+ );
299
669
  }