@vellumai/assistant 0.5.11 → 0.5.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Dockerfile +42 -9
- package/docs/architecture/integrations.md +34 -32
- package/node_modules/@vellumai/ces-contracts/src/__tests__/grants.test.ts +7 -7
- package/node_modules/@vellumai/ces-contracts/src/handles.ts +5 -4
- package/node_modules/@vellumai/ces-contracts/src/index.ts +7 -0
- package/node_modules/@vellumai/ces-contracts/src/rpc.ts +5 -0
- package/node_modules/@vellumai/credential-storage/src/index.ts +1 -1
- package/openapi.yaml +87 -9
- package/package.json +1 -1
- package/src/__tests__/catalog-cache.test.ts +164 -0
- package/src/__tests__/catalog-search.test.ts +61 -0
- package/src/__tests__/cli-command-risk-guard.test.ts +181 -6
- package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +396 -0
- package/src/__tests__/conversation-error.test.ts +3 -2
- package/src/__tests__/credential-security-invariants.test.ts +9 -15
- package/src/__tests__/credential-vault-unit.test.ts +32 -34
- package/src/__tests__/credential-vault.test.ts +25 -33
- package/src/__tests__/credentials-cli.test.ts +3 -3
- package/src/__tests__/daemon-credential-client.test.ts +2 -2
- package/src/__tests__/first-greeting.test.ts +7 -0
- package/src/__tests__/host-bash-proxy.test.ts +79 -0
- package/src/__tests__/host-cu-proxy.test.ts +90 -0
- package/src/__tests__/host-file-proxy.test.ts +89 -0
- package/src/__tests__/integration-status.test.ts +5 -5
- package/src/__tests__/list-messages-attachments.test.ts +171 -0
- package/src/__tests__/mcp-abort-signal.test.ts +205 -0
- package/src/__tests__/messaging-send-tool.test.ts +5 -5
- package/src/__tests__/navigate-settings-tab.test.ts +6 -2
- package/src/__tests__/notification-telegram-adapter.test.ts +125 -0
- package/src/__tests__/oauth-cli.test.ts +126 -119
- package/src/__tests__/oauth-provider-profiles.test.ts +55 -20
- package/src/__tests__/oauth-scope-policy.test.ts +4 -6
- package/src/__tests__/onboarding-template-contract.test.ts +2 -2
- package/src/__tests__/platform.test.ts +3 -168
- package/src/__tests__/secret-routes-managed-proxy.test.ts +78 -0
- package/src/__tests__/secure-keys-managed-failover.test.ts +73 -0
- package/src/__tests__/skill-feature-flags.test.ts +8 -0
- package/src/__tests__/skill-secret-handling-guard.test.ts +212 -0
- package/src/__tests__/skills-uninstall.test.ts +2 -2
- package/src/__tests__/slack-messaging-token-resolution.test.ts +22 -24
- package/src/__tests__/slack-share-routes.test.ts +5 -5
- package/src/__tests__/system-prompt.test.ts +39 -0
- package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +1 -1
- package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +5 -4
- package/src/cli/AGENTS.md +47 -7
- package/src/cli/commands/browser-relay.ts +2 -17
- package/src/cli/commands/contacts.ts +6 -4
- package/src/cli/commands/conversations.ts +13 -1
- package/src/cli/commands/credential-execution.ts +16 -1
- package/src/cli/commands/credentials.ts +2 -8
- package/src/cli/commands/oauth/__tests__/connect.test.ts +29 -108
- package/src/cli/commands/oauth/__tests__/disconnect.test.ts +13 -87
- package/src/cli/commands/oauth/__tests__/mode.test.ts +22 -69
- package/src/cli/commands/oauth/__tests__/ping.test.ts +20 -79
- package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +574 -0
- package/src/cli/commands/oauth/__tests__/providers-update.test.ts +416 -0
- package/src/cli/commands/oauth/__tests__/status.test.ts +12 -40
- package/src/cli/commands/oauth/__tests__/token.test.ts +3 -50
- package/src/cli/commands/oauth/apps.ts +63 -44
- package/src/cli/commands/oauth/connect.ts +187 -155
- package/src/cli/commands/oauth/disconnect.ts +27 -75
- package/src/cli/commands/oauth/index.ts +36 -46
- package/src/cli/commands/oauth/mode.ts +22 -34
- package/src/cli/commands/oauth/ping.ts +19 -45
- package/src/cli/commands/oauth/providers.ts +569 -62
- package/src/cli/commands/oauth/request.ts +36 -48
- package/src/cli/commands/oauth/shared.ts +1 -19
- package/src/cli/commands/oauth/status.ts +14 -25
- package/src/cli/commands/oauth/token.ts +25 -34
- package/src/cli/commands/platform/__tests__/connect.test.ts +224 -0
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +237 -0
- package/src/cli/commands/platform/__tests__/status.test.ts +246 -0
- package/src/cli/commands/platform/connect.ts +104 -0
- package/src/cli/commands/platform/disconnect.ts +118 -0
- package/src/cli/commands/{platform.ts → platform/index.ts} +108 -38
- package/src/cli/commands/sequence.ts +5 -4
- package/src/cli/commands/shotgun.ts +16 -0
- package/src/cli/commands/skills.ts +173 -41
- package/src/cli/commands/usage.ts +5 -11
- package/src/cli/lib/daemon-credential-client.ts +22 -38
- package/src/cli/program.ts +1 -1
- package/src/config/assistant-feature-flags.ts +3 -7
- package/src/config/bundled-skills/contacts/tools/google-contacts.ts +1 -1
- package/src/config/bundled-skills/conversations/SKILL.md +20 -0
- package/src/config/bundled-skills/conversations/TOOLS.json +23 -0
- package/src/config/bundled-skills/conversations/tools/rename-conversation.ts +66 -0
- package/src/config/bundled-skills/gmail/SKILL.md +13 -13
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +3 -3
- package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +2 -2
- package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-label.ts +2 -2
- package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +1 -1
- package/src/config/bundled-skills/google-calendar/SKILL.md +10 -4
- package/src/config/bundled-skills/google-calendar/tools/shared.ts +1 -1
- package/src/config/bundled-skills/messaging/SKILL.md +7 -7
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +5 -2
- package/src/config/bundled-skills/messaging/tools/shared.ts +5 -6
- package/src/config/bundled-skills/settings/TOOLS.json +5 -3
- package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +4 -2
- package/src/config/bundled-tool-registry.ts +5 -0
- package/src/config/feature-flag-registry.json +2 -2
- package/src/credential-execution/client.ts +15 -3
- package/src/daemon/conversation-agent-loop.ts +2 -0
- package/src/daemon/conversation-error.ts +36 -6
- package/src/daemon/conversation-messaging.ts +9 -0
- package/src/daemon/conversation-runtime-assembly.ts +33 -0
- package/src/daemon/conversation-surfaces.ts +120 -14
- package/src/daemon/conversation.ts +5 -0
- package/src/daemon/first-greeting.ts +6 -1
- package/src/daemon/handlers/skills.ts +148 -3
- package/src/daemon/host-bash-proxy.ts +16 -0
- package/src/daemon/host-cu-proxy.ts +16 -0
- package/src/daemon/host-file-proxy.ts +16 -0
- package/src/daemon/lifecycle.ts +56 -5
- package/src/daemon/message-types/conversations.ts +1 -0
- package/src/daemon/message-types/guardian-actions.ts +2 -0
- package/src/daemon/message-types/host-bash.ts +6 -1
- package/src/daemon/message-types/host-cu.ts +6 -1
- package/src/daemon/message-types/host-file.ts +6 -1
- package/src/daemon/message-types/integrations.ts +0 -1
- package/src/daemon/server.ts +29 -2
- package/src/hooks/cli.ts +74 -0
- package/src/inbound/platform-callback-registration.ts +7 -12
- package/src/index.ts +0 -12
- package/src/mcp/client.ts +6 -1
- package/src/mcp/manager.ts +2 -1
- package/src/memory/conversation-crud.ts +92 -3
- package/src/memory/conversation-key-store.ts +26 -0
- package/src/memory/conversation-queries.ts +6 -6
- package/src/memory/db-init.ts +16 -0
- package/src/memory/journal-memory.ts +8 -2
- package/src/memory/migrations/196-messages-conversation-created-at-index.ts +9 -0
- package/src/memory/migrations/196-strip-integration-prefix-from-provider-keys.ts +186 -0
- package/src/memory/migrations/197-oauth-providers-behavior-columns.ts +29 -0
- package/src/memory/migrations/198-drop-setup-skill-id-column.ts +11 -0
- package/src/memory/migrations/index.ts +4 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/schema/oauth.ts +11 -0
- package/src/messaging/provider.ts +13 -12
- package/src/messaging/providers/gmail/adapter.ts +44 -35
- package/src/messaging/providers/slack/adapter.ts +63 -33
- package/src/messaging/providers/telegram-bot/adapter.ts +6 -8
- package/src/messaging/providers/whatsapp/adapter.ts +6 -8
- package/src/notifications/adapters/telegram.ts +78 -2
- package/src/oauth/__tests__/identity-verifier.test.ts +464 -0
- package/src/oauth/byo-connection.test.ts +22 -24
- package/src/oauth/connect-orchestrator.ts +37 -76
- package/src/oauth/connect-types.ts +7 -65
- package/src/oauth/connection-resolver.test.ts +13 -13
- package/src/oauth/connection-resolver.ts +3 -4
- package/src/oauth/identity-verifier.ts +177 -0
- package/src/oauth/oauth-store.ts +228 -3
- package/src/oauth/platform-connection.test.ts +56 -6
- package/src/oauth/platform-connection.ts +8 -1
- package/src/oauth/seed-providers.ts +247 -34
- package/src/permissions/checker.ts +127 -1
- package/src/prompts/journal-context.ts +4 -1
- package/src/prompts/system-prompt.ts +54 -9
- package/src/prompts/templates/BOOTSTRAP.md +16 -5
- package/src/providers/anthropic/client.ts +2 -33
- package/src/runtime/guardian-action-service.ts +7 -2
- package/src/runtime/http-server.ts +12 -18
- package/src/runtime/http-types.ts +8 -1
- package/src/runtime/migrations/rebind-secrets-screen.ts +2 -2
- package/src/runtime/routes/conversation-management-routes.ts +31 -0
- package/src/runtime/routes/conversation-routes.ts +79 -4
- package/src/runtime/routes/guardian-action-routes.ts +15 -2
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +21 -8
- package/src/runtime/routes/integrations/slack/share.ts +1 -1
- package/src/runtime/routes/oauth-apps.ts +2 -1
- package/src/runtime/routes/secret-routes.ts +45 -15
- package/src/runtime/routes/settings-routes.ts +12 -19
- package/src/runtime/routes/skills-routes.ts +45 -4
- package/src/schedule/integration-status.ts +2 -2
- package/src/security/ces-rpc-credential-backend.ts +19 -16
- package/src/security/oauth-completion-page.ts +153 -0
- package/src/security/oauth2.ts +3 -17
- package/src/security/secure-keys.ts +207 -7
- package/src/security/token-manager.ts +3 -6
- package/src/signals/bash.ts +6 -1
- package/src/skills/catalog-cache.ts +44 -0
- package/src/skills/catalog-search.ts +18 -0
- package/src/tools/browser/browser-manager.ts +2 -2
- package/src/tools/credentials/post-connect-hooks.ts +1 -1
- package/src/tools/credentials/vault.ts +34 -45
- package/src/tools/host-terminal/host-shell.ts +16 -3
- package/src/tools/mcp/mcp-tool-factory.ts +2 -1
- package/src/tools/skills/sandbox-runner.ts +16 -3
- package/src/tools/terminal/shell.ts +16 -3
- package/src/util/logger.ts +11 -1
- package/src/util/platform.ts +1 -91
- package/src/util/sentry-log-stream.ts +51 -0
- package/src/watcher/providers/github.ts +2 -2
- package/src/watcher/providers/gmail.ts +1 -1
- package/src/watcher/providers/google-calendar.ts +1 -1
- package/src/watcher/providers/linear.ts +2 -2
- package/src/workspace/migrations/011-backfill-installation-id.ts +5 -3
- package/src/workspace/migrations/020-rename-oauth-skill-dirs.ts +119 -0
- package/src/workspace/migrations/registry.ts +2 -0
- package/src/cli/commands/oauth/connections.ts +0 -255
- package/src/oauth/provider-behaviors.ts +0 -634
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { createServer, type Server } from "node:http";
|
|
2
|
+
|
|
1
3
|
import type { Command } from "commander";
|
|
2
4
|
|
|
3
5
|
import { orchestrateOAuthConnect } from "../../../oauth/connect-orchestrator.js";
|
|
@@ -6,7 +8,7 @@ import {
|
|
|
6
8
|
getMostRecentAppByProvider,
|
|
7
9
|
getProvider,
|
|
8
10
|
} from "../../../oauth/oauth-store.js";
|
|
9
|
-
import {
|
|
11
|
+
import { renderOAuthCompletionPage } from "../../../security/oauth-completion-page.js";
|
|
10
12
|
import { openInBrowser } from "../../../util/browser.js";
|
|
11
13
|
import { getSecureKeyViaDaemon } from "../../lib/daemon-credential-client.js";
|
|
12
14
|
import { getCliLogger } from "../../logger.js";
|
|
@@ -15,12 +17,57 @@ import {
|
|
|
15
17
|
fetchActiveConnections,
|
|
16
18
|
isManagedMode,
|
|
17
19
|
requirePlatformClient,
|
|
18
|
-
resolveService,
|
|
19
|
-
toBareProvider,
|
|
20
20
|
} from "./shared.js";
|
|
21
21
|
|
|
22
22
|
const log = getCliLogger("cli");
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Start a temporary loopback server to serve a nice completion page after the
|
|
26
|
+
* platform redirects the user's browser following a managed OAuth flow.
|
|
27
|
+
* Returns the base URL and a cleanup function.
|
|
28
|
+
*/
|
|
29
|
+
function startManagedRedirectServer(provider: string): Promise<{
|
|
30
|
+
redirectUrl: string;
|
|
31
|
+
cleanup: () => void;
|
|
32
|
+
}> {
|
|
33
|
+
return new Promise((resolve, reject) => {
|
|
34
|
+
const server: Server = createServer((req, res) => {
|
|
35
|
+
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
36
|
+
const error = url.searchParams.get("error");
|
|
37
|
+
const errorDesc = url.searchParams.get("error_description");
|
|
38
|
+
const providerHint = url.searchParams.get("provider") ?? provider;
|
|
39
|
+
|
|
40
|
+
if (error) {
|
|
41
|
+
const message = errorDesc ?? error;
|
|
42
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
43
|
+
res.end(renderOAuthCompletionPage(message, false, providerHint));
|
|
44
|
+
} else {
|
|
45
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
46
|
+
res.end(
|
|
47
|
+
renderOAuthCompletionPage(
|
|
48
|
+
"You can close this tab and return to your assistant.",
|
|
49
|
+
true,
|
|
50
|
+
providerHint,
|
|
51
|
+
),
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
server.listen(0, "localhost", () => {
|
|
57
|
+
const addr = server.address() as { port: number };
|
|
58
|
+
const redirectUrl = `http://localhost:${addr.port}/oauth/complete`;
|
|
59
|
+
resolve({
|
|
60
|
+
redirectUrl,
|
|
61
|
+
cleanup: () => server.close(),
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
server.on("error", (err) => {
|
|
66
|
+
reject(new Error(`Failed to start redirect server: ${err.message}`));
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
24
71
|
// ---------------------------------------------------------------------------
|
|
25
72
|
// Command registration
|
|
26
73
|
// ---------------------------------------------------------------------------
|
|
@@ -29,7 +76,7 @@ export function registerConnectCommand(oauth: Command): void {
|
|
|
29
76
|
oauth
|
|
30
77
|
.command("connect <provider>")
|
|
31
78
|
.description(
|
|
32
|
-
"Initiate an OAuth authorization flow for a provider
|
|
79
|
+
"Initiate an OAuth authorization flow for a specified provider",
|
|
33
80
|
)
|
|
34
81
|
.option("--scopes <scopes...>", "Scopes to request for the authorization")
|
|
35
82
|
.option(
|
|
@@ -41,36 +88,18 @@ export function registerConnectCommand(oauth: Command): void {
|
|
|
41
88
|
"after",
|
|
42
89
|
`
|
|
43
90
|
Arguments:
|
|
44
|
-
provider Provider
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
(use full scope URLs where required). In BYO mode,
|
|
52
|
-
scopes are appended to the provider's defaults.
|
|
53
|
-
--open-browser Open the authorization URL in your browser and wait
|
|
54
|
-
for completion. In managed mode, polls for a new
|
|
55
|
-
platform connection. In BYO mode, starts a local
|
|
56
|
-
callback server and blocks until the OAuth redirect.
|
|
57
|
-
--client-id <id> BYO-only: select a specific OAuth app when multiple
|
|
58
|
-
apps exist for the same provider. Ignored for
|
|
59
|
-
platform-managed providers.
|
|
60
|
-
|
|
61
|
-
Mode detection:
|
|
62
|
-
The command checks the services config to determine whether the provider
|
|
63
|
-
runs in platform-managed or BYO (bring-your-own credentials) mode.
|
|
64
|
-
|
|
65
|
-
Managed mode: Calls the platform /start/ endpoint, returns a connect URL.
|
|
66
|
-
With --open-browser, opens the URL and polls for a new connection.
|
|
67
|
-
BYO mode: Resolves local client credentials from the OAuth app store and
|
|
68
|
-
runs the OAuth2 authorization code flow via the local orchestrator.
|
|
91
|
+
provider Provider name (e.g. google, slack, notion).
|
|
92
|
+
Run 'assistant oauth providers list' to see available providers.
|
|
93
|
+
|
|
94
|
+
In managed mode, --scopes must be in the provider's allowed set (use full
|
|
95
|
+
scope URLs). In BYO mode, --scopes are appended to the provider's defaults.
|
|
96
|
+
The --open-browser flag polls for a platform connection (managed) or starts
|
|
97
|
+
a local callback server (BYO).
|
|
69
98
|
|
|
70
99
|
Examples:
|
|
71
100
|
$ assistant oauth connect google
|
|
72
|
-
$ assistant oauth connect
|
|
73
|
-
$ assistant oauth connect
|
|
101
|
+
$ assistant oauth connect google --open-browser
|
|
102
|
+
$ assistant oauth connect google --scopes https://www.googleapis.com/auth/calendar https://www.googleapis.com/auth/calendar.events
|
|
74
103
|
$ assistant oauth connect google --client-id abc123 --open-browser`,
|
|
75
104
|
)
|
|
76
105
|
.action(
|
|
@@ -93,17 +122,12 @@ Examples:
|
|
|
93
122
|
|
|
94
123
|
try {
|
|
95
124
|
// ---------------------------------------------------------------
|
|
96
|
-
// 1.
|
|
97
|
-
// ---------------------------------------------------------------
|
|
98
|
-
const providerKey = resolveService(provider);
|
|
99
|
-
|
|
100
|
-
// ---------------------------------------------------------------
|
|
101
|
-
// 2. Validate provider exists
|
|
125
|
+
// 1. Validate provider exists
|
|
102
126
|
// ---------------------------------------------------------------
|
|
103
|
-
const providerRow = getProvider(
|
|
127
|
+
const providerRow = getProvider(provider);
|
|
104
128
|
if (!providerRow) {
|
|
105
129
|
writeError(
|
|
106
|
-
`Unknown provider "${
|
|
130
|
+
`Unknown provider "${provider}". ` +
|
|
107
131
|
`Run 'assistant oauth providers list' to see available providers.`,
|
|
108
132
|
);
|
|
109
133
|
return;
|
|
@@ -112,7 +136,7 @@ Examples:
|
|
|
112
136
|
// ---------------------------------------------------------------
|
|
113
137
|
// 3. Detect mode
|
|
114
138
|
// ---------------------------------------------------------------
|
|
115
|
-
const managed = isManagedMode(
|
|
139
|
+
const managed = isManagedMode(provider);
|
|
116
140
|
|
|
117
141
|
if (managed) {
|
|
118
142
|
// =============================================================
|
|
@@ -122,7 +146,7 @@ Examples:
|
|
|
122
146
|
// Warn about --client-id being ignored in managed mode
|
|
123
147
|
if (opts.clientId) {
|
|
124
148
|
log.info(
|
|
125
|
-
`Warning: --client-id is ignored for platform-managed providers. The platform manages OAuth apps for "${
|
|
149
|
+
`Warning: --client-id is ignored for platform-managed providers. The platform manages OAuth apps for "${provider}".`,
|
|
126
150
|
);
|
|
127
151
|
}
|
|
128
152
|
|
|
@@ -130,148 +154,163 @@ Examples:
|
|
|
130
154
|
if (!client) return;
|
|
131
155
|
|
|
132
156
|
// Call the platform's OAuth start endpoint
|
|
133
|
-
const startPath = `/v1/assistants/${encodeURIComponent(client.platformAssistantId)}/oauth/${encodeURIComponent(
|
|
157
|
+
const startPath = `/v1/assistants/${encodeURIComponent(client.platformAssistantId)}/oauth/${encodeURIComponent(provider)}/start/`;
|
|
134
158
|
|
|
135
159
|
const body: Record<string, unknown> = {};
|
|
136
160
|
if (opts.scopes && opts.scopes.length > 0) {
|
|
137
161
|
body.requested_scopes = opts.scopes;
|
|
138
162
|
}
|
|
139
163
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
164
|
+
// When opening the browser, start a local server to show a nice
|
|
165
|
+
// completion page instead of redirecting to the platform website.
|
|
166
|
+
let redirectServer:
|
|
167
|
+
| { redirectUrl: string; cleanup: () => void }
|
|
168
|
+
| undefined;
|
|
169
|
+
if (opts.openBrowser) {
|
|
170
|
+
try {
|
|
171
|
+
redirectServer = await startManagedRedirectServer(provider);
|
|
172
|
+
body.redirect_after_connect = redirectServer.redirectUrl;
|
|
173
|
+
} catch {
|
|
174
|
+
// Non-fatal — fall back to platform default redirect
|
|
175
|
+
}
|
|
152
176
|
}
|
|
153
177
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
"Platform did not return a connect URL — the OAuth flow could not be started",
|
|
161
|
-
);
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
178
|
+
try {
|
|
179
|
+
const response = await client.fetch(startPath, {
|
|
180
|
+
method: "POST",
|
|
181
|
+
headers: { "Content-Type": "application/json" },
|
|
182
|
+
body: JSON.stringify(body),
|
|
183
|
+
});
|
|
164
184
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
cmd,
|
|
171
|
-
);
|
|
172
|
-
if (!snapshotEntries) {
|
|
173
|
-
// fetchActiveConnections already wrote the error output
|
|
185
|
+
if (!response.ok) {
|
|
186
|
+
const errorText = await response.text().catch(() => "");
|
|
187
|
+
writeError(
|
|
188
|
+
`Platform returned HTTP ${response.status}${errorText ? `: ${errorText}` : ""}`,
|
|
189
|
+
);
|
|
174
190
|
return;
|
|
175
191
|
}
|
|
176
|
-
const snapshotIds = new Set(snapshotEntries.map((e) => e.id));
|
|
177
192
|
|
|
178
|
-
|
|
193
|
+
const result = (await response.json()) as {
|
|
194
|
+
connect_url?: string;
|
|
195
|
+
};
|
|
179
196
|
|
|
180
|
-
if (!
|
|
181
|
-
|
|
182
|
-
|
|
197
|
+
if (!result.connect_url) {
|
|
198
|
+
writeError(
|
|
199
|
+
"Platform did not return a connect URL — the OAuth flow could not be started",
|
|
183
200
|
);
|
|
201
|
+
return;
|
|
184
202
|
}
|
|
185
203
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
const deadline = Date.now() + timeoutMs;
|
|
190
|
-
let newConnection: {
|
|
191
|
-
id: string;
|
|
192
|
-
account_label?: string;
|
|
193
|
-
scopes_granted?: string[];
|
|
194
|
-
} | null = null;
|
|
195
|
-
|
|
196
|
-
while (Date.now() < deadline) {
|
|
197
|
-
await new Promise((r) => setTimeout(r, pollIntervalMs));
|
|
198
|
-
|
|
199
|
-
const currentEntries = await fetchActiveConnections(
|
|
204
|
+
if (opts.openBrowser) {
|
|
205
|
+
// Snapshot existing connection IDs before opening browser
|
|
206
|
+
const snapshotEntries = await fetchActiveConnections(
|
|
200
207
|
client,
|
|
201
|
-
|
|
208
|
+
provider,
|
|
202
209
|
cmd,
|
|
203
|
-
{ silent: true },
|
|
204
210
|
);
|
|
205
|
-
if (!
|
|
211
|
+
if (!snapshotEntries) {
|
|
212
|
+
// fetchActiveConnections already wrote the error output
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
const snapshotIds = new Set(snapshotEntries.map((e) => e.id));
|
|
206
216
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
)
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
217
|
+
openInBrowser(result.connect_url);
|
|
218
|
+
|
|
219
|
+
if (!jsonMode) {
|
|
220
|
+
log.info(
|
|
221
|
+
`Opening browser to connect ${provider}. Waiting for authorization...`,
|
|
222
|
+
);
|
|
213
223
|
}
|
|
214
|
-
}
|
|
215
224
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
225
|
+
// Poll for a new connection every 2s for up to 5 minutes
|
|
226
|
+
const pollIntervalMs = 2000;
|
|
227
|
+
const timeoutMs = 5 * 60 * 1000;
|
|
228
|
+
const deadline = Date.now() + timeoutMs;
|
|
229
|
+
let newConnection: {
|
|
230
|
+
id: string;
|
|
231
|
+
account_label?: string;
|
|
232
|
+
scopes_granted?: string[];
|
|
233
|
+
} | null = null;
|
|
234
|
+
|
|
235
|
+
while (Date.now() < deadline) {
|
|
236
|
+
await new Promise((r) => setTimeout(r, pollIntervalMs));
|
|
237
|
+
|
|
238
|
+
const currentEntries = await fetchActiveConnections(
|
|
239
|
+
client,
|
|
240
|
+
provider,
|
|
241
|
+
cmd,
|
|
242
|
+
{ silent: true },
|
|
243
|
+
);
|
|
244
|
+
if (!currentEntries) continue;
|
|
245
|
+
|
|
246
|
+
const found = currentEntries.find(
|
|
247
|
+
(e) => !snapshotIds.has(e.id),
|
|
248
|
+
);
|
|
249
|
+
if (found) {
|
|
250
|
+
newConnection = found;
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (newConnection) {
|
|
256
|
+
// Success — new connection found
|
|
257
|
+
if (jsonMode) {
|
|
258
|
+
writeOutput(cmd, {
|
|
259
|
+
ok: true,
|
|
260
|
+
provider: provider,
|
|
261
|
+
connectionId: newConnection.id,
|
|
262
|
+
accountLabel: newConnection.account_label ?? null,
|
|
263
|
+
scopesGranted: newConnection.scopes_granted ?? [],
|
|
264
|
+
});
|
|
265
|
+
} else {
|
|
266
|
+
const label = newConnection.account_label
|
|
267
|
+
? ` as ${newConnection.account_label}`
|
|
268
|
+
: "";
|
|
269
|
+
process.stdout.write(`Connected to ${provider}${label}\n`);
|
|
270
|
+
}
|
|
226
271
|
} else {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
272
|
+
// Timeout — authorization may still be in progress
|
|
273
|
+
if (jsonMode) {
|
|
274
|
+
writeOutput(cmd, {
|
|
275
|
+
ok: true,
|
|
276
|
+
deferred: true,
|
|
277
|
+
provider: provider,
|
|
278
|
+
connectUrl: result.connect_url,
|
|
279
|
+
message:
|
|
280
|
+
"Authorization may still be in progress. Check with 'assistant oauth status <provider>'.",
|
|
281
|
+
});
|
|
282
|
+
} else {
|
|
283
|
+
process.stdout.write(
|
|
284
|
+
`Timed out waiting for authorization. It may still be in progress.\n` +
|
|
285
|
+
`Check with: assistant oauth status ${provider}\n`,
|
|
286
|
+
);
|
|
287
|
+
}
|
|
231
288
|
}
|
|
232
289
|
} else {
|
|
233
|
-
//
|
|
290
|
+
// No --open-browser: output the connect URL
|
|
234
291
|
if (jsonMode) {
|
|
235
292
|
writeOutput(cmd, {
|
|
236
293
|
ok: true,
|
|
237
294
|
deferred: true,
|
|
238
|
-
provider: providerKey,
|
|
239
295
|
connectUrl: result.connect_url,
|
|
240
|
-
|
|
241
|
-
"Authorization may still be in progress. Check with 'assistant oauth status <provider>'.",
|
|
296
|
+
provider: provider,
|
|
242
297
|
});
|
|
243
298
|
} else {
|
|
244
|
-
process.stdout.write(
|
|
245
|
-
`Timed out waiting for authorization. It may still be in progress.\n` +
|
|
246
|
-
`Check with: assistant oauth status ${providerKey}\n`,
|
|
247
|
-
);
|
|
299
|
+
process.stdout.write(result.connect_url + "\n");
|
|
248
300
|
}
|
|
249
301
|
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
if (jsonMode) {
|
|
253
|
-
writeOutput(cmd, {
|
|
254
|
-
ok: true,
|
|
255
|
-
deferred: true,
|
|
256
|
-
connectUrl: result.connect_url,
|
|
257
|
-
provider: providerKey,
|
|
258
|
-
});
|
|
259
|
-
} else {
|
|
260
|
-
process.stdout.write(result.connect_url + "\n");
|
|
261
|
-
}
|
|
302
|
+
} finally {
|
|
303
|
+
redirectServer?.cleanup();
|
|
262
304
|
}
|
|
263
305
|
} else {
|
|
264
306
|
// =============================================================
|
|
265
307
|
// BYO PATH
|
|
266
308
|
// =============================================================
|
|
267
309
|
|
|
268
|
-
// a. Resolve
|
|
269
|
-
const resolvedServiceKey = providerKey;
|
|
270
|
-
|
|
271
|
-
// b. Resolve client credentials from the DB
|
|
310
|
+
// a. Resolve client credentials from the DB
|
|
272
311
|
const dbApp = opts.clientId
|
|
273
|
-
? getAppByProviderAndClientId(
|
|
274
|
-
: getMostRecentAppByProvider(
|
|
312
|
+
? getAppByProviderAndClientId(provider, opts.clientId)
|
|
313
|
+
: getMostRecentAppByProvider(provider);
|
|
275
314
|
|
|
276
315
|
let clientId = opts.clientId;
|
|
277
316
|
let clientSecret: string | undefined;
|
|
@@ -285,7 +324,7 @@ Examples:
|
|
|
285
324
|
} else if (opts.clientId) {
|
|
286
325
|
// --client-id was explicitly provided but no matching app exists
|
|
287
326
|
writeError(
|
|
288
|
-
`No registered app found for "${
|
|
327
|
+
`No registered app found for "${provider}" with client ID "${opts.clientId}". ` +
|
|
289
328
|
`Register one with 'assistant oauth apps upsert'.`,
|
|
290
329
|
);
|
|
291
330
|
return;
|
|
@@ -294,7 +333,7 @@ Examples:
|
|
|
294
333
|
// c. Validate client_id exists
|
|
295
334
|
if (!clientId) {
|
|
296
335
|
writeError(
|
|
297
|
-
`No client_id found for "${
|
|
336
|
+
`No client_id found for "${provider}". ` +
|
|
298
337
|
`Register one with 'assistant oauth apps upsert'.`,
|
|
299
338
|
);
|
|
300
339
|
return;
|
|
@@ -302,18 +341,11 @@ Examples:
|
|
|
302
341
|
|
|
303
342
|
// d. Check if client_secret is required but missing
|
|
304
343
|
if (clientSecret === undefined) {
|
|
305
|
-
const
|
|
306
|
-
|
|
307
|
-
const requiresSecret =
|
|
308
|
-
behavior?.setup?.requiresClientSecret ??
|
|
309
|
-
!!(
|
|
310
|
-
providerRow?.tokenEndpointAuthMethod ||
|
|
311
|
-
providerRow?.extraParams
|
|
312
|
-
);
|
|
344
|
+
const requiresSecret = !!providerRow?.requiresClientSecret;
|
|
313
345
|
|
|
314
346
|
if (requiresSecret) {
|
|
315
347
|
writeError(
|
|
316
|
-
`client_secret is required for ${
|
|
348
|
+
`client_secret is required for ${provider} but not found. ` +
|
|
317
349
|
`Store it with 'assistant oauth apps upsert --client-secret'.`,
|
|
318
350
|
);
|
|
319
351
|
return;
|
|
@@ -346,7 +378,7 @@ Examples:
|
|
|
346
378
|
});
|
|
347
379
|
} else {
|
|
348
380
|
process.stdout.write(
|
|
349
|
-
`\nAuthorize with ${
|
|
381
|
+
`\nAuthorize with ${provider}:\n\n${result.authUrl}\n\nThe connection will complete automatically once you authorize.\n`,
|
|
350
382
|
);
|
|
351
383
|
}
|
|
352
384
|
return;
|
|
@@ -360,7 +392,7 @@ Examples:
|
|
|
360
392
|
accountInfo: result.accountInfo,
|
|
361
393
|
});
|
|
362
394
|
} else {
|
|
363
|
-
const msg = `Connected to ${
|
|
395
|
+
const msg = `Connected to ${provider}${result.accountInfo ? ` as ${result.accountInfo}` : ""}`;
|
|
364
396
|
process.stdout.write(msg + "\n");
|
|
365
397
|
}
|
|
366
398
|
}
|