feat(sdk): confirm authentication method on remote add (#19947)

## 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)
This commit is contained in:
Félix Malfait
2026-04-22 15:18:25 +02:00
committed by GitHub
parent 789f8aba5d
commit c2cf3eac50
+39 -12
View File
@@ -18,20 +18,29 @@ const deriveRemoteName = (url: string): string => {
}
};
const authenticate = async (apiUrl: string, apiKey?: string): Promise<void> => {
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<AuthMethod> => {
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<AuthMethod> => {
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}".`));