(preselectId?: string)
| 34 | * the Groq key while still using the friendly key-prompt + auto-wire path. |
| 35 | */ |
| 36 | export async function interactiveAddProvider(preselectId?: string): Promise<InteractiveAddResult | null> { |
| 37 | const opts: PromptOptions = { interactive: isInteractiveTTY() }; |
| 38 | if (!opts.interactive) { |
| 39 | throw new Error('Interactive add needs a TTY. Use: qodex provider add <id> --base-url <url> --key-env <ENV> [--model <id>]'); |
| 40 | } |
| 41 | |
| 42 | // 1. Resolve the gateway — either preselected or chosen from the list. |
| 43 | let spec: GatewaySpec | undefined; |
| 44 | let customName = ''; |
| 45 | let customBaseUrl = ''; |
| 46 | let customKeyEnv = ''; |
| 47 | |
| 48 | if (preselectId && findGateway(preselectId)) { |
| 49 | spec = findGateway(preselectId); |
| 50 | } else if (preselectId) { |
| 51 | // A name we don't know — treat it as a custom provider. If the user passed a |
| 52 | // URL (`provider add https://api.x.com/v1`), use it as the base URL and derive |
| 53 | // a valid slug name from its host; otherwise slugify the given name. |
| 54 | if (/^[a-z][a-z0-9+.-]*:\/\//i.test(preselectId.trim())) { |
| 55 | customBaseUrl = preselectId.trim(); |
| 56 | } |
| 57 | customName = slugifyProviderName(preselectId); |
| 58 | } else { |
| 59 | const ids = listGatewayIds(); |
| 60 | const choices = [ |
| 61 | ...ids.map(id => { |
| 62 | const g = KNOWN_GATEWAYS[id]!; |
| 63 | return { value: id, label: g.title, hint: g.baseUrl }; |
| 64 | }), |
| 65 | { value: CUSTOM_SENTINEL, label: 'Something else (custom OpenAI-compatible endpoint)', hint: 'enter base URL yourself' }, |
| 66 | ]; |
| 67 | const picked = await choose('\nWhich provider do you want to add?', choices, ids[0]!, opts); |
| 68 | if (picked === CUSTOM_SENTINEL) { |
| 69 | customName = slugifyProviderName(await text(' Provider name (one word, e.g. "myhost")', '', opts)); |
| 70 | if (!customName) { console.log(' ✗ Cancelled — no name given.'); return null; } |
| 71 | } else { |
| 72 | spec = findGateway(picked); |
| 73 | } |
| 74 | } |
| 75 | |
| 76 | // 2. For a custom provider, gather base URL + derive the env var name. |
| 77 | if (!spec) { |
| 78 | if (!customBaseUrl) { |
| 79 | customBaseUrl = (await text(' OpenAI-compatible base URL (…/v1)', '', opts)).trim(); |
| 80 | if (!customBaseUrl) { console.log(' ✗ Cancelled — no base URL given.'); return null; } |
| 81 | } |
| 82 | customKeyEnv = `${customName.toUpperCase().replace(/[^A-Z0-9]/g, '_')}_API_KEY`; |
| 83 | } |
| 84 | |
| 85 | const name = spec ? spec.name : customName; |
| 86 | const apiKeyEnv = spec ? spec.apiKeyEnv : customKeyEnv; |
| 87 | const title = spec ? spec.title : name; |
| 88 | |
| 89 | // If the provider came from the interactive menu (not a `provider add <id>` argument), confirm |
| 90 | // the pick before doing anything. This catches the case where a stray Enter — e.g. from pasting |
| 91 | // multiple lines into the terminal — lands on the default row and would otherwise silently |
| 92 | // configure the wrong provider. |
| 93 | if (!preselectId) { |
no test coverage detected