feat(wizard): add BRAVE_ANSWERS_KEY support

Brave now uses separate API keys per plan:
- BRAVE_API_KEY (Search plan) → web search, LLM context, news, etc.
- BRAVE_ANSWERS_KEY (Answers plan) → chat/completions

Updated:
- wizard: prompts for and stores both keys
- loadStoredEnvKeys: hydrates BRAVE_ANSWERS_KEY from auth.json
- smoke tests: covers BRAVE_ANSWERS_KEY hydration
- verify-s03.sh: includes BRAVE_ANSWERS_KEY in env and structural checks
This commit is contained in:
Lex Christopherson 2026-03-10 22:44:28 -06:00
parent ec1c50c739
commit a4779f8e83
3 changed files with 22 additions and 4 deletions

View file

@ -88,6 +88,7 @@ tmp4=$(mktemp)
env -i HOME="$HOME" PATH="$PATH" \
ANTHROPIC_API_KEY="${ANTHROPIC_API_KEY:-}" \
BRAVE_API_KEY="test-brave" \
BRAVE_ANSWERS_KEY="test-answers" \
CONTEXT7_API_KEY="test-ctx7" \
JINA_API_KEY="test-jina" \
node dist/loader.js < /dev/null > "$tmp4" 2>&1
@ -111,8 +112,8 @@ echo "--- loadStoredEnvKeys hydration ---"
# ----------------------------------------------------------------
# Check 5 — Structural: env var names compiled into dist/wizard.js
# ----------------------------------------------------------------
if grep -q "BRAVE_API_KEY" dist/wizard.js && grep -q "CONTEXT7_API_KEY" dist/wizard.js && grep -q "JINA_API_KEY" dist/wizard.js; then
pass "5 — dist/wizard.js contains all three optional key env var names"
if grep -q "BRAVE_API_KEY" dist/wizard.js && grep -q "BRAVE_ANSWERS_KEY" dist/wizard.js && grep -q "CONTEXT7_API_KEY" dist/wizard.js && grep -q "JINA_API_KEY" dist/wizard.js; then
pass "5 — dist/wizard.js contains all four optional key env var names"
else
fail "5 — dist/wizard.js missing one or more optional key env var names"
fi

View file

@ -162,14 +162,17 @@ test("loadStoredEnvKeys hydrates process.env from auth.json", async () => {
const authPath = join(tmp, "auth.json");
writeFileSync(authPath, JSON.stringify({
brave: { type: "api_key", key: "test-brave-key" },
brave_answers: { type: "api_key", key: "test-answers-key" },
context7: { type: "api_key", key: "test-ctx7-key" },
}));
// Clear any existing env vars
const origBrave = process.env.BRAVE_API_KEY;
const origBraveAnswers = process.env.BRAVE_ANSWERS_KEY;
const origCtx7 = process.env.CONTEXT7_API_KEY;
const origJina = process.env.JINA_API_KEY;
delete process.env.BRAVE_API_KEY;
delete process.env.BRAVE_ANSWERS_KEY;
delete process.env.CONTEXT7_API_KEY;
delete process.env.JINA_API_KEY;
@ -178,11 +181,13 @@ test("loadStoredEnvKeys hydrates process.env from auth.json", async () => {
loadStoredEnvKeys(auth);
assert.equal(process.env.BRAVE_API_KEY, "test-brave-key", "BRAVE_API_KEY hydrated");
assert.equal(process.env.BRAVE_ANSWERS_KEY, "test-answers-key", "BRAVE_ANSWERS_KEY hydrated");
assert.equal(process.env.CONTEXT7_API_KEY, "test-ctx7-key", "CONTEXT7_API_KEY hydrated");
assert.equal(process.env.JINA_API_KEY, undefined, "JINA_API_KEY not set (not in auth)");
} finally {
// Restore original env
if (origBrave) process.env.BRAVE_API_KEY = origBrave; else delete process.env.BRAVE_API_KEY;
if (origBraveAnswers) process.env.BRAVE_ANSWERS_KEY = origBraveAnswers; else delete process.env.BRAVE_ANSWERS_KEY;
if (origCtx7) process.env.CONTEXT7_API_KEY = origCtx7; else delete process.env.CONTEXT7_API_KEY;
if (origJina) process.env.JINA_API_KEY = origJina; else delete process.env.JINA_API_KEY;
rmSync(tmp, { recursive: true, force: true });
@ -322,6 +327,7 @@ test("gsd launches and loads extensions without errors", async () => {
env: {
...process.env,
BRAVE_API_KEY: "test",
BRAVE_ANSWERS_KEY: "test",
CONTEXT7_API_KEY: "test",
JINA_API_KEY: "test",
},

View file

@ -60,6 +60,7 @@ async function promptMasked(question: string): Promise<string> {
export function loadStoredEnvKeys(authStorage: AuthStorage): void {
const providers: Array<[string, string]> = [
['brave', 'BRAVE_API_KEY'],
['brave_answers', 'BRAVE_ANSWERS_KEY'],
['context7', 'CONTEXT7_API_KEY'],
['jina', 'JINA_API_KEY'],
]
@ -87,15 +88,17 @@ export function loadStoredEnvKeys(authStorage: AuthStorage): void {
*/
export async function runWizardIfNeeded(authStorage: AuthStorage): Promise<void> {
const needsBrave = !authStorage.has('brave') && !process.env.BRAVE_API_KEY
const needsBraveAnswers = !authStorage.has('brave_answers') && !process.env.BRAVE_ANSWERS_KEY
const needsContext7 = !authStorage.has('context7') && !process.env.CONTEXT7_API_KEY
const needsJina = !authStorage.has('jina') && !process.env.JINA_API_KEY
if (!needsBrave && !needsContext7 && !needsJina) {
if (!needsBrave && !needsBraveAnswers && !needsContext7 && !needsJina) {
return
}
const missing = [
needsBrave && 'Brave Search',
needsBraveAnswers && 'Brave Answers',
needsContext7 && 'Context7',
needsJina && 'Jina',
]
@ -115,13 +118,21 @@ export async function runWizardIfNeeded(authStorage: AuthStorage): Promise<void>
process.stdout.write('[gsd] Press Enter to skip any key you want to set up later.\n\n')
if (needsBrave) {
const key = await promptMasked('Brave Search API key (optional): ')
const key = await promptMasked('Brave Search API key (optional, for web search + LLM context): ')
if (key) {
authStorage.set('brave', { type: 'api_key', key })
process.env.BRAVE_API_KEY = key
}
}
if (needsBraveAnswers) {
const key = await promptMasked('Brave Answers API key (optional, for AI-generated answers): ')
if (key) {
authStorage.set('brave_answers', { type: 'api_key', key })
process.env.BRAVE_ANSWERS_KEY = key
}
}
if (needsContext7) {
const key = await promptMasked('Context7 API key (optional): ')
if (key) {