Skip to content
Open
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
89 changes: 74 additions & 15 deletions quickstart.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -1022,6 +1022,10 @@ $hiveKey = [System.Environment]::GetEnvironmentVariable("HIVE_API_KEY", "User")
if (-not $hiveKey) { $hiveKey = $env:HIVE_API_KEY }
if ($hiveKey) { $HiveCredDetected = $true }

$AntigravityCredDetected = $false
$antigravityAuthPath = Join-Path $env:USERPROFILE ".hive\antigravity-accounts.json"
if (Test-Path $antigravityAuthPath) { $AntigravityCredDetected = $true }

# Detect API key providers
$ProviderMenuEnvVars = @("ANTHROPIC_API_KEY", "OPENAI_API_KEY", "GEMINI_API_KEY", "GROQ_API_KEY", "CEREBRAS_API_KEY", "OPENROUTER_API_KEY")
$ProviderMenuNames = @("Anthropic (Claude) - Recommended", "OpenAI (GPT)", "Google Gemini - Free tier available", "Groq - Fast, free tier", "Cerebras - Fast, free tier", "OpenRouter - Bring any OpenRouter model")
Expand Down Expand Up @@ -1057,6 +1061,7 @@ if (Test-Path $HiveConfigFile) {
if ($prevLlm.use_claude_code_subscription) { $PrevSubMode = "claude_code" }
elseif ($prevLlm.use_codex_subscription) { $PrevSubMode = "codex" }
elseif ($prevLlm.use_kimi_code_subscription) { $PrevSubMode = "kimi_code" }
elseif ($prevLlm.use_antigravity_subscription) { $PrevSubMode = "antigravity" }
elseif ($prevLlm.api_base -and $prevLlm.api_base -like "*api.z.ai*") { $PrevSubMode = "zai_code" }
elseif ($prevLlm.provider -eq "minimax" -or ($prevLlm.api_base -and $prevLlm.api_base -like "*api.minimax.io*")) { $PrevSubMode = "minimax_code" }
elseif ($prevLlm.api_base -and $prevLlm.api_base -like "*api.kimi.com*") { $PrevSubMode = "kimi_code" }
Expand All @@ -1076,6 +1081,7 @@ if ($PrevSubMode -or $PrevProvider) {
"minimax_code" { if ($MinimaxCredDetected) { $prevCredValid = $true } }
"kimi_code" { if ($KimiCredDetected) { $prevCredValid = $true } }
"hive_llm" { if ($HiveCredDetected) { $prevCredValid = $true } }
"antigravity" { if ($AntigravityCredDetected) { $prevCredValid = $true } }
default {
if ($PrevProvider -eq "ollama") {
$prevCredValid = $true
Expand All @@ -1094,18 +1100,20 @@ if ($PrevSubMode -or $PrevProvider) {
"minimax_code" { $DefaultChoice = "4" }
"kimi_code" { $DefaultChoice = "5" }
"hive_llm" { $DefaultChoice = "6" }
"antigravity" { $DefaultChoice = "7" }
}
if (-not $DefaultChoice) {
switch ($PrevProvider) {
"anthropic" { $DefaultChoice = "7" }
"openai" { $DefaultChoice = "8" }
"gemini" { $DefaultChoice = "9" }
"groq" { $DefaultChoice = "10" }
"cerebras" { $DefaultChoice = "11" }
"openrouter" { $DefaultChoice = "12" }
"ollama" { $DefaultChoice = "13" }
"anthropic" { $DefaultChoice = "8" }
"openai" { $DefaultChoice = "9" }
"gemini" { $DefaultChoice = "10" }
"groq" { $DefaultChoice = "11" }
"cerebras" { $DefaultChoice = "12" }
"openrouter" { $DefaultChoice = "13" }
"ollama" { $DefaultChoice = "14" }
"minimax" { $DefaultChoice = "4" }
"kimi" { $DefaultChoice = "5" }
"hive" { $DefaultChoice = "6" }
}
}
}
Expand Down Expand Up @@ -1158,12 +1166,19 @@ Write-Host ") Hive LLM " -NoNewline
Write-Color -Text "(use your Hive API key)" -Color DarkGray -NoNewline
if ($HiveCredDetected) { Write-Color -Text " (credential detected)" -Color Green } else { Write-Host "" }

# 7) Antigravity
Write-Host " " -NoNewline
Write-Color -Text "7" -Color Cyan -NoNewline
Write-Host ") Antigravity Subscription " -NoNewline
Write-Color -Text "(use your Google/Gemini plan)" -Color DarkGray -NoNewline
if ($AntigravityCredDetected) { Write-Color -Text " (credential detected)" -Color Green } else { Write-Host "" }

Write-Host ""
Write-Color -Text " API key providers:" -Color Cyan

# 7-12) API key providers
# 8-13) API key providers
for ($idx = 0; $idx -lt $ProviderMenuEnvVars.Count; $idx++) {
$num = $idx + 7
$num = $idx + 8
$envVal = [System.Environment]::GetEnvironmentVariable($ProviderMenuEnvVars[$idx], "Process")
if (-not $envVal) { $envVal = [System.Environment]::GetEnvironmentVariable($ProviderMenuEnvVars[$idx], "User") }
Write-Host " " -NoNewline
Expand All @@ -1172,17 +1187,17 @@ for ($idx = 0; $idx -lt $ProviderMenuEnvVars.Count; $idx++) {
if ($envVal) { Write-Color -Text " (credential detected)" -Color Green } else { Write-Host "" }
}

# 13) Local (Ollama) no API key needed
# 14) Local (Ollama) - no API key needed
Write-Host " " -NoNewline
Write-Color -Text "13" -Color Cyan -NoNewline
Write-Color -Text "14" -Color Cyan -NoNewline
if ($OllamaDetected) {
Write-Host ") Local (Ollama) - No API key needed " -NoNewline
Write-Color -Text "(ollama detected)" -Color Green
} else {
Write-Host ") Local (Ollama) - No API key needed"
}

$SkipChoice = 7 + $ProviderMenuEnvVars.Count + 1
$SkipChoice = 8 + $ProviderMenuEnvVars.Count + 1
Write-Host " " -NoNewline
Write-Color -Text "$SkipChoice" -Color Cyan -NoNewline
Write-Host ") Skip for now"
Expand Down Expand Up @@ -1320,9 +1335,48 @@ switch ($num) {
}
Write-Color -Text " Model: $SelectedModel | API: $HiveLlmEndpoint" -Color DarkGray
}
{ $_ -ge 7 -and $_ -le 12 } {
7 {
# Antigravity Subscription
if (-not $AntigravityCredDetected) {
Write-Host ""
Write-Color -Text " Setting up Antigravity authentication..." -Color Cyan
Write-Host ""
Write-Warn "A browser window will open for Google OAuth."
Write-Host " Sign in with your Google account that has Antigravity access."
Write-Host ""
try {
$null = & $UvCmd run python (Join-Path $ScriptDir "core\antigravity_auth.py") auth account add 2>&1
if ($LASTEXITCODE -eq 0 -and (Test-Path $antigravityAuthPath)) {
$AntigravityCredDetected = $true
}
} catch {
$AntigravityCredDetected = $false
}

if (-not $AntigravityCredDetected) {
Write-Host ""
Write-Fail "Authentication failed or was cancelled."
Write-Host ""
$SelectedProviderId = ""
}
}

if ($AntigravityCredDetected) {
$SubscriptionMode = "antigravity"
$SelectedProviderId = "openai"
$SelectedModel = "gemini-3-flash"
$SelectedMaxTokens = 32768
$SelectedMaxContextTokens = 1000000
Write-Host ""
Write-Warn "Using Antigravity can technically cause your account suspension. Please use at your own risk."
Write-Host ""
Write-Ok "Using Antigravity subscription"
Write-Color -Text " Model: gemini-3-flash | Direct OAuth (no proxy required)" -Color DarkGray
}
}
{ $_ -ge 8 -and $_ -le 13 } {
# API key providers
$provIdx = $num - 7
$provIdx = $num - 8
$SelectedEnvVar = $ProviderMenuEnvVars[$provIdx]
$SelectedProviderId = $ProviderMenuIds[$provIdx]
$providerName = $ProviderMenuNames[$provIdx] -replace ' - .*', '' # strip description
Expand Down Expand Up @@ -1402,7 +1456,7 @@ switch ($num) {
}
}
}
13 {
14 {
# Local (Ollama)
if (-not $OllamaDetected) {
Write-Host ""
Expand Down Expand Up @@ -1774,6 +1828,8 @@ if ($SelectedProviderId) {
$config.llm["use_claude_code_subscription"] = $true
} elseif ($SubscriptionMode -eq "codex") {
$config.llm["use_codex_subscription"] = $true
} elseif ($SubscriptionMode -eq "antigravity") {
$config.llm["use_antigravity_subscription"] = $true
} elseif ($SubscriptionMode -eq "zai_code") {
$config.llm["api_base"] = "https://api.z.ai/api/coding/paas/v4"
$config.llm["api_key_env_var"] = $SelectedEnvVar
Expand Down Expand Up @@ -2096,6 +2152,9 @@ if ($SelectedProviderId) {
Write-Color -Text " API: api.minimax.io/v1 (OpenAI-compatible)" -Color DarkGray
} elseif ($SubscriptionMode -eq "codex") {
Write-Ok "OpenAI Codex Subscription -> $SelectedModel"
} elseif ($SubscriptionMode -eq "antigravity") {
Write-Ok "Antigravity Subscription -> $SelectedModel"
Write-Color -Text " Direct OAuth (no proxy required)" -Color DarkGray
} elseif ($SelectedProviderId -eq "openrouter") {
Write-Ok "OpenRouter API Key -> $SelectedModel"
Write-Color -Text " API: openrouter.ai/api/v1 (OpenAI-compatible)" -Color DarkGray
Expand Down
13 changes: 12 additions & 1 deletion quickstart.sh
Original file line number Diff line number Diff line change
Expand Up @@ -673,7 +673,18 @@ detect_shell_rc() {
fi
;;
bash)
if [ -f "$HOME/.bashrc" ]; then
# Git Bash on Windows commonly starts as a login shell, so prefer
# .bash_profile there when it already exists. On Unix-like shells,
# keep the traditional .bashrc-first behavior.
if [ -n "$MSYSTEM" ] || [ -n "$MINGW_PREFIX" ]; then
if [ -f "$HOME/.bash_profile" ]; then
echo "$HOME/.bash_profile"
elif [ -f "$HOME/.bashrc" ]; then
echo "$HOME/.bashrc"
else
echo "$HOME/.profile"
fi
elif [ -f "$HOME/.bashrc" ]; then
echo "$HOME/.bashrc"
elif [ -f "$HOME/.bash_profile" ]; then
echo "$HOME/.bash_profile"
Expand Down
68 changes: 43 additions & 25 deletions tools/src/aden_tools/credentials/shell_config.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
"""
Shell configuration utilities for persisting environment variables.

Supports both bash and zsh, detecting the user's default shell.
Supports bash and zsh with platform-aware fallbacks for login-shell config
files such as ``.bash_profile``, ``.zshenv``, and ``.profile``.
Used primarily for persisting ADEN_API_KEY across sessions.
"""

from __future__ import annotations

import os
import platform
import re
from pathlib import Path
from typing import Literal
Expand All @@ -34,9 +36,9 @@ def detect_shell() -> ShellType:
else:
# Try to detect from config file existence
home = Path.home()
if (home / ".zshrc").exists():
if (home / ".zshrc").exists() or (home / ".zshenv").exists():
return "zsh"
elif (home / ".bashrc").exists():
elif (home / ".bashrc").exists() or (home / ".bash_profile").exists():
return "bash"
return "unknown"

Expand All @@ -55,14 +57,12 @@ def get_shell_config_path(shell_type: ShellType | None = None) -> Path:
shell_type = detect_shell()

home = Path.home()
candidates = _get_shell_config_candidates(home, shell_type)

if shell_type == "zsh":
return home / ".zshrc"
elif shell_type == "bash":
return home / ".bashrc"
else:
# Default to .bashrc for unknown shells
return home / ".bashrc"
for candidate in candidates:
if candidate.exists():
return candidate
return candidates[0]


def check_env_var_in_shell_config(
Expand All @@ -79,29 +79,47 @@ def check_env_var_in_shell_config(
Returns:
Tuple of (exists, current_value or None)
"""
config_path = get_shell_config_path(shell_type)
if shell_type is None:
shell_type = detect_shell()

if not config_path.exists():
return False, None
for config_path in _get_shell_config_candidates(Path.home(), shell_type):
if not config_path.exists():
continue

content = config_path.read_text(encoding="utf-8")
content = config_path.read_text(encoding="utf-8")

# Look for export ENV_VAR=value or export ENV_VAR="value"
pattern = rf"^export\s+{re.escape(env_var)}=(.+)$"
match = re.search(pattern, content, re.MULTILINE)
# Look for export ENV_VAR=value or export ENV_VAR="value"
pattern = rf"^export\s+{re.escape(env_var)}=(.+)$"
match = re.search(pattern, content, re.MULTILINE)

if match:
value = match.group(1).strip()
# Remove surrounding quotes if present
if (value.startswith('"') and value.endswith('"')) or (
value.startswith("'") and value.endswith("'")
):
value = value[1:-1]
return True, value
if match:
value = match.group(1).strip()
# Remove surrounding quotes if present
if (value.startswith('"') and value.endswith('"')) or (
value.startswith("'") and value.endswith("'")
):
value = value[1:-1]
return True, value

return False, None


def _get_shell_config_candidates(home: Path, shell_type: ShellType) -> list[Path]:
"""Return candidate config files in lookup order for the detected shell."""
if shell_type == "zsh":
return [home / ".zshrc", home / ".zshenv"]

if shell_type == "bash":
# Git Bash commonly launches login shells on Windows, so prefer
# ``.bash_profile`` there for writes, but keep ``.bashrc`` in the
# lookup list so older setups continue to work.
if platform.system() == "Windows":
return [home / ".bash_profile", home / ".bashrc", home / ".profile"]
return [home / ".bashrc", home / ".bash_profile", home / ".profile"]

return [home / ".profile", home / ".bashrc"]


def add_env_var_to_shell_config(
env_var: str,
value: str,
Expand Down
90 changes: 90 additions & 0 deletions tools/tests/test_shell_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
"""Tests for shell config path selection and env-var lookups."""

from pathlib import Path

from aden_tools.credentials import shell_config


def _mock_home(monkeypatch, tmp_path: Path) -> None:
monkeypatch.setattr(shell_config.Path, "home", staticmethod(lambda: tmp_path))


def test_get_shell_config_path_prefers_existing_bash_profile(monkeypatch, tmp_path):
_mock_home(monkeypatch, tmp_path)
monkeypatch.setenv("SHELL", "/usr/bin/bash")
monkeypatch.setattr(shell_config.platform, "system", lambda: "Windows")

(tmp_path / ".bashrc").write_text("# bashrc\n", encoding="utf-8")
(tmp_path / ".bash_profile").write_text("# bash profile\n", encoding="utf-8")

assert shell_config.get_shell_config_path() == tmp_path / ".bash_profile"


def test_get_shell_config_path_prefers_bashrc_for_non_windows_bash(monkeypatch, tmp_path):
_mock_home(monkeypatch, tmp_path)
monkeypatch.setenv("SHELL", "/usr/bin/bash")
monkeypatch.setattr(shell_config.platform, "system", lambda: "Linux")

(tmp_path / ".bashrc").write_text("# bashrc\n", encoding="utf-8")
(tmp_path / ".bash_profile").write_text("# bash profile\n", encoding="utf-8")

assert shell_config.get_shell_config_path() == tmp_path / ".bashrc"


def test_check_env_var_in_shell_config_reads_bash_profile(monkeypatch, tmp_path):
_mock_home(monkeypatch, tmp_path)
monkeypatch.setenv("SHELL", "/usr/bin/bash")
monkeypatch.setattr(shell_config.platform, "system", lambda: "Windows")

(tmp_path / ".bash_profile").write_text(
'export HIVE_API_KEY="hive-key-123"\n',
encoding="utf-8",
)

assert shell_config.check_env_var_in_shell_config("HIVE_API_KEY") == (
True,
"hive-key-123",
)


def test_check_env_var_in_shell_config_falls_back_to_bashrc_on_windows(monkeypatch, tmp_path):
_mock_home(monkeypatch, tmp_path)
monkeypatch.setenv("SHELL", "/usr/bin/bash")
monkeypatch.setattr(shell_config.platform, "system", lambda: "Windows")

(tmp_path / ".bash_profile").write_text("# no key here\n", encoding="utf-8")
(tmp_path / ".bashrc").write_text(
'export HIVE_API_KEY="hive-key-from-bashrc"\n',
encoding="utf-8",
)

assert shell_config.check_env_var_in_shell_config("HIVE_API_KEY") == (
True,
"hive-key-from-bashrc",
)


def test_check_env_var_in_shell_config_reads_zshenv_when_zshrc_missing(monkeypatch, tmp_path):
_mock_home(monkeypatch, tmp_path)
monkeypatch.setenv("SHELL", "/bin/zsh")
monkeypatch.setattr(shell_config.platform, "system", lambda: "Darwin")

(tmp_path / ".zshenv").write_text(
"export OPENROUTER_API_KEY='or-key-123'\n",
encoding="utf-8",
)

assert shell_config.check_env_var_in_shell_config("OPENROUTER_API_KEY") == (
True,
"or-key-123",
)


def test_get_shell_config_path_falls_back_to_profile_for_unknown_shell(monkeypatch, tmp_path):
_mock_home(monkeypatch, tmp_path)
monkeypatch.setenv("SHELL", "/usr/bin/fish")
monkeypatch.setattr(shell_config.platform, "system", lambda: "Linux")

(tmp_path / ".profile").write_text("# profile\n", encoding="utf-8")

assert shell_config.get_shell_config_path() == tmp_path / ".profile"
Loading