Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions bin/baudbot
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ usage() {
echo " install Bootstrap install from GitHub (download script, then escalate)"
echo " setup One-time system setup (user, deps, firewall, systemd; --experimental enables risky integrations)"
echo " config Interactive secrets and config setup"
echo " login Authenticate with an LLM subscription (OAuth)"
echo " env Manage env vars and backend source (set/get/sync/backend)"
echo " deploy Deploy source + config to agent runtime"
echo " broker Slack broker commands (register workspace linkage)"
Expand Down Expand Up @@ -443,6 +444,33 @@ case "$COMMAND_NAME" in
dispatch_registered_command "start" "$@"
;;

login)
require_root "login"
BAUDBOT_AGENT_USER="${BAUDBOT_AGENT_USER:-baudbot_agent}"
BAUDBOT_HOME="$(resolve_user_home "$BAUDBOT_AGENT_USER" 2>/dev/null || echo "/home/$BAUDBOT_AGENT_USER")"
AUTH_JSON="$BAUDBOT_HOME/.pi/agent/auth.json"

NODE_BIN="$(resolve_node_bin || true)"
if [ -z "$NODE_BIN" ]; then
echo "❌ Could not find node runtime for OAuth login."
exit 1
fi

OAUTH_SCRIPT="$BAUDBOT_ROOT/bin/oauth-login.mjs"
if [ ! -f "$OAUTH_SCRIPT" ]; then
echo "❌ oauth-login.mjs not found at $OAUTH_SCRIPT"
exit 1
fi

"$NODE_BIN" "$OAUTH_SCRIPT" --auth-path "$AUTH_JSON" "$@"

# Fix ownership
if [ -f "$AUTH_JSON" ] && id "$BAUDBOT_AGENT_USER" &>/dev/null; then
chown "$BAUDBOT_AGENT_USER:$BAUDBOT_AGENT_USER" "$AUTH_JSON"
chown "$BAUDBOT_AGENT_USER:$BAUDBOT_AGENT_USER" "$(dirname "$AUTH_JSON")" 2>/dev/null || true
fi
;;

setup)
if [ "${1:-}" = "--slack-broker" ]; then
shift
Expand Down
197 changes: 144 additions & 53 deletions bin/config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -361,62 +361,148 @@ fi
echo -e "${BOLD}Required${RESET} ${DIM}(agent won't start without these)${RESET}"
echo ""

# LLM provider picker
echo -e "${BOLD}LLM provider${RESET}"
LLM_CHOICE="$(ui_choose "Choose your primary LLM provider:" \
"Anthropic" \
"OpenAI" \
"Gemini" \
"OpenCode Zen")"

case "$LLM_CHOICE" in
"Anthropic")
prompt_secret "ANTHROPIC_API_KEY" \
"Anthropic API key" \
"https://console.anthropic.com/settings/keys" \
"required" \
"sk-ant-"
;;
"OpenAI")
prompt_secret "OPENAI_API_KEY" \
"OpenAI API key" \
"https://platform.openai.com/api-keys" \
"required" \
"sk-"
;;
"Gemini")
prompt_secret "GEMINI_API_KEY" \
"Google Gemini API key" \
"https://aistudio.google.com/apikey" \
"required"
;;
"OpenCode Zen")
prompt_secret "OPENCODE_ZEN_API_KEY" \
"OpenCode Zen API key (multi-provider router)" \
"https://opencode.ai" \
"required"
;;
esac
# LLM authentication tier
echo -e "${BOLD}LLM authentication${RESET}"
LLM_AUTH_TIER="$(ui_choose "How would you like to authenticate with your LLM?" \
"API key" \
"Subscription login (OAuth)")"

USED_SUBSCRIPTION_LOGIN=false

if [ "$LLM_AUTH_TIER" = "API key" ]; then
# ── API key path ──
echo ""
LLM_CHOICE="$(ui_choose "Choose your primary LLM provider:" \
"Anthropic" \
"OpenAI" \
"Gemini" \
"OpenCode Zen")"

case "$LLM_CHOICE" in
"Anthropic")
prompt_secret "ANTHROPIC_API_KEY" \
"Anthropic API key" \
"https://console.anthropic.com/settings/keys" \
"required" \
"sk-ant-"
;;
"OpenAI")
prompt_secret "OPENAI_API_KEY" \
"OpenAI API key" \
"https://platform.openai.com/api-keys" \
"required" \
"sk-"
;;
"Gemini")
prompt_secret "GEMINI_API_KEY" \
"Google Gemini API key" \
"https://aistudio.google.com/apikey" \
"required"
;;
"OpenCode Zen")
prompt_secret "OPENCODE_ZEN_API_KEY" \
"OpenCode Zen API key (multi-provider router)" \
"https://opencode.ai" \
"required"
;;
esac

SELECTED_LLM_KEY=""
case "$LLM_CHOICE" in
"Anthropic") SELECTED_LLM_KEY="ANTHROPIC_API_KEY" ;;
"OpenAI") SELECTED_LLM_KEY="OPENAI_API_KEY" ;;
"Gemini") SELECTED_LLM_KEY="GEMINI_API_KEY" ;;
"OpenCode Zen") SELECTED_LLM_KEY="OPENCODE_ZEN_API_KEY" ;;
esac
SELECTED_LLM_KEY=""
case "$LLM_CHOICE" in
"Anthropic") SELECTED_LLM_KEY="ANTHROPIC_API_KEY" ;;
"OpenAI") SELECTED_LLM_KEY="OPENAI_API_KEY" ;;
"Gemini") SELECTED_LLM_KEY="GEMINI_API_KEY" ;;
"OpenCode Zen") SELECTED_LLM_KEY="OPENCODE_ZEN_API_KEY" ;;
esac

if [ -z "${ENV_VARS[$SELECTED_LLM_KEY]:-}" ]; then
echo "❌ $SELECTED_LLM_KEY is required for selected provider '$LLM_CHOICE'"
exit 1
fi
if [ -z "${ENV_VARS[$SELECTED_LLM_KEY]:-}" ]; then
echo "❌ $SELECTED_LLM_KEY is required for selected provider '$LLM_CHOICE'"
exit 1
fi

# Keep only selected provider key for deterministic config.
for key in ANTHROPIC_API_KEY OPENAI_API_KEY GEMINI_API_KEY OPENCODE_ZEN_API_KEY; do
if [ "$key" != "$SELECTED_LLM_KEY" ]; then
unset "ENV_VARS[$key]"
# Keep only selected provider key for deterministic config.
for key in ANTHROPIC_API_KEY OPENAI_API_KEY GEMINI_API_KEY OPENCODE_ZEN_API_KEY; do
if [ "$key" != "$SELECTED_LLM_KEY" ]; then
unset "ENV_VARS[$key]"
fi
done

else
# ── Subscription login (OAuth) path ──
clear_keys ANTHROPIC_API_KEY OPENAI_API_KEY GEMINI_API_KEY OPENCODE_ZEN_API_KEY
LLM_CHOICE="Subscription"
SELECTED_LLM_KEY=""

# Resolve the agent home for auth.json
BAUDBOT_HOME="${BAUDBOT_HOME:-/home/baudbot_agent}"
AUTH_JSON="$BAUDBOT_HOME/.pi/agent/auth.json"

# Find Node.js for oauth-login.mjs
OAUTH_SCRIPT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/oauth-login.mjs"
OAUTH_NODE_BIN=""
if [ -f "$SCRIPT_DIR/../bin/lib/runtime-node.sh" ]; then
# shellcheck source=bin/lib/runtime-node.sh
source "$SCRIPT_DIR/../bin/lib/runtime-node.sh" 2>/dev/null || true
OAUTH_NODE_BIN="$(bb_resolve_runtime_node_bin "$BAUDBOT_HOME" 2>/dev/null || true)"
fi
if [ -z "$OAUTH_NODE_BIN" ] || [ ! -x "$OAUTH_NODE_BIN" ]; then
OAUTH_NODE_BIN="$(command -v node 2>/dev/null || true)"
fi

if [ ! -f "$OAUTH_SCRIPT" ]; then
echo "❌ oauth-login.mjs not found at $OAUTH_SCRIPT"
exit 1
fi
if [ -z "$OAUTH_NODE_BIN" ]; then
echo "❌ Node.js not found — required for OAuth login"
exit 1
fi
done

# Check for existing OAuth credentials
HAS_EXISTING_OAUTH=false
EXISTING_OAUTH_PROVIDER=""
if [ -f "$AUTH_JSON" ] && command -v jq &>/dev/null; then
for op in "openai-codex" "anthropic"; do
if jq -e --arg p "$op" '.[$p]' "$AUTH_JSON" &>/dev/null; then
HAS_EXISTING_OAUTH=true
EXISTING_OAUTH_PROVIDER="$op"
break
fi
done
fi

if [ "$HAS_EXISTING_OAUTH" = true ]; then
info "Existing OAuth credentials found ($EXISTING_OAUTH_PROVIDER)."
if ! ui_confirm "Re-authenticate with a different provider?" false; then
info "Keeping existing OAuth credentials."
USED_SUBSCRIPTION_LOGIN=true
fi
fi

if [ "$USED_SUBSCRIPTION_LOGIN" = false ]; then
echo ""
dim " This will open an OAuth flow. You'll get a URL to open in your browser."
echo ""

# Run oauth-login.mjs interactively
OAUTH_PROVIDER_ID=""
if OAUTH_PROVIDER_ID=$("$OAUTH_NODE_BIN" "$OAUTH_SCRIPT" --auth-path "$AUTH_JSON"); then
OAUTH_PROVIDER_ID="$(echo "$OAUTH_PROVIDER_ID" | tr -d '[:space:]')"
info "✓ OAuth login complete ($OAUTH_PROVIDER_ID)"

# Fix ownership if running as root
if [ "$(id -u)" -eq 0 ] && id baudbot_agent &>/dev/null; then
chown baudbot_agent:baudbot_agent "$AUTH_JSON"
# Also fix parent dirs
chown baudbot_agent:baudbot_agent "$(dirname "$AUTH_JSON")" 2>/dev/null || true
fi
else
echo "❌ OAuth login failed"
exit 1
fi
USED_SUBSCRIPTION_LOGIN=true
fi
fi

echo ""

Expand Down Expand Up @@ -655,7 +741,12 @@ VAR_COUNT=$(grep -c '=' "$CONFIG_FILE")
info "Wrote $VAR_COUNT variables to $CONFIG_FILE"
echo ""
echo -e "${BOLD}Summary${RESET}"
echo -e " LLM provider: ${BOLD}${LLM_CHOICE}${RESET}"
echo -e " LLM auth: ${BOLD}${LLM_AUTH_TIER}${RESET}"
if [ "$LLM_AUTH_TIER" = "API key" ]; then
echo -e " LLM provider: ${BOLD}${LLM_CHOICE}${RESET}"
else
echo -e " LLM provider: ${BOLD}Subscription (OAuth via auth.json)${RESET}"
fi
echo -e " Slack mode: ${BOLD}${SLACK_CHOICE}${RESET}"
if [ "$SLACK_CHOICE" = "Use baudbot.ai Slack integration (easy)" ]; then
echo -e " ${DIM}Next: run 'sudo baudbot broker register' after install${RESET}"
Expand Down
40 changes: 34 additions & 6 deletions bin/config.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -83,50 +83,78 @@ echo "================="
echo ""

# Test 1: Advanced Slack path writes socket-mode keys only
# Input: 1=API key tier, 1=Anthropic, key, 2=advanced Slack, tokens, ...
HOME1="$TMPDIR/advanced"
run_config "$HOME1" '1\nsk-ant-test\n2\nxoxb-test\nxapp-test\n\nn\nn\n'
run_config "$HOME1" '1\n1\nsk-ant-test\n2\nxoxb-test\nxapp-test\n\nn\nn\n'
ENV1="$HOME1/.baudbot/.env"
expect_file_contains "advanced path writes Anthropic key" "$ENV1" "ANTHROPIC_API_KEY=sk-ant-test"
expect_file_contains "advanced path writes SLACK_BOT_TOKEN" "$ENV1" "SLACK_BOT_TOKEN=xoxb-test"
expect_file_contains "advanced path writes SLACK_APP_TOKEN" "$ENV1" "SLACK_APP_TOKEN=xapp-test"
expect_file_not_contains "advanced path does not write OPENAI key" "$ENV1" "OPENAI_API_KEY="

# Test 2: Easy Slack path avoids socket-mode keys
Comment on lines 85 to 95
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no test coverage for OAuth subscription login path (choice 2)

All tests select "1" (API key tier) then provider. The new OAuth subscription path needs at least one test to verify the flow works and auth.json is properly created/detected.

Prompt To Fix With AI
This is a comment left during a code review.
Path: bin/config.test.sh
Line: 85-95

Comment:
no test coverage for OAuth subscription login path (choice 2)

All tests select "1" (API key tier) then provider. The new OAuth subscription path needs at least one test to verify the flow works and `auth.json` is properly created/detected.

How can I resolve this? If you propose a fix, please make it concise.

# Input: 1=API key tier, 2=OpenAI, key, 1=easy Slack, ...
HOME2="$TMPDIR/easy"
run_config "$HOME2" '2\nsk-openai-test\n1\n\nn\nn\n'
run_config "$HOME2" '1\n2\nsk-openai-test\n1\n\nn\nn\n'
ENV2="$HOME2/.baudbot/.env"
expect_file_contains "easy path writes OpenAI key" "$ENV2" "OPENAI_API_KEY=sk-openai-test"
expect_file_not_contains "easy path omits SLACK_BOT_TOKEN" "$ENV2" "SLACK_BOT_TOKEN="
expect_file_not_contains "easy path omits SLACK_APP_TOKEN" "$ENV2" "SLACK_APP_TOKEN="

# Test 3: Optional integration toggle prompts conditionally
# Input: 1=API key tier, 3=Gemini, key, 2=advanced Slack, tokens, ..., y=kernel, key, n=sentry
HOME3="$TMPDIR/kernel"
run_config "$HOME3" '3\ngem-key\n2\nxoxb-test\nxapp-test\n\ny\nkernel-key\nn\n'
run_config "$HOME3" '1\n3\ngem-key\n2\nxoxb-test\nxapp-test\n\ny\nkernel-key\nn\n'
ENV3="$HOME3/.baudbot/.env"
expect_file_contains "kernel enabled writes key" "$ENV3" "KERNEL_API_KEY=kernel-key"
expect_file_not_contains "sentry skipped omits token" "$ENV3" "SENTRY_AUTH_TOKEN="
expect_file_not_contains "email skipped omits AgentMail" "$ENV3" "AGENTMAIL_API_KEY="

# Test 4: Selected LLM key is required
# Input: 1=API key tier, 1=Anthropic, empty key
HOME4="$TMPDIR/missing-llm"
expect_exit_nonzero "fails when selected provider key is missing" "$HOME4" '1\n\n'
expect_exit_nonzero "fails when selected provider key is missing" "$HOME4" '1\n1\n\n'

# Test 5: Re-run preserves existing selected LLM key when input is blank
# Input: 1=API key tier, 1=Anthropic, blank (keep existing), 1=easy Slack, ...
HOME5="$TMPDIR/rerun-keep-llm"
write_existing_env "$HOME5" 'ANTHROPIC_API_KEY=sk-ant-existing\n'
run_config "$HOME5" '1\n\n1\n\nn\nn\n'
run_config "$HOME5" '1\n1\n\n1\n\nn\nn\n'
ENV5="$HOME5/.baudbot/.env"
expect_file_contains "rerun keeps existing Anthropic key" "$ENV5" "ANTHROPIC_API_KEY=sk-ant-existing"

# Test 6: Advanced Slack mode clears stale broker registration keys
# Input: 1=API key tier, 2=OpenAI, key, 2=advanced Slack, tokens, ...
HOME6="$TMPDIR/clear-broker"
write_existing_env "$HOME6" 'OPENAI_API_KEY=sk-old\nSLACK_BROKER_URL=https://broker.example.com\nSLACK_BROKER_WORKSPACE_ID=T0123\nSLACK_BROKER_PUBLIC_KEY=abc\n'
run_config "$HOME6" '2\nsk-openai-new\n2\nxoxb-new\nxapp-new\n\nn\nn\n'
run_config "$HOME6" '1\n2\nsk-openai-new\n2\nxoxb-new\nxapp-new\n\nn\nn\n'
ENV6="$HOME6/.baudbot/.env"
expect_file_not_contains "advanced clears broker URL" "$ENV6" "SLACK_BROKER_URL="
expect_file_not_contains "advanced clears broker workspace" "$ENV6" "SLACK_BROKER_WORKSPACE_ID="
expect_file_contains "advanced retains socket bot token" "$ENV6" "SLACK_BOT_TOKEN=xoxb-new"

# Test 7: Subscription login tier with existing auth.json skips OAuth flow
# Input: 2=Subscription tier, n=don't re-auth, 1=easy Slack, n=kernel, n=sentry
HOME7="$TMPDIR/subscription"
mkdir -p "$HOME7/.pi/agent"
echo '{"anthropic":{"type":"oauth","access":"tok","refresh":"ref","expires":9999999999999}}' \
> "$HOME7/.pi/agent/auth.json"
config_user="$(id -un)"
printf "%b" '2\nn\n1\n\nn\nn\n' \
| HOME="$HOME7" BAUDBOT_HOME="$HOME7" BAUDBOT_CONFIG_USER="$config_user" BAUDBOT_TRY_INSTALL_GUM=0 \
bash "$CONFIG_SCRIPT" >"$OUT_FILE" 2>"$ERR_FILE"
ENV7="$HOME7/.baudbot/.env"
expect_file_not_contains "subscription path omits ANTHROPIC_API_KEY" "$ENV7" "ANTHROPIC_API_KEY="
expect_file_not_contains "subscription path omits OPENAI_API_KEY" "$ENV7" "OPENAI_API_KEY="
# Verify subscription was detected in output
if grep -q "Subscription" "$OUT_FILE"; then
echo " PASS: subscription tier shown in summary"
PASS=$((PASS + 1))
else
echo " FAIL: subscription tier shown in summary"
FAIL=$((FAIL + 1))
fi

echo ""
echo "Results: $PASS passed, $FAIL failed"

Expand Down
18 changes: 17 additions & 1 deletion bin/doctor.sh
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,23 @@ if [ -f "$ENV_FILE" ]; then
if [ "$VALID_LLM_COUNT" -gt 0 ]; then
pass "at least one valid LLM API key is set"
else
fail "no valid LLM API key set (need ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY, or OPENCODE_ZEN_API_KEY)"
# Check auth.json for OAuth subscription credentials
AUTH_JSON="$BAUDBOT_HOME/.pi/agent/auth.json"
HAS_OAUTH=false
OAUTH_PROVIDERS=""
if [ -f "$AUTH_JSON" ] && command -v jq &>/dev/null; then
for oauth_provider in "openai-codex" "anthropic" "google" "github-copilot"; do
if jq -e --arg p "$oauth_provider" '.[$p]' "$AUTH_JSON" &>/dev/null; then
HAS_OAUTH=true
OAUTH_PROVIDERS="${OAUTH_PROVIDERS:+$OAUTH_PROVIDERS, }$oauth_provider"
fi
done
fi
if [ "$HAS_OAUTH" = true ]; then
pass "OAuth subscription credentials found in auth.json ($OAUTH_PROVIDERS)"
else
fail "no valid LLM API key set (need ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY, or OPENCODE_ZEN_API_KEY; or use: sudo baudbot login)"
fi
fi

read_first_env_value() {
Expand Down
Loading