diff --git a/chat_ui.html b/chat_ui.html
index fe1ca70a1..62c16e1e9 100644
--- a/chat_ui.html
+++ b/chat_ui.html
@@ -5840,14 +5840,18 @@
}
})();
- // ── Auth guard — redirect to login if no token ──
- const _token = localStorage.getItem('gf_token');
- if (!_token) {
- window.location.replace('/login');
- }
-
- // ── Load user profile from localStorage (set at login) ──
- (function loadUser() {
+ // ── Auth: auto-fetch token when missing (no login required) ──
+ (async function initAuth() {
+ if (!localStorage.getItem('gf_token')) {
+ try {
+ const r = await fetch('/auth/auto');
+ const d = await r.json();
+ if (d.success && d.token) {
+ localStorage.setItem('gf_token', d.token);
+ if (d.name) localStorage.setItem('gf_user_name', d.name);
+ }
+ } catch { /* continue without token — backend uses env */ }
+ }
const name = localStorage.getItem('gf_user_name') || 'Investor';
const initials = name.slice(0, 2).toUpperCase();
document.getElementById('user-avatar').textContent = initials;
@@ -9125,7 +9129,7 @@
localStorage.removeItem('gf_user_email');
localStorage.removeItem(STORAGE_KEY);
// Clear session-specific memory (keep watchlist / memory by default — user owns those)
- window.location.replace('/login');
+ window.location.replace('/');
});
// ── Clear session ──
diff --git a/main.py b/main.py
index 082cb6d69..55a311f20 100644
--- a/main.py
+++ b/main.py
@@ -5,13 +5,15 @@ from datetime import datetime
from fastapi import FastAPI, Response
from fastapi.middleware.cors import CORSMiddleware
-from fastapi.responses import StreamingResponse, HTMLResponse, JSONResponse
+from fastapi.responses import RedirectResponse, StreamingResponse, HTMLResponse, JSONResponse
from pydantic import BaseModel
from dotenv import load_dotenv
import httpx
from langchain_core.messages import HumanMessage, AIMessage
load_dotenv()
+# Load agent/.env so ANTHROPIC_API_KEY, GHOSTFOLIO_BEARER_TOKEN, etc. are available
+load_dotenv(os.path.join(os.path.dirname(__file__), "agent", ".env"))
from graph import build_graph
from state import AgentState
@@ -393,10 +395,34 @@ async def auth_login(req: LoginRequest):
}
-@app.get("/login", response_class=HTMLResponse, include_in_schema=False)
+@app.get("/auth/auto")
+async def auth_auto():
+ """
+ No-login auth: returns the configured token and user info without credentials.
+ Enables the app to work without a login page.
+ """
+ token = os.getenv("GHOSTFOLIO_BEARER_TOKEN", "")
+ base_url = os.getenv("GHOSTFOLIO_BASE_URL", "http://localhost:3333")
+ display_name = "Investor"
+ try:
+ async with httpx.AsyncClient(timeout=4.0) as client:
+ r = await client.get(
+ f"{base_url}/api/v1/user",
+ headers={"Authorization": f"Bearer {token}"},
+ )
+ if r.status_code == 200:
+ data = r.json()
+ alias = data.get("settings", {}).get("alias") or ""
+ display_name = alias or "Investor"
+ except Exception:
+ pass
+ return {"success": True, "token": token, "name": display_name}
+
+
+@app.get("/login", include_in_schema=False)
async def login_page():
- with open(os.path.join(os.path.dirname(__file__), "login.html")) as f:
- return f.read()
+ """Redirect to chat — login is bypassed."""
+ return RedirectResponse(url="/", status_code=302)
@app.get("/me")