import * as React from 'react' import { useCallback, useState } from 'react' import { Select } from '../../components/CustomSelect/select.js' import { Spinner } from '../../components/Spinner.js' import TextInput from '../../components/TextInput.js' import { Box, Text } from '../../ink.js' import { openVerificationUri, pollAccessToken, requestDeviceCode, } from '../../services/github/deviceFlow.js' import type { LocalJSXCommandCall } from '../../types/command.js' import { hydrateGithubModelsTokenFromSecureStorage, saveGithubModelsToken, } from '../../utils/githubModelsCredentials.js' import { updateSettingsForSource } from '../../utils/settings/settings.js' const DEFAULT_MODEL = 'github:copilot' type Step = | 'menu' | 'device-busy' | 'pat' | 'error' function mergeUserSettingsEnv(model: string): { ok: boolean; detail?: string } { const { error } = updateSettingsForSource('userSettings', { env: { CLAUDE_CODE_USE_GITHUB: '1', OPENAI_MODEL: model, CLAUDE_CODE_USE_OPENAI: undefined as any, CLAUDE_CODE_USE_GEMINI: undefined as any, CLAUDE_CODE_USE_BEDROCK: undefined as any, CLAUDE_CODE_USE_VERTEX: undefined as any, CLAUDE_CODE_USE_FOUNDRY: undefined as any, }, }) if (error) { return { ok: false, detail: error.message } } return { ok: true } } function OnboardGithub(props: { onDone: Parameters[0] onChangeAPIKey: () => void }): React.ReactNode { const { onDone, onChangeAPIKey } = props const [step, setStep] = useState('menu') const [errorMsg, setErrorMsg] = useState(null) const [deviceHint, setDeviceHint] = useState<{ user_code: string verification_uri: string } | null>(null) const [patDraft, setPatDraft] = useState('') const [cursorOffset, setCursorOffset] = useState(0) const finalize = useCallback( async (token: string, model: string = DEFAULT_MODEL) => { const saved = saveGithubModelsToken(token) if (!saved.success) { setErrorMsg(saved.warning ?? 'Could not save token to secure storage.') setStep('error') return } const merged = mergeUserSettingsEnv(model.trim() || DEFAULT_MODEL) if (!merged.ok) { setErrorMsg( `Token saved, but settings were not updated: ${merged.detail ?? 'unknown error'}. ` + `Add env CLAUDE_CODE_USE_GITHUB=1 and OPENAI_MODEL to ~/.claude/settings.json manually.`, ) setStep('error') return } process.env.CLAUDE_CODE_USE_GITHUB = '1' process.env.OPENAI_MODEL = model.trim() || DEFAULT_MODEL hydrateGithubModelsTokenFromSecureStorage() onChangeAPIKey() onDone( 'GitHub Models onboard complete. Token stored in secure storage; user settings updated. Restart if the model does not switch.', { display: 'user' }, ) }, [onChangeAPIKey, onDone], ) const runDeviceFlow = useCallback(async () => { setStep('device-busy') setErrorMsg(null) setDeviceHint(null) try { const device = await requestDeviceCode() setDeviceHint({ user_code: device.user_code, verification_uri: device.verification_uri, }) await openVerificationUri(device.verification_uri) const token = await pollAccessToken(device.device_code, { initialInterval: device.interval, timeoutSeconds: device.expires_in, }) await finalize(token, DEFAULT_MODEL) } catch (e) { setErrorMsg(e instanceof Error ? e.message : String(e)) setStep('error') } }, [finalize]) if (step === 'error' && errorMsg) { const options = [ { label: 'Back to menu', value: 'back' as const, }, { label: 'Exit', value: 'exit' as const, }, ] return ( {errorMsg} { if (v === 'cancel') { onDone('GitHub onboard cancelled', { display: 'system' }) return } if (v === 'pat') { setStep('pat') return } void runDeviceFlow() }} /> ) } export const call: LocalJSXCommandCall = async (onDone, context) => { return ( ) }