@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.
- package/ARCHITECTURE.md +24 -33
- package/README.md +3 -3
- package/docs/architecture/integrations.md +2 -2
- package/docs/architecture/keychain-broker.md +6 -6
- package/docs/architecture/memory.md +180 -119
- package/knip.json +32 -0
- package/package.json +3 -2
- package/src/__tests__/agent-loop.test.ts +3 -1
- package/src/__tests__/anthropic-provider.test.ts +114 -23
- package/src/__tests__/approval-cascade.test.ts +1 -15
- package/src/__tests__/approval-routes-http.test.ts +2 -0
- package/src/__tests__/assistant-feature-flag-guard.test.ts +0 -23
- package/src/__tests__/btw-routes.test.ts +61 -5
- package/src/__tests__/canonical-guardian-store.test.ts +95 -0
- package/src/__tests__/checker.test.ts +13 -0
- package/src/__tests__/config-schema.test.ts +1 -68
- package/src/__tests__/config-watcher.test.ts +8 -0
- package/src/__tests__/context-memory-e2e.test.ts +11 -100
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +8 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
- package/src/__tests__/credential-security-e2e.test.ts +1 -0
- package/src/__tests__/credential-security-invariants.test.ts +8 -7
- package/src/__tests__/credential-vault-unit.test.ts +23 -18
- package/src/__tests__/credential-vault.test.ts +30 -18
- package/src/__tests__/credentials-cli.test.ts +257 -82
- package/src/__tests__/cu-unified-flow.test.ts +532 -0
- package/src/__tests__/date-context.test.ts +93 -77
- package/src/__tests__/deterministic-verification-control-plane.test.ts +64 -0
- package/src/__tests__/guardian-routing-invariants.test.ts +93 -0
- package/src/__tests__/history-repair.test.ts +245 -0
- package/src/__tests__/host-cu-proxy.test.ts +165 -3
- package/src/__tests__/http-user-message-parity.test.ts +1 -0
- package/src/__tests__/inbound-invite-redemption.test.ts +36 -7
- package/src/__tests__/integration-status.test.ts +31 -30
- package/src/__tests__/invite-redemption-service.test.ts +166 -13
- package/src/__tests__/invite-routes-http.test.ts +166 -5
- package/src/__tests__/keychain-broker-client.test.ts +4 -4
- package/src/__tests__/list-messages-attachments.test.ts +193 -0
- package/src/__tests__/memory-context-benchmark.benchmark.test.ts +56 -18
- package/src/__tests__/memory-lifecycle-e2e.test.ts +244 -387
- package/src/__tests__/memory-recall-quality.test.ts +244 -407
- package/src/__tests__/memory-regressions.experimental.test.ts +126 -101
- package/src/__tests__/memory-regressions.test.ts +477 -2841
- package/src/__tests__/memory-retrieval.benchmark.test.ts +33 -150
- package/src/__tests__/memory-upsert-concurrency.test.ts +5 -244
- package/src/__tests__/mime-builder.test.ts +28 -0
- package/src/__tests__/native-web-search.test.ts +1 -0
- package/src/__tests__/oauth-cli.test.ts +824 -31
- package/src/__tests__/oauth-provider-profiles.test.ts +1 -1
- package/src/__tests__/oauth-store.test.ts +363 -17
- package/src/__tests__/qdrant-collection-migration.test.ts +53 -8
- package/src/__tests__/registry.test.ts +0 -1
- package/src/__tests__/relay-server.test.ts +55 -1
- package/src/__tests__/schedule-tools.test.ts +32 -0
- package/src/__tests__/script-proxy-certs.test.ts +1 -1
- package/src/__tests__/secret-onetime-send.test.ts +1 -0
- package/src/__tests__/secret-routes-managed-proxy.test.ts +183 -0
- package/src/__tests__/secure-keys.test.ts +78 -18
- package/src/__tests__/send-endpoint-busy.test.ts +3 -0
- package/src/__tests__/server-history-render.test.ts +2 -2
- package/src/__tests__/session-abort-tool-results.test.ts +1 -14
- package/src/__tests__/session-agent-loop-overflow.test.ts +1583 -0
- package/src/__tests__/session-agent-loop.test.ts +19 -15
- package/src/__tests__/session-confirmation-signals.test.ts +1 -15
- package/src/__tests__/session-error.test.ts +124 -2
- package/src/__tests__/session-history-web-search.test.ts +918 -0
- package/src/__tests__/session-pre-run-repair.test.ts +1 -14
- package/src/__tests__/session-provider-retry-repair.test.ts +25 -28
- package/src/__tests__/session-queue.test.ts +37 -27
- package/src/__tests__/session-runtime-assembly.test.ts +54 -0
- package/src/__tests__/session-slash-known.test.ts +1 -15
- package/src/__tests__/session-slash-queue.test.ts +1 -15
- package/src/__tests__/session-slash-unknown.test.ts +1 -15
- package/src/__tests__/session-workspace-cache-state.test.ts +3 -33
- package/src/__tests__/session-workspace-injection.test.ts +3 -37
- package/src/__tests__/session-workspace-tool-tracking.test.ts +3 -37
- package/src/__tests__/skills-install-extract.test.ts +93 -0
- package/src/__tests__/skills.test.ts +2 -2
- package/src/__tests__/skillssh-registry.test.ts +451 -0
- package/src/__tests__/slack-channel-config.test.ts +10 -8
- package/src/__tests__/trust-store.test.ts +15 -0
- package/src/__tests__/twilio-config.test.ts +11 -10
- package/src/__tests__/twilio-provider.test.ts +9 -4
- package/src/__tests__/voice-invite-redemption.test.ts +85 -5
- package/src/agent/ax-tree-compaction.test.ts +51 -0
- package/src/agent/loop.ts +39 -12
- package/src/approvals/AGENTS.md +1 -1
- package/src/approvals/guardian-request-resolvers.ts +14 -2
- package/src/bundler/compiler-tools.ts +66 -2
- package/src/calls/call-domain.ts +134 -3
- package/src/calls/call-store.ts +6 -0
- package/src/calls/relay-server.ts +44 -6
- package/src/calls/relay-setup-router.ts +17 -1
- package/src/calls/twilio-config.ts +5 -4
- package/src/calls/twilio-provider.ts +14 -9
- package/src/calls/twilio-rest.ts +10 -7
- package/src/calls/types.ts +3 -1
- package/src/cli/commands/config.ts +14 -9
- package/src/cli/commands/contacts.ts +3 -0
- package/src/cli/commands/credentials.ts +170 -174
- package/src/cli/commands/doctor.ts +11 -8
- package/src/cli/commands/keys.ts +9 -9
- package/src/cli/commands/mcp.ts +46 -59
- package/src/cli/commands/memory.ts +16 -165
- package/src/cli/commands/oauth/apps.ts +68 -10
- package/src/cli/commands/oauth/connections.ts +475 -105
- package/src/cli/commands/oauth/index.ts +3 -3
- package/src/cli/commands/oauth/providers.ts +18 -4
- package/src/cli/commands/sessions.ts +5 -2
- package/src/cli/commands/skills.ts +173 -1
- package/src/cli/http-client.ts +0 -20
- package/src/cli/main-screen.tsx +2 -2
- package/src/cli/program.ts +5 -6
- package/src/cli.ts +20 -22
- package/src/config/__tests__/feature-flag-registry-bundled.test.ts +39 -0
- package/src/config/bundled-skills/computer-use/TOOLS.json +1 -1
- package/src/config/bundled-skills/computer-use/tools/computer-use-observe.ts +12 -0
- package/src/config/bundled-skills/contacts/SKILL.md +35 -11
- package/src/config/bundled-skills/contacts/tools/google-contacts.ts +1 -1
- package/src/config/bundled-skills/gmail/SKILL.md +1 -1
- package/src/config/bundled-skills/gmail/TOOLS.json +52 -0
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +13 -3
- package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +9 -2
- package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-label.ts +9 -2
- package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +5 -1
- package/src/config/bundled-skills/google-calendar/TOOLS.json +20 -0
- package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +2 -1
- package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +2 -1
- package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +2 -1
- package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +2 -1
- package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +2 -1
- package/src/config/bundled-skills/google-calendar/tools/shared.ts +8 -2
- package/src/config/bundled-skills/messaging/SKILL.md +1 -1
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-auth-test.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-list-conversations.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-mark-read.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-read.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-search.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-sender-digest.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/shared.ts +7 -5
- package/src/config/bundled-skills/slack/tools/shared.ts +1 -1
- package/src/config/bundled-skills/slack/tools/slack-add-reaction.ts +1 -1
- package/src/config/bundled-skills/slack/tools/slack-channel-details.ts +1 -1
- package/src/config/bundled-skills/slack/tools/slack-delete-message.ts +1 -1
- package/src/config/bundled-skills/slack/tools/slack-edit-message.ts +1 -1
- package/src/config/bundled-skills/slack/tools/slack-leave-channel.ts +1 -1
- package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +1 -1
- package/src/config/bundled-tool-registry.ts +2 -5
- package/src/config/loader.ts +6 -42
- package/src/config/schema.ts +1 -12
- package/src/config/schemas/memory-lifecycle.ts +0 -9
- package/src/config/schemas/memory-processing.ts +0 -180
- package/src/config/schemas/memory-retrieval.ts +32 -104
- package/src/config/schemas/memory.ts +0 -10
- package/src/config/types.ts +0 -4
- package/src/contacts/contact-store.ts +39 -2
- package/src/contacts/contacts-write.ts +9 -0
- package/src/context/window-manager.ts +4 -1
- package/src/daemon/config-watcher.ts +55 -2
- package/src/daemon/daemon-control.ts +1 -1
- package/src/daemon/date-context.ts +114 -31
- package/src/daemon/handlers/config-ingress.ts +2 -2
- package/src/daemon/handlers/config-slack-channel.ts +59 -39
- package/src/daemon/handlers/config-telegram.ts +23 -14
- package/src/daemon/handlers/session-history.ts +1 -358
- package/src/daemon/handlers/sessions.ts +18 -13
- package/src/daemon/handlers/shared.ts +3 -17
- package/src/daemon/handlers/skills.ts +20 -1
- package/src/daemon/history-repair.ts +72 -8
- package/src/daemon/host-cu-proxy.ts +55 -26
- package/src/daemon/lifecycle.ts +39 -4
- package/src/daemon/mcp-reload-service.ts +2 -2
- package/src/daemon/message-types/computer-use.ts +1 -12
- package/src/daemon/message-types/memory.ts +4 -16
- package/src/daemon/message-types/messages.ts +1 -0
- package/src/daemon/message-types/sessions.ts +4 -42
- package/src/daemon/server.ts +6 -1
- package/src/daemon/session-agent-loop-handlers.ts +38 -0
- package/src/daemon/session-agent-loop.ts +334 -48
- package/src/daemon/session-error.ts +89 -6
- package/src/daemon/session-history.ts +17 -7
- package/src/daemon/session-media-retry.ts +6 -2
- package/src/daemon/session-memory.ts +69 -149
- package/src/daemon/session-process.ts +10 -1
- package/src/daemon/session-runtime-assembly.ts +49 -19
- package/src/daemon/session-slash.ts +3 -5
- package/src/daemon/session-surfaces.ts +4 -1
- package/src/daemon/session-tool-setup.ts +7 -1
- package/src/daemon/session.ts +12 -2
- package/src/email/providers/index.ts +2 -2
- package/src/instrument.ts +61 -1
- package/src/media/avatar-router.ts +1 -1
- package/src/memory/admin.ts +2 -191
- package/src/memory/canonical-guardian-store.ts +38 -2
- package/src/memory/conversation-crud.ts +0 -33
- package/src/memory/conversation-queries.ts +25 -83
- package/src/memory/db-init.ts +32 -0
- package/src/memory/embedding-backend.ts +84 -8
- package/src/memory/embedding-types.ts +9 -1
- package/src/memory/indexer.ts +7 -46
- package/src/memory/invite-store.ts +19 -0
- package/src/memory/items-extractor.ts +274 -76
- package/src/memory/job-handlers/backfill.ts +2 -127
- package/src/memory/job-handlers/cleanup.ts +2 -16
- package/src/memory/job-handlers/extraction.ts +2 -138
- package/src/memory/job-handlers/index-maintenance.ts +1 -6
- package/src/memory/job-handlers/summarization.ts +3 -148
- package/src/memory/job-utils.ts +21 -59
- package/src/memory/jobs-store.ts +1 -159
- package/src/memory/jobs-worker.ts +9 -52
- package/src/memory/migrations/104-core-indexes.ts +3 -3
- package/src/memory/migrations/149-oauth-tables.ts +2 -0
- package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +98 -0
- package/src/memory/migrations/151-oauth-providers-ping-url.ts +11 -0
- package/src/memory/migrations/152-memory-item-supersession.ts +44 -0
- package/src/memory/migrations/153-drop-entity-tables.ts +15 -0
- package/src/memory/migrations/154-drop-fts.ts +20 -0
- package/src/memory/migrations/155-drop-conflicts.ts +7 -0
- package/src/memory/migrations/156-call-session-invite-metadata.ts +24 -0
- package/src/memory/migrations/157-invite-contact-id.ts +104 -0
- package/src/memory/migrations/index.ts +8 -0
- package/src/memory/migrations/registry.ts +6 -0
- package/src/memory/qdrant-client.ts +148 -51
- package/src/memory/raw-query.ts +1 -1
- package/src/memory/retriever.test.ts +294 -273
- package/src/memory/retriever.ts +421 -645
- package/src/memory/schema/calls.ts +2 -0
- package/src/memory/schema/contacts.ts +1 -0
- package/src/memory/schema/memory-core.ts +3 -48
- package/src/memory/schema/oauth.ts +2 -0
- package/src/memory/search/formatting.ts +263 -176
- package/src/memory/search/lexical.ts +1 -254
- package/src/memory/search/ranking.ts +0 -455
- package/src/memory/search/semantic.ts +100 -14
- package/src/memory/search/staleness.ts +47 -0
- package/src/memory/search/tier-classifier.ts +21 -0
- package/src/memory/search/types.ts +15 -77
- package/src/memory/task-memory-cleanup.ts +4 -6
- package/src/messaging/provider.ts +1 -1
- package/src/messaging/providers/gmail/adapter.ts +1 -1
- package/src/messaging/providers/gmail/mime-builder.ts +17 -7
- package/src/messaging/providers/telegram-bot/adapter.ts +17 -8
- package/src/messaging/providers/whatsapp/adapter.ts +13 -9
- package/src/messaging/registry.ts +9 -5
- package/src/oauth/byo-connection.test.ts +40 -25
- package/src/oauth/connect-orchestrator.ts +4 -10
- package/src/oauth/connection-resolver.ts +20 -6
- package/src/oauth/manual-token-connection.ts +5 -5
- package/src/oauth/oauth-store.ts +183 -31
- package/src/oauth/platform-connection.test.ts +1 -1
- package/src/oauth/provider-behaviors.ts +503 -4
- package/src/oauth/seed-providers.ts +214 -8
- package/src/oauth/token-persistence.ts +31 -16
- package/src/permissions/defaults.ts +1 -0
- package/src/permissions/trust-store.ts +23 -1
- package/src/playbooks/playbook-compiler.ts +1 -1
- package/src/prompts/system-prompt.ts +18 -2
- package/src/providers/anthropic/client.ts +56 -126
- package/src/providers/types.ts +7 -1
- package/src/runtime/AGENTS.md +9 -0
- package/src/runtime/auth/route-policy.ts +6 -3
- package/src/runtime/channel-readiness-service.ts +48 -40
- package/src/runtime/guardian-reply-router.ts +24 -22
- package/src/runtime/http-server.ts +2 -2
- package/src/runtime/http-types.ts +2 -0
- package/src/runtime/invite-redemption-service.ts +72 -12
- package/src/runtime/invite-service.ts +43 -0
- package/src/runtime/middleware/twilio-validation.ts +1 -1
- package/src/runtime/pending-interactions.ts +2 -2
- package/src/runtime/routes/brain-graph-routes.ts +10 -90
- package/src/runtime/routes/btw-routes.ts +10 -5
- package/src/runtime/routes/conversation-routes.ts +56 -11
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +21 -12
- package/src/runtime/routes/integrations/slack/channel.ts +2 -2
- package/src/runtime/routes/integrations/telegram.ts +2 -2
- package/src/runtime/routes/integrations/twilio.ts +17 -17
- package/src/runtime/routes/invite-routes.ts +29 -4
- package/src/runtime/routes/memory-item-routes.test.ts +754 -0
- package/src/runtime/routes/memory-item-routes.ts +503 -0
- package/src/runtime/routes/secret-routes.ts +17 -0
- package/src/runtime/routes/session-management-routes.ts +3 -3
- package/src/runtime/routes/settings-routes.ts +3 -3
- package/src/runtime/routes/trust-rules-routes.ts +14 -0
- package/src/runtime/routes/workspace-routes.ts +9 -4
- package/src/runtime/routes/workspace-utils.ts +8 -2
- package/src/schedule/integration-status.ts +26 -19
- package/src/security/keychain-broker-client.ts +17 -4
- package/src/security/oauth2.ts +6 -7
- package/src/security/secure-keys.ts +44 -19
- package/src/security/token-manager.ts +46 -39
- package/src/services/vercel-deploy.ts +0 -24
- package/src/signals/confirm.ts +78 -0
- package/src/signals/mcp-reload.ts +18 -0
- package/src/skills/catalog-install.ts +74 -18
- package/src/skills/skillssh-registry.ts +503 -0
- package/src/tools/assets/search.ts +5 -1
- package/src/tools/computer-use/definitions.ts +0 -10
- package/src/tools/computer-use/registry.ts +1 -1
- package/src/tools/credentials/vault.ts +22 -7
- package/src/tools/memory/definitions.ts +4 -13
- package/src/tools/memory/handlers.test.ts +83 -103
- package/src/tools/memory/handlers.ts +50 -85
- package/src/tools/network/script-proxy/session-manager.ts +8 -8
- package/src/tools/schedule/create.ts +10 -3
- package/src/tools/schedule/update.ts +8 -1
- package/src/tools/skills/load.ts +25 -2
- package/src/watcher/provider-types.ts +1 -1
- package/src/watcher/providers/github.ts +1 -1
- package/src/watcher/providers/gmail.ts +3 -3
- package/src/watcher/providers/google-calendar.ts +3 -3
- package/src/watcher/providers/linear.ts +1 -1
- package/src/__tests__/clarification-resolver.test.ts +0 -193
- package/src/__tests__/conflict-intent-tokenization.test.ts +0 -160
- package/src/__tests__/conflict-policy.test.ts +0 -269
- package/src/__tests__/conflict-store.test.ts +0 -372
- package/src/__tests__/contradiction-checker.test.ts +0 -361
- package/src/__tests__/entity-extractor.test.ts +0 -211
- package/src/__tests__/entity-search.test.ts +0 -1117
- package/src/__tests__/profile-compiler.test.ts +0 -392
- package/src/__tests__/session-conflict-gate.test.ts +0 -1228
- package/src/__tests__/session-profile-injection.test.ts +0 -557
- package/src/config/bundled-skills/knowledge-graph/SKILL.md +0 -25
- package/src/config/bundled-skills/knowledge-graph/TOOLS.json +0 -66
- package/src/config/bundled-skills/knowledge-graph/tools/graph-query.ts +0 -211
- package/src/daemon/session-conflict-gate.ts +0 -167
- package/src/daemon/session-dynamic-profile.ts +0 -77
- package/src/memory/clarification-resolver.ts +0 -417
- package/src/memory/conflict-intent.ts +0 -205
- package/src/memory/conflict-policy.ts +0 -127
- package/src/memory/conflict-store.ts +0 -410
- package/src/memory/contradiction-checker.ts +0 -508
- package/src/memory/entity-extractor.ts +0 -535
- package/src/memory/format-recall.ts +0 -47
- package/src/memory/fts-reconciler.ts +0 -165
- package/src/memory/job-handlers/conflict.ts +0 -200
- package/src/memory/profile-compiler.ts +0 -195
- package/src/memory/recall-cache.ts +0 -117
- package/src/memory/search/entity.ts +0 -535
- package/src/memory/search/query-expansion.test.ts +0 -70
- package/src/memory/search/query-expansion.ts +0 -118
- 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 {
|
|
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 (
|
|
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:
|
|
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:
|
|
79
|
-
$ assistant oauth connections
|
|
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:
|
|
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:
|
|
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(
|
|
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:
|
|
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(
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
244
|
+
$ assistant oauth connections token integration:google --json
|
|
245
|
+
$ assistant oauth connections token integration:google --client-id abc123`,
|
|
203
246
|
)
|
|
204
|
-
.action(
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
-
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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:
|
|
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:
|
|
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(
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
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
|
-
|
|
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
|
-
|
|
291
|
-
|
|
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
|
-
}
|
|
294
|
-
|
|
295
|
-
writeOutput(cmd, { ok: false, error: message });
|
|
296
|
-
process.exitCode = 1;
|
|
297
|
-
}
|
|
298
|
-
});
|
|
667
|
+
},
|
|
668
|
+
);
|
|
299
669
|
}
|