diff --git a/.nvimlog b/.nvimlog new file mode 100644 index 00000000..37fc1df7 --- /dev/null +++ b/.nvimlog @@ -0,0 +1 @@ +WRN 2026-04-29T11:21:24.001 ?.14533 server_start:199: Failed to start server: operation not permitted: /var/folders/1g/3yj40ngn5b33fs9gg0wzvms00000gn/T/nvim.apple/gwHDxl/nvim.14533.0 diff --git a/src/cli/handlers/auth.ts b/src/cli/handlers/auth.ts index c4cba5d0..96c98b81 100644 --- a/src/cli/handlers/auth.ts +++ b/src/cli/handlers/auth.ts @@ -20,13 +20,19 @@ import { import { getOauthProfileFromOauthToken } from '../../services/oauth/getOauthProfile.js' import { OAuthService } from '../../services/oauth/index.js' import type { OAuthTokens } from '../../services/oauth/types.js' +import { OpenAIOAuthService } from '../../services/openaiAuth/index.js' +import { getOpenAIOAuthTokens } from '../../services/openaiAuth/storage.js' +import type { OpenAIOAuthTokens } from '../../services/openaiAuth/types.js' import { + clearStoredClaudeAIOAuthTokens, clearOAuthTokenCache, getAnthropicApiKeyWithSource, getAuthTokenSource, + getOpenAIAuthOverrideWarning, getOauthAccountInfo, getSubscriptionType, isUsing3PServices, + removeApiKey, saveOAuthTokensIfNeeded, validateForceLoginOrg, } from '../../utils/auth.js' @@ -109,17 +115,75 @@ export async function installOAuthTokens(tokens: OAuthTokens): Promise { await clearAuthRelatedCaches() } +export async function installOpenAIOAuthTokens( + tokens: OpenAIOAuthTokens, +): Promise { + await removeApiKey() + const clearClaudeOauth = clearStoredClaudeAIOAuthTokens() + if (!clearClaudeOauth.success) { + throw new Error( + clearClaudeOauth.warning ?? 'Failed to disable existing Claude auth state', + ) + } + + saveGlobalConfig(current => ({ + ...current, + oauthAccount: undefined, + })) + + if (!tokens.refreshToken) { + throw new Error('OpenAI OAuth tokens are incomplete.') + } + + await clearAuthRelatedCaches() + return getOpenAIAuthOverrideWarning() +} + export async function authLogin({ email, sso, console: useConsole, claudeai, + openai, }: { email?: string sso?: boolean console?: boolean claudeai?: boolean + openai?: boolean }): Promise { + if (openai) { + if (email || sso || useConsole || claudeai) { + process.stderr.write( + 'Error: --openai cannot be combined with --email, --sso, --console, or --claudeai.\n', + ) + process.exit(1) + } + + const openaiOAuthService = new OpenAIOAuthService() + + try { + const tokens = await openaiOAuthService.startOAuthFlow(async url => { + process.stdout.write('Opening browser to sign in to OpenAI…\n') + process.stdout.write(`If the browser did not open, visit: ${url}\n`) + }) + + const warning = await installOpenAIOAuthTokens(tokens) + + process.stdout.write('OpenAI login successful.\n') + if (warning) { + process.stdout.write(`${warning}\n`) + } + process.exit(0) + } catch (err) { + logError(err) + process.stderr.write(`OpenAI login failed: ${errorMessage(err)}\n`) + process.exit(1) + } finally { + openaiOAuthService.cleanup() + } + } + if (useConsole && claudeai) { process.stderr.write( 'Error: --console and --claudeai cannot be used together.\n', @@ -232,7 +296,52 @@ export async function authLogin({ export async function authStatus(opts: { json?: boolean text?: boolean + openai?: boolean }): Promise { + if (opts.openai) { + const openaiTokens = getOpenAIOAuthTokens() + const loggedIn = + !!openaiTokens && + openaiTokens.refreshToken.length > 0 && + openaiTokens.expiresAt > 0 + + if (opts.text) { + if (!loggedIn) { + process.stdout.write( + 'Not logged in to OpenAI. Run claude auth login --openai to authenticate.\n', + ) + } else { + process.stdout.write('Provider: openai\n') + if (openaiTokens.email) { + process.stdout.write(`Email: ${openaiTokens.email}\n`) + } + if (openaiTokens.accountId) { + process.stdout.write(`Account ID: ${openaiTokens.accountId}\n`) + } + process.stdout.write( + `Expires At: ${new Date(openaiTokens.expiresAt).toISOString()}\n`, + ) + } + } else { + process.stdout.write( + jsonStringify( + { + loggedIn, + authMethod: loggedIn ? 'openai_oauth' : 'none', + provider: 'openai', + email: openaiTokens?.email ?? null, + accountId: openaiTokens?.accountId ?? null, + expiresAt: openaiTokens?.expiresAt ?? null, + }, + null, + 2, + ) + '\n', + ) + } + + process.exit(loggedIn ? 0 : 1) + } + const { source: authTokenSource, hasToken } = getAuthTokenSource() const { source: apiKeySource } = getAnthropicApiKeyWithSource() const hasApiKeyEnvVar = @@ -240,12 +349,25 @@ export async function authStatus(opts: { const oauthAccount = getOauthAccountInfo() const subscriptionType = getSubscriptionType() const using3P = isUsing3PServices() + const openaiTokens = getOpenAIOAuthTokens() + const usingOpenAI = + !!openaiTokens?.refreshToken && + !hasToken && + apiKeySource === 'none' && + !hasApiKeyEnvVar && + !using3P const loggedIn = - hasToken || apiKeySource !== 'none' || hasApiKeyEnvVar || using3P + hasToken || + apiKeySource !== 'none' || + hasApiKeyEnvVar || + using3P || + usingOpenAI // Determine auth method let authMethod: string = 'none' - if (using3P) { + if (usingOpenAI) { + authMethod = 'openai_oauth' + } else if (using3P) { authMethod = 'third_party' } else if (authTokenSource === 'claude.ai') { authMethod = 'claude.ai' @@ -311,6 +433,11 @@ export async function authStatus(opts: { output.orgId = oauthAccount?.organizationUuid ?? null output.orgName = oauthAccount?.organizationName ?? null output.subscriptionType = subscriptionType ?? null + } else if (authMethod === 'openai_oauth') { + output.provider = 'openai' + output.email = openaiTokens?.email ?? null + output.accountId = openaiTokens?.accountId ?? null + output.subscriptionType = 'ChatGPT Pro/Plus' } process.stdout.write(jsonStringify(output, null, 2) + '\n') @@ -318,7 +445,18 @@ export async function authStatus(opts: { process.exit(loggedIn ? 0 : 1) } -export async function authLogout(): Promise { +export async function authLogout(opts?: { openai?: boolean }): Promise { + if (opts?.openai) { + const openaiOAuthService = new OpenAIOAuthService() + const success = openaiOAuthService.logout() + if (!success) { + process.stderr.write('Failed to log out from OpenAI.\n') + process.exit(1) + } + process.stdout.write('Successfully logged out from your OpenAI account.\n') + process.exit(0) + } + try { await performLogout({ clearOnboarding: false }) } catch { diff --git a/src/commands/login/login.tsx b/src/commands/login/login.tsx index b532534d..9247b440 100644 --- a/src/commands/login/login.tsx +++ b/src/commands/login/login.tsx @@ -5,8 +5,8 @@ import { resetCostState } from '../../bootstrap/state.js'; import { clearTrustedDeviceToken, enrollTrustedDevice } from '../../bridge/trustedDevice.js'; import type { LocalJSXCommandContext } from '../../commands.js'; import { ConfigurableShortcutHint } from '../../components/ConfigurableShortcutHint.js'; -import { ConsoleOAuthFlow } from '../../components/ConsoleOAuthFlow.js'; import { Dialog } from '../../components/design-system/Dialog.js'; +import { SlashLoginFlow } from '../../components/SlashLoginFlow.js'; import { useMainLoopModel } from '../../hooks/useMainLoopModel.js'; import { Text } from '../../ink.js'; import { refreshGrowthBookAfterAuthChange } from '../../services/analytics/growthbook.js'; @@ -80,7 +80,7 @@ export function Login(props) { } let t2; if ($[6] !== props.startingMessage || $[7] !== t1) { - t2 = ; + t2 = ; $[6] = props.startingMessage; $[7] = t1; $[8] = t2; @@ -101,4 +101,4 @@ export function Login(props) { function _temp(exitState) { return exitState.pending ? Press {exitState.keyName} again to exit : ; } -//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJmZWF0dXJlIiwiUmVhY3QiLCJyZXNldENvc3RTdGF0ZSIsImNsZWFyVHJ1c3RlZERldmljZVRva2VuIiwiZW5yb2xsVHJ1c3RlZERldmljZSIsIkxvY2FsSlNYQ29tbWFuZENvbnRleHQiLCJDb25maWd1cmFibGVTaG9ydGN1dEhpbnQiLCJDb25zb2xlT0F1dGhGbG93IiwiRGlhbG9nIiwidXNlTWFpbkxvb3BNb2RlbCIsIlRleHQiLCJyZWZyZXNoR3Jvd3RoQm9va0FmdGVyQXV0aENoYW5nZSIsInJlZnJlc2hQb2xpY3lMaW1pdHMiLCJyZWZyZXNoUmVtb3RlTWFuYWdlZFNldHRpbmdzIiwiTG9jYWxKU1hDb21tYW5kT25Eb25lIiwic3RyaXBTaWduYXR1cmVCbG9ja3MiLCJjaGVja0FuZERpc2FibGVBdXRvTW9kZUlmTmVlZGVkIiwiY2hlY2tBbmREaXNhYmxlQnlwYXNzUGVybWlzc2lvbnNJZk5lZWRlZCIsInJlc2V0QXV0b01vZGVHYXRlQ2hlY2siLCJyZXNldEJ5cGFzc1Blcm1pc3Npb25zQ2hlY2siLCJyZXNldFVzZXJDYWNoZSIsImNhbGwiLCJvbkRvbmUiLCJjb250ZXh0IiwiUHJvbWlzZSIsIlJlYWN0Tm9kZSIsInN1Y2Nlc3MiLCJvbkNoYW5nZUFQSUtleSIsInNldE1lc3NhZ2VzIiwiYXBwU3RhdGUiLCJnZXRBcHBTdGF0ZSIsInRvb2xQZXJtaXNzaW9uQ29udGV4dCIsInNldEFwcFN0YXRlIiwiZmFzdE1vZGUiLCJwcmV2IiwiYXV0aFZlcnNpb24iLCJMb2dpbiIsInByb3BzIiwiJCIsIl9jIiwibWFpbkxvb3BNb2RlbCIsInQwIiwidDEiLCJ0MiIsInN0YXJ0aW5nTWVzc2FnZSIsInQzIiwiX3RlbXAiLCJleGl0U3RhdGUiLCJwZW5kaW5nIiwia2V5TmFtZSJdLCJzb3VyY2VzIjpbImxvZ2luLnRzeCJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBmZWF0dXJlIH0gZnJvbSAnYnVuOmJ1bmRsZSdcbmltcG9ydCAqIGFzIFJlYWN0IGZyb20gJ3JlYWN0J1xuaW1wb3J0IHsgcmVzZXRDb3N0U3RhdGUgfSBmcm9tICcuLi8uLi9ib290c3RyYXAvc3RhdGUuanMnXG5pbXBvcnQge1xuICBjbGVhclRydXN0ZWREZXZpY2VUb2tlbixcbiAgZW5yb2xsVHJ1c3RlZERldmljZSxcbn0gZnJvbSAnLi4vLi4vYnJpZGdlL3RydXN0ZWREZXZpY2UuanMnXG5pbXBvcnQgdHlwZSB7IExvY2FsSlNYQ29tbWFuZENvbnRleHQgfSBmcm9tICcuLi8uLi9jb21tYW5kcy5qcydcbmltcG9ydCB7IENvbmZpZ3VyYWJsZVNob3J0Y3V0SGludCB9IGZyb20gJy4uLy4uL2NvbXBvbmVudHMvQ29uZmlndXJhYmxlU2hvcnRjdXRIaW50LmpzJ1xuaW1wb3J0IHsgQ29uc29sZU9BdXRoRmxvdyB9IGZyb20gJy4uLy4uL2NvbXBvbmVudHMvQ29uc29sZU9BdXRoRmxvdy5qcydcbmltcG9ydCB7IERpYWxvZyB9IGZyb20gJy4uLy4uL2NvbXBvbmVudHMvZGVzaWduLXN5c3RlbS9EaWFsb2cuanMnXG5pbXBvcnQgeyB1c2VNYWluTG9vcE1vZGVsIH0gZnJvbSAnLi4vLi4vaG9va3MvdXNlTWFpbkxvb3BNb2RlbC5qcydcbmltcG9ydCB7IFRleHQgfSBmcm9tICcuLi8uLi9pbmsuanMnXG5pbXBvcnQgeyByZWZyZXNoR3Jvd3RoQm9va0FmdGVyQXV0aENoYW5nZSB9IGZyb20gJy4uLy4uL3NlcnZpY2VzL2FuYWx5dGljcy9ncm93dGhib29rLmpzJ1xuaW1wb3J0IHsgcmVmcmVzaFBvbGljeUxpbWl0cyB9IGZyb20gJy4uLy4uL3NlcnZpY2VzL3BvbGljeUxpbWl0cy9pbmRleC5qcydcbmltcG9ydCB7IHJlZnJlc2hSZW1vdGVNYW5hZ2VkU2V0dGluZ3MgfSBmcm9tICcuLi8uLi9zZXJ2aWNlcy9yZW1vdGVNYW5hZ2VkU2V0dGluZ3MvaW5kZXguanMnXG5pbXBvcnQgdHlwZSB7IExvY2FsSlNYQ29tbWFuZE9uRG9uZSB9IGZyb20gJy4uLy4uL3R5cGVzL2NvbW1hbmQuanMnXG5pbXBvcnQgeyBzdHJpcFNpZ25hdHVyZUJsb2NrcyB9IGZyb20gJy4uLy4uL3V0aWxzL21lc3NhZ2VzLmpzJ1xuaW1wb3J0IHtcbiAgY2hlY2tBbmREaXNhYmxlQXV0b01vZGVJZk5lZWRlZCxcbiAgY2hlY2tBbmREaXNhYmxlQnlwYXNzUGVybWlzc2lvbnNJZk5lZWRlZCxcbiAgcmVzZXRBdXRvTW9kZUdhdGVDaGVjayxcbiAgcmVzZXRCeXBhc3NQZXJtaXNzaW9uc0NoZWNrLFxufSBmcm9tICcuLi8uLi91dGlscy9wZXJtaXNzaW9ucy9ieXBhc3NQZXJtaXNzaW9uc0tpbGxzd2l0Y2guanMnXG5pbXBvcnQgeyByZXNldFVzZXJDYWNoZSB9IGZyb20gJy4uLy4uL3V0aWxzL3VzZXIuanMnXG5cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBjYWxsKFxuICBvbkRvbmU6IExvY2FsSlNYQ29tbWFuZE9uRG9uZSxcbiAgY29udGV4dDogTG9jYWxKU1hDb21tYW5kQ29udGV4dCxcbik6IFByb21pc2U8UmVhY3QuUmVhY3ROb2RlPiB7XG4gIHJldHVybiAoXG4gICAgPExvZ2luXG4gICAgICBvbkRvbmU9e2FzeW5jIHN1Y2Nlc3MgPT4ge1xuICAgICAgICBjb250ZXh0Lm9uQ2hhbmdlQVBJS2V5KClcbiAgICAgICAgLy8gU2lnbmF0dXJlLWJlYXJpbmcgYmxvY2tzICh0aGlua2luZywgY29ubmVjdG9yX3RleHQpIGFyZSBib3VuZCB0byB0aGUgQVBJIGtleSDigJRcbiAgICAgICAgLy8gc3RyaXAgdGhlbSBzbyB0aGUgbmV3IGtleSBkb2Vzbid0IHJlamVjdCBzdGFsZSBzaWduYXR1cmVzLlxuICAgICAgICBjb250ZXh0LnNldE1lc3NhZ2VzKHN0cmlwU2lnbmF0dXJlQmxvY2tzKVxuICAgICAgICBpZiAoc3VjY2Vzcykge1xuICAgICAgICAgIC8vIFBvc3QtbG9naW4gcmVmcmVzaCBsb2dpYy4gS2VlcCBpbiBzeW5jIHdpdGggb25ib2FyZGluZyBpbiBzcmMvaW50ZXJhY3RpdmVIZWxwZXJzLnRzeFxuICAgICAgICAgIC8vIFJlc2V0IGNvc3Qgc3RhdGUgd2hlbiBzd2l0Y2hpbmcgYWNjb3VudHNcbiAgICAgICAgICByZXNldENvc3RTdGF0ZSgpXG4gICAgICAgICAgLy8gUmVmcmVzaCByZW1vdGVseSBtYW5hZ2VkIHNldHRpbmdzIGFmdGVyIGxvZ2luIChub24tYmxvY2tpbmcpXG4gICAgICAgICAgdm9pZCByZWZyZXNoUmVtb3RlTWFuYWdlZFNldHRpbmdzKClcbiAgICAgICAgICAvLyBSZWZyZXNoIHBvbGljeSBsaW1pdHMgYWZ0ZXIgbG9naW4gKG5vbi1ibG9ja2luZylcbiAgICAgICAgICB2b2lkIHJlZnJlc2hQb2xpY3lMaW1pdHMoKVxuICAgICAgICAgIC8vIENsZWFyIHVzZXIgZGF0YSBjYWNoZSBCRUZPUkUgR3Jvd3RoQm9vayByZWZyZXNoIHNvIGl0IHBpY2tzIHVwIGZyZXNoIGNyZWRlbnRpYWxzXG4gICAgICAgICAgcmVzZXRVc2VyQ2FjaGUoKVxuICAgICAgICAgIC8vIFJlZnJlc2ggR3Jvd3RoQm9vayBhZnRlciBsb2dpbiB0byBnZXQgdXBkYXRlZCBmZWF0dXJlIGZsYWdzIChlLmcuLCBmb3IgY2xhdWRlLmFpIE1DUHMpXG4gICAgICAgICAgcmVmcmVzaEdyb3d0aEJvb2tBZnRlckF1dGhDaGFuZ2UoKVxuICAgICAgICAgIC8vIENsZWFyIGFueSBzdGFsZSB0cnVzdGVkIGRldmljZSB0b2tlbiBmcm9tIGEgcHJldmlvdXMgYWNjb3VudCBiZWZvcmVcbiAgICAgICAgICAvLyByZS1lbnJvbGxpbmcg4oCUIHByZXZlbnRzIHNlbmRpbmcgdGhlIG9sZCB0b2tlbiBvbiBicmlkZ2UgY2FsbHMgd2hpbGVcbiAgICAgICAgICAvLyB0aGUgYXN5bmMgZW5yb2xsVHJ1c3RlZERldmljZSgpIGlzIGluLWZsaWdodC5cbiAgICAgICAgICBjbGVhclRydXN0ZWREZXZpY2VUb2tlbigpXG4gICAgICAgICAgLy8gRW5yb2xsIGFzIGEgdHJ1c3RlZCBkZXZpY2UgZm9yIFJlbW90ZSBDb250cm9sICgxMC1taW4gZnJlc2gtc2Vzc2lvbiB3aW5kb3cpXG4gICAgICAgICAgdm9pZCBlbnJvbGxUcnVzdGVkRGV2aWNlKClcbiAgICAgICAgICAvLyBSZXNldCBraWxsc3dpdGNoIGdhdGUgY2hlY2tzIGFuZCByZS1ydW4gd2l0aCBuZXcgb3JnXG4gICAgICAgICAgcmVzZXRCeXBhc3NQZXJtaXNzaW9uc0NoZWNrKClcbiAgICAgICAgICBjb25zdCBhcHBTdGF0ZSA9IGNvbnRleHQuZ2V0QXBwU3RhdGUoKVxuICAgICAgICAgIHZvaWQgY2hlY2tBbmREaXNhYmxlQnlwYXNzUGVybWlzc2lvbnNJZk5lZWRlZChcbiAgICAgICAgICAgIGFwcFN0YXRlLnRvb2xQZXJtaXNzaW9uQ29udGV4dCxcbiAgICAgICAgICAgIGNvbnRleHQuc2V0QXBwU3RhdGUsXG4gICAgICAgICAgKVxuICAgICAgICAgIGlmIChmZWF0dXJlKCdUUkFOU0NSSVBUX0NMQVNTSUZJRVInKSkge1xuICAgICAgICAgICAgcmVzZXRBdXRvTW9kZUdhdGVDaGVjaygpXG4gICAgICAgICAgICB2b2lkIGNoZWNrQW5kRGlzYWJsZUF1dG9Nb2RlSWZOZWVkZWQoXG4gICAgICAgICAgICAgIGFwcFN0YXRlLnRvb2xQZXJtaXNzaW9uQ29udGV4dCxcbiAgICAgICAgICAgICAgY29udGV4dC5zZXRBcHBTdGF0ZSxcbiAgICAgICAgICAgICAgYXBwU3RhdGUuZmFzdE1vZGUsXG4gICAgICAgICAgICApXG4gICAgICAgICAgfVxuICAgICAgICAgIC8vIEluY3JlbWVudCBhdXRoVmVyc2lvbiB0byB0cmlnZ2VyIHJlLWZldGNoaW5nIG9mIGF1dGgtZGVwZW5kZW50IGRhdGEgaW4gaG9va3MgKGUuZy4sIE1DUCBzZXJ2ZXJzKVxuICAgICAgICAgIGNvbnRleHQuc2V0QXBwU3RhdGUocHJldiA9PiAoe1xuICAgICAgICAgICAgLi4ucHJldixcbiAgICAgICAgICAgIGF1dGhWZXJzaW9uOiBwcmV2LmF1dGhWZXJzaW9uICsgMSxcbiAgICAgICAgICB9KSlcbiAgICAgICAgfVxuICAgICAgICBvbkRvbmUoc3VjY2VzcyA/ICdMb2dpbiBzdWNjZXNzZnVsJyA6ICdMb2dpbiBpbnRlcnJ1cHRlZCcpXG4gICAgICB9fVxuICAgIC8+XG4gIClcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIExvZ2luKHByb3BzOiB7XG4gIG9uRG9uZTogKHN1Y2Nlc3M6IGJvb2xlYW4sIG1haW5Mb29wTW9kZWw6IHN0cmluZykgPT4gdm9pZFxuICBzdGFydGluZ01lc3NhZ2U/OiBzdHJpbmdcbn0pOiBSZWFjdC5SZWFjdE5vZGUge1xuICBjb25zdCBtYWluTG9vcE1vZGVsID0gdXNlTWFpbkxvb3BNb2RlbCgpXG5cbiAgcmV0dXJuIChcbiAgICA8RGlhbG9nXG4gICAgICB0aXRsZT1cIkxvZ2luXCJcbiAgICAgIG9uQ2FuY2VsPXsoKSA9PiBwcm9wcy5vbkRvbmUoZmFsc2UsIG1haW5Mb29wTW9kZWwpfVxuICAgICAgY29sb3I9XCJwZXJtaXNzaW9uXCJcbiAgICAgIGlucHV0R3VpZGU9e2V4aXRTdGF0ZSA9PlxuICAgICAgICBleGl0U3RhdGUucGVuZGluZyA/IChcbiAgICAgICAgICA8VGV4dD5QcmVzcyB7ZXhpdFN0YXRlLmtleU5hbWV9IGFnYWluIHRvIGV4aXQ8L1RleHQ+XG4gICAgICAgICkgOiAoXG4gICAgICAgICAgPENvbmZpZ3VyYWJsZVNob3J0Y3V0SGludFxuICAgICAgICAgICAgYWN0aW9uPVwiY29uZmlybTpub1wiXG4gICAgICAgICAgICBjb250ZXh0PVwiQ29uZmlybWF0aW9uXCJcbiAgICAgICAgICAgIGZhbGxiYWNrPVwiRXNjXCJcbiAgICAgICAgICAgIGRlc2NyaXB0aW9uPVwiY2FuY2VsXCJcbiAgICAgICAgICAvPlxuICAgICAgICApXG4gICAgICB9XG4gICAgPlxuICAgICAgPENvbnNvbGVPQXV0aEZsb3dcbiAgICAgICAgb25Eb25lPXsoKSA9PiBwcm9wcy5vbkRvbmUodHJ1ZSwgbWFpbkxvb3BNb2RlbCl9XG4gICAgICAgIHN0YXJ0aW5nTWVzc2FnZT17cHJvcHMu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 \ No newline at end of file +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJmZWF0dXJlIiwiUmVhY3QiLCJyZXNldENvc3RTdGF0ZSIsImNsZWFyVHJ1c3RlZERldmljZVRva2VuIiwiZW5yb2xsVHJ1c3RlZERldmljZSIsIkxvY2FsSlNYQ29tbWFuZENvbnRleHQiLCJDb25maWd1cmFibGVTaG9ydGN1dEhpbnQiLCJDb25zb2xlT0F1dGhGbG93IiwiRGlhbG9nIiwidXNlTWFpbkxvb3BNb2RlbCIsIlRleHQiLCJyZWZyZXNoR3Jvd3RoQm9va0FmdGVyQXV0aENoYW5nZSIsInJlZnJlc2hQb2xpY3lMaW1pdHMiLCJyZWZyZXNoUmVtb3RlTWFuYWdlZFNldHRpbmdzIiwiTG9jYWxKU1hDb21tYW5kT25Eb25lIiwic3RyaXBTaWduYXR1cmVCbG9ja3MiLCJjaGVja0FuZERpc2FibGVBdXRvTW9kZUlmTmVlZGVkIiwiY2hlY2tBbmREaXNhYmxlQnlwYXNzUGVybWlzc2lvbnNJZk5lZWRlZCIsInJlc2V0QXV0b01vZGVHYXRlQ2hlY2siLCJyZXNldEJ5cGFzc1Blcm1pc3Npb25zQ2hlY2siLCJyZXNldFVzZXJDYWNoZSIsImNhbGwiLCJvbkRvbmUiLCJjb250ZXh0IiwiUHJvbWlzZSIsIlJlYWN0Tm9kZSIsInN1Y2Nlc3MiLCJvbkNoYW5nZUFQSUtleSIsInNldE1lc3NhZ2VzIiwiYXBwU3RhdGUiLCJnZXRBcHBTdGF0ZSIsInRvb2xQZXJtaXNzaW9uQ29udGV4dCIsInNldEFwcFN0YXRlIiwiZmFzdE1vZGUiLCJwcmV2IiwiYXV0aFZlcnNpb24iLCJMb2dpbiIsInByb3BzIiwiJCIsIl9jIiwibWFpbkxvb3BNb2RlbCIsInQwIiwidDEiLCJ0MiIsInN0YXJ0aW5nTWVzc2FnZSIsInQzIiwiX3RlbXAiLCJleGl0U3RhdGUiLCJwZW5kaW5nIiwia2V5TmFtZSJdLCJzb3VyY2VzIjpbImxvZ2luLnRzeCJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBmZWF0dXJlIH0gZnJvbSAnYnVuOmJ1bmRsZSdcbmltcG9ydCAqIGFzIFJlYWN0IGZyb20gJ3JlYWN0J1xuaW1wb3J0IHsgcmVzZXRDb3N0U3RhdGUgfSBmcm9tICcuLi8uLi9ib290c3RyYXAvc3RhdGUuanMnXG5pbXBvcnQge1xuICBjbGVhclRydXN0ZWREZXZpY2VUb2tlbixcbiAgZW5yb2xsVHJ1c3RlZERldmljZSxcbn0gZnJvbSAnLi4vLi4vYnJpZGdlL3RydXN0ZWREZXZpY2UuanMnXG5pbXBvcnQgdHlwZSB7IExvY2FsSlNYQ29tbWFuZENvbnRleHQgfSBmcm9tICcuLi8uLi9jb21tYW5kcy5qcydcbmltcG9ydCB7IENvbmZpZ3VyYWJsZVNob3J0Y3V0SGludCB9IGZyb20gJy4uLy4uL2NvbXBvbmVudHMvQ29uZmlndXJhYmxlU2hvcnRjdXRIaW50LmpzJ1xuaW1wb3J0IHsgQ29uc29sZU9BdXRoRmxvdyB9IGZyb20gJy4uLy4uL2NvbXBvbmVudHMvQ29uc29sZU9BdXRoRmxvdy5qcydcbmltcG9ydCB7IERpYWxvZyB9IGZyb20gJy4uLy4uL2NvbXBvbmVudHMvZGVzaWduLXN5c3RlbS9EaWFsb2cuanMnXG5pbXBvcnQgeyB1c2VNYWluTG9vcE1vZGVsIH0gZnJvbSAnLi4vLi4vaG9va3MvdXNlTWFpbkxvb3BNb2RlbC5qcydcbmltcG9ydCB7IFRleHQgfSBmcm9tICcuLi8uLi9pbmsuanMnXG5pbXBvcnQgeyByZWZyZXNoR3Jvd3RoQm9va0FmdGVyQXV0aENoYW5nZSB9IGZyb20gJy4uLy4uL3NlcnZpY2VzL2FuYWx5dGljcy9ncm93dGhib29rLmpzJ1xuaW1wb3J0IHsgcmVmcmVzaFBvbGljeUxpbWl0cyB9IGZyb20gJy4uLy4uL3NlcnZpY2VzL3BvbGljeUxpbWl0cy9pbmRleC5qcydcbmltcG9ydCB7IHJlZnJlc2hSZW1vdGVNYW5hZ2VkU2V0dGluZ3MgfSBmcm9tICcuLi8uLi9zZXJ2aWNlcy9yZW1vdGVNYW5hZ2VkU2V0dGluZ3MvaW5kZXguanMnXG5pbXBvcnQgdHlwZSB7IExvY2FsSlNYQ29tbWFuZE9uRG9uZSB9IGZyb20gJy4uLy4uL3R5cGVzL2NvbW1hbmQuanMnXG5pbXBvcnQgeyBzdHJpcFNpZ25hdHVyZUJsb2NrcyB9IGZyb20gJy4uLy4uL3V0aWxzL21lc3NhZ2VzLmpzJ1xuaW1wb3J0IHtcbiAgY2hlY2tBbmREaXNhYmxlQXV0b01vZGVJZk5lZWRlZCxcbiAgY2hlY2tBbmREaXNhYmxlQnlwYXNzUGVybWlzc2lvbnNJZk5lZWRlZCxcbiAgcmVzZXRBdXRvTW9kZUdhdGVDaGVjayxcbiAgcmVzZXRCeXBhc3NQZXJtaXNzaW9uc0NoZWNrLFxufSBmcm9tICcuLi8uLi91dGlscy9wZXJtaXNzaW9ucy9ieXBhc3NQZXJtaXNzaW9uc0tpbGxzd2l0Y2guanMnXG5pbXBvcnQgeyByZXNldFVzZXJDYWNoZSB9IGZyb20gJy4uLy4uL3V0aWxzL3VzZXIuanMnXG5cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBjYWxsKFxuICBvbkRvbmU6IExvY2FsSlNYQ29tbWFuZE9uRG9uZSxcbiAgY29udGV4dDogTG9jYWxKU1hDb21tYW5kQ29udGV4dCxcbik6IFByb21pc2U8UmVhY3QuUmVhY3ROb2RlPiB7XG4gIHJldHVybiAoXG4gICAgPExvZ2luXG4gICAgICBvbkRvbmU9e2FzeW5jIHN1Y2Nlc3MgPT4ge1xuICAgICAgICBjb250ZXh0Lm9uQ2hhbmdlQVBJS2V5KClcbiAgICAgICAgLy8gU2lnbmF0dXJlLWJlYXJpbmcgYmxvY2tzICh0aGlua2luZywgY29ubmVjdG9yX3RleHQpIGFyZSBib3VuZCB0byB0aGUgQVBJIGtleSDigJRcbiAgICAgICAgLy8gc3RyaXAgdGhlbSBzbyB0aGUgbmV3IGtleSBkb2Vzbid0IHJlamVjdCBzdGFsZSBzaWduYXR1cmVzLlxuICAgICAgICBjb250ZXh0LnNldE1lc3NhZ2VzKHN0cmlwU2lnbmF0dXJlQmxvY2tzKVxuICAgICAgICBpZiAoc3VjY2Vzcykge1xuICAgICAgICAgIC8vIFBvc3QtbG9naW4gcmVmcmVzaCBsb2dpYy4gS2VlcCBpbiBzeW5jIHdpdGggb25ib2FyZGluZyBpbiBzcmMvaW50ZXJhY3RpdmVIZWxwZXJzLnRzeFxuICAgICAgICAgIC8vIFJlc2V0IGNvc3Qgc3RhdGUgd2hlbiBzd2l0Y2hpbmcgYWNjb3VudHNcbiAgICAgICAgICByZXNldENvc3RTdGF0ZSgpXG4gICAgICAgICAgLy8gUmVmcmVzaCByZW1vdGVseSBtYW5hZ2VkIHNldHRpbmdzIGFmdGVyIGxvZ2luIChub24tYmxvY2tpbmcpXG4gICAgICAgICAgdm9pZCByZWZyZXNoUmVtb3RlTWFuYWdlZFNldHRpbmdzKClcbiAgICAgICAgICAvLyBSZWZyZXNoIHBvbGljeSBsaW1pdHMgYWZ0ZXIgbG9naW4gKG5vbi1ibG9ja2luZylcbiAgICAgICAgICB2b2lkIHJlZnJlc2hQb2xpY3lMaW1pdHMoKVxuICAgICAgICAgIC8vIENsZWFyIHVzZXIgZGF0YSBjYWNoZSBCRUZPUkUgR3Jvd3RoQm9vayByZWZyZXNoIHNvIGl0IHBpY2tzIHVwIGZyZXNoIGNyZWRlbnRpYWxzXG4gICAgICAgICAgcmVzZXRVc2VyQ2FjaGUoKVxuICAgICAgICAgIC8vIFJlZnJlc2ggR3Jvd3RoQm9vayBhZnRlciBsb2dpbiB0byBnZXQgdXBkYXRlZCBmZWF0dXJlIGZsYWdzIChlLmcuLCBmb3IgY2xhdWRlLmFpIE1DUHMpXG4gICAgICAgICAgcmVmcmVzaEdyb3d0aEJvb2tBZnRlckF1dGhDaGFuZ2UoKVxuICAgICAgICAgIC8vIENsZWFyIGFueSBzdGFsZSB0cnVzdGVkIGRldmljZSB0b2tlbiBmcm9tIGEgcHJldmlvdXMgYWNjb3VudCBiZWZvcmVcbiAgICAgICAgICAvLyByZS1lbnJvbGxpbmcg4oCUIHByZXZlbnRzIHNlbmRpbmcgdGhlIG9sZCB0b2tlbiBvbiBicmlkZ2UgY2FsbHMgd2hpbGVcbiAgICAgICAgICAvLyB0aGUgYXN5bmMgZW5yb2xsVHJ1c3RlZERldmljZSgpIGlzIGluLWZsaWdodC5cbiAgICAgICAgICBjbGVhclRydXN0ZWREZXZpY2VUb2tlbigpXG4gICAgICAgICAgLy8gRW5yb2xsIGFzIGEgdHJ1c3RlZCBkZXZpY2UgZm9yIFJlbW90ZSBDb250cm9sICgxMC1taW4gZnJlc2gtc2Vzc2lvbiB3aW5kb3cpXG4gICAgICAgICAgdm9pZCBlbnJvbGxUcnVzdGVkRGV2aWNlKClcbiAgICAgICAgICAvLyBSZXNldCBraWxsc3dpdGNoIGdhdGUgY2hlY2tzIGFuZCByZS1ydW4gd2l0aCBuZXcgb3JnXG4gICAgICAgICAgcmVzZXRCeXBhc3NQZXJtaXNzaW9uc0NoZWNrKClcbiAgICAgICAgICBjb25zdCBhcHBTdGF0ZSA9IGNvbnRleHQuZ2V0QXBwU3RhdGUoKVxuICAgICAgICAgIHZvaWQgY2hlY2tBbmREaXNhYmxlQnlwYXNzUGVybWlzc2lvbnNJZk5lZWRlZChcbiAgICAgICAgICAgIGFwcFN0YXRlLnRvb2xQZXJtaXNzaW9uQ29udGV4dCxcbiAgICAgICAgICAgIGNvbnRleHQuc2V0QXBwU3RhdGUsXG4gICAgICAgICAgKVxuICAgICAgICAgIGlmIChmZWF0dXJlKCdUUkFOU0NSSVBUX0NMQVNTSUZJRVInKSkge1xuICAgICAgICAgICAgcmVzZXRBdXRvTW9kZUdhdGVDaGVjaygpXG4gICAgICAgICAgICB2b2lkIGNoZWNrQW5kRGlzYWJsZUF1dG9Nb2RlSWZOZWVkZWQoXG4gICAgICAgICAgICAgIGFwcFN0YXRlLnRvb2xQZXJtaXNzaW9uQ29udGV4dCxcbiAgICAgICAgICAgICAgY29udGV4dC5zZXRBcHBTdGF0ZSxcbiAgICAgICAgICAgICAgYXBwU3RhdGUuZmFzdE1vZGUsXG4gICAgICAgICAgICApXG4gICAgICAgICAgfVxuICAgICAgICAgIC8vIEluY3JlbWVudCBhdXRoVmVyc2lvbiB0byB0cmlnZ2VyIHJlLWZldGNoaW5nIG9mIGF1dGgtZGVwZW5kZW50IGRhdGEgaW4gaG9va3MgKGUuZy4sIE1DUCBzZXJ2ZXJzKVxuICAgICAgICAgIGNvbnRleHQuc2V0QXBwU3RhdGUocHJldiA9PiAoe1xuICAgICAgICAgICAgLi4ucHJldixcbiAgICAgICAgICAgIGF1dGhWZXJzaW9uOiBwcmV2LmF1dGhWZXJzaW9uICsgMSxcbiAgICAgICAgICB9KSlcbiAgICAgICAgfVxuICAgICAgICBvbkRvbmUoc3VjY2VzcyA/ICdMb2dpbiBzdWNjZXNzZnVsJyA6ICdMb2dpbiBpbnRlcnJ1cHRlZCcpXG4gICAgICB9fVxuICAgIC8+XG4gIClcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIExvZ2luKHByb3BzOiB7XG4gIG9uRG9uZTogKHN1Y2Nlc3M6IGJvb2xlYW4sIG1haW5Mb29wTW9kZWw6IHN0cmluZykgPT4gdm9pZFxuICBzdGFydGluZ01lc3NhZ2U/OiBzdHJpbmdcbn0pOiBSZWFjdC5SZWFjdE5vZGUge1xuICBjb25zdCBtYWluTG9vcE1vZGVsID0gdXNlTWFpbkxvb3BNb2RlbCgpXG5cbiAgcmV0dXJuIChcbiAgICA8RGlhbG9nXG4gICAgICB0aXRsZT1cIkxvZ2luXCJcbiAgICAgIG9uQ2FuY2VsPXsoKSA9PiBwcm9wcy5vbkRvbmUoZmFsc2UsIG1haW5Mb29wTW9kZWwpfVxuICAgICAgY29sb3I9XCJwZXJtaXNzaW9uXCJcbiAgICAgIGlucHV0R3VpZGU9e2V4aXRTdGF0ZSA9PlxuICAgICAgICBleGl0U3RhdGUucGVuZGluZyA/IChcbiAgICAgICAgICA8VGV4dD5QcmVzcyB7ZXhpdFN0YXRlLmtleU5hbWV9IGFnYWluIHRvIGV4aXQ8L1RleHQ+XG4gICAgICAgICkgOiAoXG4gICAgICAgICAgPENvbmZpZ3VyYWJsZVNob3J0Y3V0SGludFxuICAgICAgICAgICAgYWN0aW9uPVwiY29uZmlybTpub1wiXG4gICAgICAgICAgICBjb250ZXh0PVwiQ29uZmlybWF0aW9uXCJcbiAgICAgICAgICAgIGZhbGxiYWNrPVwiRXNjXCJcbiAgICAgICAgICAgIGRlc2NyaXB0aW9uPVwiY2FuY2VsXCJcbiAgICAgICAgICAvPlxuICAgICAgICApXG4gICAgICB9XG4gICAgPlxuICAgICAgPENvbnNvbGVPQXV0aEZsb3dcbiAgICAgICAgb25Eb25lPXsoKSA9PiBwcm9wcy5vbkRvbmUodHJ1ZSwgbWFpbkxvb3BNb2RlbCl9XG4gICAgICAgIHN0YXJ0aW5nTWVzc2FnZT17cHJvcHMu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 diff --git a/src/components/OpenAILoginFlow.tsx b/src/components/OpenAILoginFlow.tsx new file mode 100644 index 00000000..1ba11121 --- /dev/null +++ b/src/components/OpenAILoginFlow.tsx @@ -0,0 +1,124 @@ +import React, { useCallback, useEffect, useState } from 'react' +import { + type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, + logEvent, +} from 'src/services/analytics/index.js' +import { installOpenAIOAuthTokens } from '../cli/handlers/auth.js' +import { Box, Link, Text } from '../ink.js' +import { useKeybinding } from '../keybindings/useKeybinding.js' +import { OpenAIOAuthService } from '../services/openaiAuth/index.js' +import { getOpenAIOAuthTokens } from '../services/openaiAuth/storage.js' +import { logError } from '../utils/log.js' +import { Spinner } from './Spinner.js' + +type Props = { + onDone(): void + startingMessage?: string +} + +type OpenAILoginStatus = + | { state: 'ready_to_start' } + | { state: 'waiting_for_login'; url: string } + | { state: 'success'; warning?: string | null } + | { state: 'error'; message: string } + +export function OpenAILoginFlow({ + onDone, + startingMessage, +}: Props): React.ReactNode { + const [oauthService] = useState(() => new OpenAIOAuthService()) + const [status, setStatus] = useState({ + state: 'ready_to_start', + }) + + useKeybinding( + 'confirm:yes', + () => { + logEvent('tengu_openai_oauth_success', {}) + onDone() + }, + { + context: 'Confirmation', + isActive: status.state === 'success', + }, + ) + + const startOAuth = useCallback(async () => { + try { + logEvent('tengu_openai_oauth_flow_start', {}) + const tokens = await oauthService.startOAuthFlow(async url => { + setStatus({ state: 'waiting_for_login', url }) + }) + const warning = await installOpenAIOAuthTokens(tokens) + setStatus({ state: 'success', warning }) + } catch (error) { + logError(error) + setStatus({ + state: 'error', + message: (error as Error).message, + }) + logEvent('tengu_openai_oauth_error', { + error: (error as Error) + .message as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, + }) + } + }, [oauthService]) + + useEffect(() => { + if (status.state !== 'ready_to_start') { + return + } + + void startOAuth() + }, [startOAuth, status.state]) + + useEffect(() => { + return () => { + oauthService.cleanup() + } + }, [oauthService]) + + const account = getOpenAIOAuthTokens() + + return ( + + + {startingMessage ?? + 'Claude Code can also run through your ChatGPT subscription via OpenAI auth.'} + + + {status.state === 'waiting_for_login' && ( + + + + Opening browser to sign in to OpenAI… + + Use the URL below if your browser did not open: + + {status.url} + + + )} + + {status.state === 'success' && ( + + {account?.email ? ( + + Logged in as {account.email} + + ) : null} + {status.warning ? ( + {status.warning} + ) : null} + + Login successful. Press Enter to continue… + + + )} + + {status.state === 'error' && ( + OpenAI OAuth error: {status.message} + )} + + ) +} diff --git a/src/components/SlashLoginFlow.tsx b/src/components/SlashLoginFlow.tsx new file mode 100644 index 00000000..e14e4cb7 --- /dev/null +++ b/src/components/SlashLoginFlow.tsx @@ -0,0 +1,157 @@ +import React, { useMemo, useState } from 'react' +import { Box, Link, Text } from '../ink.js' +import { useKeybinding } from '../keybindings/useKeybinding.js' +import { getSettings_DEPRECATED } from '../utils/settings/settings.js' +import { ConsoleOAuthFlow } from './ConsoleOAuthFlow.js' +import { Select } from './CustomSelect/select.js' +import { OpenAILoginFlow } from './OpenAILoginFlow.js' + +type Props = { + onDone(): void + startingMessage?: string +} + +type LoginSelection = 'claudeai' | 'console' | 'openai' | 'platform' | 'idle' + +function PlatformSetupFlow({ + onBack, +}: { + onBack(): void +}): React.ReactNode { + useKeybinding( + 'confirm:yes', + () => { + onBack() + }, + { + context: 'Confirmation', + isActive: true, + }, + ) + + return ( + + Using 3rd-party platforms + + Claude Code supports Amazon Bedrock, Microsoft Foundry, and Vertex AI. + Set the required environment variables, then restart Claude Code. + + + If you are part of an enterprise organization, contact your + administrator for setup instructions. + + + Documentation: + + · Amazon Bedrock:{' '} + + + + · Microsoft Foundry:{' '} + + + + · Vertex AI:{' '} + + + + + Press Enter to go back to login options. + + + ) +} + +export function SlashLoginFlow({ + onDone, + startingMessage, +}: Props): React.ReactNode { + const settings = getSettings_DEPRECATED() || {} + const forceLoginMethod = settings.forceLoginMethod + + const [selection, setSelection] = useState(() => { + if (forceLoginMethod === 'claudeai' || forceLoginMethod === 'console') { + return forceLoginMethod + } + return 'idle' + }) + + const options = useMemo( + () => [ + { + label: ( + + Claude account with subscription ·{' '} + Pro, Max, Team, or Enterprise + {'\n'} + + ), + value: 'claudeai' as const, + }, + { + label: ( + + Anthropic Console account ·{' '} + API usage billing + {'\n'} + + ), + value: 'console' as const, + }, + { + label: ( + + OpenAI account · ChatGPT Pro/Plus + {'\n'} + + ), + value: 'openai' as const, + }, + { + label: ( + + 3rd-party platform ·{' '} + Amazon Bedrock, Microsoft Foundry, or Vertex AI + {'\n'} + + ), + value: 'platform' as const, + }, + ], + [], + ) + + if (selection === 'claudeai' || selection === 'console') { + return ( + + ) + } + + if (selection === 'openai') { + return + } + + if (selection === 'platform') { + return setSelection('idle')} /> + } + + return ( + + + {startingMessage ?? + 'Claude Code can be used with your Claude subscription, Anthropic Console billing, or your ChatGPT subscription.'} + + Select login method: + +