From c2cf3eac50a0273b0afd2dcca4c8790be8036cb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Malfait?= Date: Wed, 22 Apr 2026 15:18:25 +0200 Subject: [PATCH] feat(sdk): confirm authentication method on remote add (#19947) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary `yarn twenty remote add` only prints `✓ Default remote set to X.` after authenticating. When using the OAuth path, the browser flow happens silently — there's no line that says _"you authenticated"_ — so users (including me this morning while installing a Twenty app) are left wondering whether auth actually completed and which method was used. This PR adds explicit confirmation of the auth step: **New remote via OAuth** ``` ✓ Remote "myremote" added (https://app.twenty.com) via OAuth. ✓ Default remote set to "myremote". ``` **New remote via API key** ``` ✓ Remote "myremote" added (https://app.twenty.com) via API key. ✓ Default remote set to "myremote". ``` **Re-authenticating an existing remote** ``` ✓ Re-authenticated "myremote" via OAuth. ✓ Default remote set to "myremote". ``` ## Implementation - `authenticate()` now returns the method actually used (`'OAuth' | 'API key'`) instead of `void`. This correctly surfaces OAuth → API-key fallback: if OAuth fails and the user drops into the API-key prompt, the success line reflects that. - New-remote and re-auth paths print distinct messages so the user can tell which path they took. - No new API calls — method name comes from which branch of `authenticate()` succeeded. ## Test plan - [x] `nx typecheck twenty-sdk` — clean - [x] `nx lint twenty-sdk` — clean - [ ] Manual smoke test: `yarn twenty remote add --as test --api-url ...` via OAuth, API key, and re-auth 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- .../twenty-sdk/src/cli/commands/remote.ts | 51 ++++++++++++++----- 1 file changed, 39 insertions(+), 12 deletions(-) diff --git a/packages/twenty-sdk/src/cli/commands/remote.ts b/packages/twenty-sdk/src/cli/commands/remote.ts index ef41b49d60..fbf2c9fcdb 100644 --- a/packages/twenty-sdk/src/cli/commands/remote.ts +++ b/packages/twenty-sdk/src/cli/commands/remote.ts @@ -18,20 +18,29 @@ const deriveRemoteName = (url: string): string => { } }; -const authenticate = async (apiUrl: string, apiKey?: string): Promise => { - const result = apiKey - ? await authLogin({ apiKey, apiUrl }) - : await runOAuthWithApiKeyFallback(apiUrl); +type AuthMethod = 'OAuth' | 'API key'; - if (!result.success) { - console.error(chalk.red('✗ Authentication failed.')); - process.exit(1); +const authenticate = async ( + apiUrl: string, + apiKey?: string, +): Promise => { + if (apiKey) { + const result = await authLogin({ apiKey, apiUrl }); + + if (!result.success) { + console.error(chalk.red('✗ Authentication failed.')); + process.exit(1); + } + + return 'API key'; } + + return runOAuthWithApiKeyFallback(apiUrl); }; const runOAuthWithApiKeyFallback = async ( apiUrl: string, -): Promise<{ success: boolean }> => { +): Promise => { await inquirer.prompt([ { type: 'input', @@ -43,7 +52,7 @@ const runOAuthWithApiKeyFallback = async ( const oauthResult = await authLoginOAuth({ apiUrl }); if (oauthResult.success) { - return oauthResult; + return 'OAuth'; } console.log(chalk.yellow(oauthResult.error.message)); @@ -58,7 +67,17 @@ const runOAuthWithApiKeyFallback = async ( }, ]); - return authLogin({ apiKey: keyAnswer.apiKey, apiUrl }); + const fallbackResult = await authLogin({ + apiKey: keyAnswer.apiKey, + apiUrl, + }); + + if (!fallbackResult.success) { + console.error(chalk.red('✗ Authentication failed.')); + process.exit(1); + } + + return 'API key'; }; export const registerRemoteCommands = (program: Command): void => { @@ -92,7 +111,11 @@ export const registerRemoteCommands = (program: Command): void => { const config = await configService.getConfigForRemote(options.as); ConfigService.setActiveRemote(options.as); - await authenticate(config.apiUrl, options.apiKey); + const method = await authenticate(config.apiUrl, options.apiKey); + + console.log( + chalk.green(`✓ Re-authenticated "${options.as}" via ${method}.`), + ); await configService.setDefaultRemote(options.as); console.log(chalk.green(`✓ Default remote set to "${options.as}".`)); @@ -143,7 +166,11 @@ export const registerRemoteCommands = (program: Command): void => { const name = options.as ?? deriveRemoteName(apiUrl); ConfigService.setActiveRemote(name); - await authenticate(apiUrl, options.apiKey); + const method = await authenticate(apiUrl, options.apiKey); + + console.log( + chalk.green(`✓ Remote "${name}" added (${apiUrl}) via ${method}.`), + ); await configService.setDefaultRemote(name); console.log(chalk.green(`✓ Default remote set to "${name}".`));