Skip to content

Commit 0877f36

Browse files
authored
Merge pull request #1 from nangsontay/feat/setting-panel
Feat/setting panel
2 parents 52a024d + 0d31939 commit 0877f36

25 files changed

Lines changed: 2620 additions & 12 deletions

headroom/cli/main.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,19 @@ def main(ctx: click.Context) -> None:
3232
"""
3333
ctx.ensure_object(dict)
3434

35+
# Apply file-backed settings (settings.json) to the process environment
36+
# BEFORE Click parses any subcommand's ``envvar=`` options (Click resolves
37+
# those when it builds the subcommand context, which happens after this
38+
# group callback runs). ``os.environ.setdefault`` keeps explicit shell
39+
# exports authoritative over the stored file. Fail-open so a corrupt
40+
# settings.json can never block the CLI.
41+
try:
42+
from headroom import settings_store
43+
44+
settings_store.apply_to_environ(settings_store.load())
45+
except Exception: # noqa: BLE001 — settings load must never break the CLI
46+
pass
47+
3548
# Fire a rate-limited, opt-out background check for newer releases so other
3649
# surfaces (e.g. the proxy banner) can show an "update available" notice.
3750
# Never blocks, never raises; skipped for `update` (it checks explicitly).

headroom/cli/proxy.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@
99
import click
1010

1111
from headroom import paths as _paths
12-
from headroom.providers.registry import resolve_api_overrides, resolve_api_targets
12+
from headroom.providers.registry import (
13+
resolve_api_overrides,
14+
resolve_api_targets,
15+
resolve_extra_headers,
16+
)
1317
from headroom.proxy.modes import PROXY_MODE_CACHE, normalize_proxy_mode
1418

1519
from .main import main
@@ -872,6 +876,22 @@ def dashboard(port: int, no_open: bool) -> None:
872876
"Default: /tmp/headroom-embed-{port}.sock. "
873877
"(env: HEADROOM_EMBEDDING_SERVER_SOCKET)",
874878
)
879+
@click.option(
880+
"--anthropic-extra-headers",
881+
default=None,
882+
help=(
883+
"JSON object of extra headers merged into (and overriding) headers forwarded to "
884+
'the Anthropic endpoint, e.g. \'{"Api-Key": "..."}\' (env: ANTHROPIC_TARGET_API_HEADERS)'
885+
),
886+
)
887+
@click.option(
888+
"--openai-extra-headers",
889+
default=None,
890+
help=(
891+
"JSON object of extra headers merged into (and overriding) headers forwarded to "
892+
"the OpenAI endpoint (env: OPENAI_TARGET_API_HEADERS)"
893+
),
894+
)
875895
@click.pass_context
876896
def proxy(
877897
ctx: click.Context,
@@ -943,6 +963,8 @@ def proxy(
943963
backend: str,
944964
anyllm_provider: str,
945965
anthropic_api_url: str | None,
966+
anthropic_extra_headers: str | None,
967+
openai_extra_headers: str | None,
946968
openai_api_url: str | None,
947969
gemini_api_url: str | None,
948970
cloudcode_api_url: str | None,
@@ -1047,6 +1069,17 @@ def proxy(
10471069
sys.exit(1)
10481070
os.environ["HEADROOM_INTERCEPT_ENABLED"] = "1"
10491071

1072+
try:
1073+
resolved_anthropic_extra_headers = resolve_extra_headers(
1074+
anthropic_extra_headers, "ANTHROPIC_TARGET_API_HEADERS"
1075+
)
1076+
resolved_openai_extra_headers = resolve_extra_headers(
1077+
openai_extra_headers, "OPENAI_TARGET_API_HEADERS"
1078+
)
1079+
except ValueError as exc:
1080+
click.secho(f"error: {exc}", fg="red", err=True)
1081+
sys.exit(1)
1082+
10501083
provider_api_overrides = resolve_api_overrides(
10511084
anthropic_api_url=anthropic_api_url,
10521085
openai_api_url=openai_api_url,
@@ -1110,6 +1143,8 @@ def proxy(
11101143
host=host,
11111144
port=port,
11121145
anthropic_api_url=provider_api_overrides.anthropic,
1146+
anthropic_extra_headers=resolved_anthropic_extra_headers,
1147+
openai_extra_headers=resolved_openai_extra_headers,
11131148
openai_api_url=provider_api_overrides.openai,
11141149
gemini_api_url=provider_api_overrides.gemini,
11151150
cloudcode_api_url=provider_api_overrides.cloudcode,

headroom/dashboard/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,9 @@ def get_dashboard_html() -> str:
1010
"""Load the dashboard HTML template."""
1111
template_path = TEMPLATES_DIR / "dashboard.html"
1212
return template_path.read_text(encoding="utf-8")
13+
14+
15+
def get_settings_html() -> str:
16+
"""Load the settings GUI HTML template."""
17+
template_path = TEMPLATES_DIR / "settings.html"
18+
return template_path.read_text(encoding="utf-8")

headroom/dashboard/templates/dashboard.html

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,11 @@ <h1 class="text-xl font-semibold tracking-tight">HEADROOM</h1>
159159
<div class="text-xs text-gray-500">
160160
Updated <span x-text="lastUpdate"></span>
161161
</div>
162+
<a href="/dashboard/settings" id="settings-link"
163+
class="px-3 py-1.5 text-sm rounded-md border border-border bg-surface text-gray-400 hover:text-gray-200 transition-colors"
164+
title="Settings">
165+
&#9881; Settings
166+
</a>
162167
<button onclick="toggleTheme()" id="theme-toggle"
163168
class="px-3 py-1.5 text-sm rounded-md border border-border bg-surface text-gray-400 hover:text-gray-200 transition-colors"
164169
title="Toggle light/dark mode">

0 commit comments

Comments
 (0)