ArunKr commited on
Commit
2cb8f5c
·
verified ·
1 Parent(s): 77ad471

Upload folder using huggingface_hub

Browse files
TASKS.md CHANGED
@@ -20,9 +20,9 @@ Legend:
20
  - [ ] Standardize API error schema across endpoints (single shape for UI).
21
 
22
  ## P2 — UI/UX, settings, admin, landing
23
- - [~] Landing + route split (`/` landing, `/login`, `/app`) and UI redirects updated.
24
- - [ ] Split `static/dashboard.html` into JS/CSS modules.
25
- - [ ] Theme tokens shared across login + dashboard (single source of truth).
26
  - [ ] Separate Settings vs Admin dashboard (dedicated pages/sections + RBAC).
27
 
28
  ## P2 — Provider auth parity (Codex/Gemini/Claude)
@@ -41,7 +41,7 @@ Legend:
41
  - [ ] Stop/reconnect improvements (resume stream after transient disconnects).
42
 
43
  ## P2/P3 — MCP registry
44
- - [ ] First-class MCP registry storage (per-user + templates).
45
  - [ ] “Test connection”, “list tools”, tool allow/deny UI.
46
  - [ ] Import/export `mcp.json` via UI with validation.
47
 
@@ -61,4 +61,3 @@ Legend:
61
  - [x] Add `.env.example`.
62
  - [x] Add `docs/ARCHITECTURE.md`, `docs/SECURITY_DEPLOYMENT.md`, `docs/TROUBLESHOOTING.md`.
63
  - [x] Add lint/tests + CI (`ruff`, `pytest`, `.github/workflows/ci.yml`).
64
-
 
20
  - [ ] Standardize API error schema across endpoints (single shape for UI).
21
 
22
  ## P2 — UI/UX, settings, admin, landing
23
+ - [x] Landing + route split (`/` landing, `/login`, `/app`) and UI redirects updated.
24
+ - [x] Split `static/dashboard.html` into JS/CSS files (`static/dashboard.js`, `static/dashboard.css`).
25
+ - [~] Theme tokens shared across login + dashboard (single source of truth via `static/theme.css`).
26
  - [ ] Separate Settings vs Admin dashboard (dedicated pages/sections + RBAC).
27
 
28
  ## P2 — Provider auth parity (Codex/Gemini/Claude)
 
41
  - [ ] Stop/reconnect improvements (resume stream after transient disconnects).
42
 
43
  ## P2/P3 — MCP registry
44
+ - [~] First-class MCP registry storage (per-user persistence via backend).
45
  - [ ] “Test connection”, “list tools”, tool allow/deny UI.
46
  - [ ] Import/export `mcp.json` via UI with validation.
47
 
 
61
  - [x] Add `.env.example`.
62
  - [x] Add `docs/ARCHITECTURE.md`, `docs/SECURITY_DEPLOYMENT.md`, `docs/TROUBLESHOOTING.md`.
63
  - [x] Add lint/tests + CI (`ruff`, `pytest`, `.github/workflows/ci.yml`).
 
app/routes/base.py CHANGED
@@ -38,6 +38,16 @@ async def read_app():
38
  return FileResponse(str(_STATIC / "dashboard.html"))
39
 
40
 
 
 
 
 
 
 
 
 
 
 
41
  @router.get("/health")
42
  async def health_check():
43
  return {"status": "ok"}
 
38
  return FileResponse(str(_STATIC / "dashboard.html"))
39
 
40
 
41
+ @router.get("/settings")
42
+ async def read_settings():
43
+ return FileResponse(str(_STATIC / "dashboard.html"))
44
+
45
+
46
+ @router.get("/admin")
47
+ async def read_admin():
48
+ return FileResponse(str(_STATIC / "dashboard.html"))
49
+
50
+
51
  @router.get("/health")
52
  async def health_check():
53
  return {"status": "ok"}
app/routes/user.py ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from typing import Any, Optional
5
+
6
+ from fastapi import APIRouter, HTTPException, Request
7
+ from pydantic import BaseModel
8
+
9
+ from app.auth import require_user_from_request
10
+ from app.settings import feature_enabled
11
+ from app.storage import user_data_dir
12
+
13
+ router = APIRouter()
14
+
15
+
16
+ def _is_admin(user: dict[str, Any]) -> bool:
17
+ # Minimal heuristic; can be replaced with Supabase custom claims / RLS-backed roles later.
18
+ meta = user.get("user_metadata") or {}
19
+ if isinstance(meta, dict) and meta.get("is_admin") is True:
20
+ return True
21
+ return False
22
+
23
+
24
+ @router.get("/api/me")
25
+ async def me(http_request: Request):
26
+ user = await require_user_from_request(http_request)
27
+ return {
28
+ "id": user.get("id"),
29
+ "email": user.get("email"),
30
+ "isAdmin": _is_admin(user),
31
+ "features": {
32
+ "terminal": feature_enabled("terminal"),
33
+ "codex": feature_enabled("codex"),
34
+ "mcp": feature_enabled("mcp"),
35
+ "indexing": feature_enabled("indexing"),
36
+ },
37
+ }
38
+
39
+
40
+ class McpRegistry(BaseModel):
41
+ version: int = 1
42
+ servers: list[dict[str, Any]] = []
43
+
44
+
45
+ def _registry_path(user_id: str) -> str:
46
+ return str(user_data_dir(user_id) / "mcp-registry.json")
47
+
48
+
49
+ @router.get("/api/user/mcp-registry")
50
+ async def get_mcp_registry(http_request: Request):
51
+ user = await require_user_from_request(http_request)
52
+ path = _registry_path(str(user.get("id") or ""))
53
+ try:
54
+ with open(path, "r", encoding="utf-8") as f:
55
+ return json.load(f)
56
+ except FileNotFoundError:
57
+ return {"version": 1, "servers": []}
58
+ except Exception as e:
59
+ raise HTTPException(status_code=500, detail=str(e))
60
+
61
+
62
+ @router.put("/api/user/mcp-registry")
63
+ async def put_mcp_registry(body: McpRegistry, http_request: Request):
64
+ user = await require_user_from_request(http_request)
65
+ path = _registry_path(str(user.get("id") or ""))
66
+
67
+ # Light validation: ensure each server has an id and url.
68
+ servers = []
69
+ for s in body.servers or []:
70
+ if not isinstance(s, dict):
71
+ continue
72
+ sid = str(s.get("id") or s.get("name") or "").strip()
73
+ url = str(s.get("url") or "").strip()
74
+ if not sid or not url:
75
+ continue
76
+ servers.append(s)
77
+
78
+ payload = {"version": int(body.version or 1), "servers": servers}
79
+ try:
80
+ with open(path, "w", encoding="utf-8") as f:
81
+ json.dump(payload, f, indent=2, ensure_ascii=False)
82
+ f.write("\n")
83
+ return {"ok": True, "count": len(servers)}
84
+ except Exception as e:
85
+ raise HTTPException(status_code=500, detail=str(e))
86
+
app/server.py CHANGED
@@ -14,6 +14,7 @@ from app.routes.chat import router as chat_router
14
  from app.routes.codex import router as codex_router
15
  from app.routes.mcp import router as mcp_router
16
  from app.routes.terminal import router as terminal_router
 
17
 
18
  _ROOT = Path(__file__).resolve().parent.parent
19
 
@@ -73,5 +74,6 @@ def create_app() -> FastAPI:
73
  app.include_router(codex_router)
74
  app.include_router(mcp_router)
75
  app.include_router(terminal_router)
 
76
 
77
  return app
 
14
  from app.routes.codex import router as codex_router
15
  from app.routes.mcp import router as mcp_router
16
  from app.routes.terminal import router as terminal_router
17
+ from app.routes.user import router as user_router
18
 
19
  _ROOT = Path(__file__).resolve().parent.parent
20
 
 
74
  app.include_router(codex_router)
75
  app.include_router(mcp_router)
76
  app.include_router(terminal_router)
77
+ app.include_router(user_router)
78
 
79
  return app
app/storage.py ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ from pathlib import Path
5
+
6
+
7
+ def _writable_dir(path: Path) -> bool:
8
+ try:
9
+ path.mkdir(parents=True, exist_ok=True)
10
+ test = path / ".write_test"
11
+ test.write_text("ok", encoding="utf-8")
12
+ test.unlink(missing_ok=True)
13
+ return True
14
+ except Exception:
15
+ return False
16
+
17
+
18
+ def user_data_dir(user_id: str) -> Path:
19
+ """
20
+ Returns a per-user writable directory for server-side persistence.
21
+
22
+ Prefers `/data` (HF Spaces) and falls back to `~/.autonomy-labs`.
23
+ """
24
+ user_id = (user_id or "").strip() or "unknown"
25
+
26
+ preferred = Path("/data") / "autonomy-labs" / "users" / user_id
27
+ if preferred.parent.exists() and _writable_dir(preferred):
28
+ return preferred
29
+
30
+ fallback = Path(os.path.expanduser("~")) / ".autonomy-labs" / "users" / user_id
31
+ fallback.mkdir(parents=True, exist_ok=True)
32
+ return fallback
33
+
docs/ARCHITECTURE.md CHANGED
@@ -17,7 +17,9 @@
17
  - `codex.py`: `/api/codex*` and Codex login helpers
18
  - `mcp.py`: `/api/mcp/*`
19
  - `terminal.py`: `/ws/terminal`
 
20
  - `app/auth.py`: Supabase access-token verification (server-side) with small TTL cache.
 
21
 
22
  ## Frontend layout
23
 
@@ -31,4 +33,3 @@ The following capabilities are high-risk and gated:
31
  - MCP tool calls
32
 
33
  Auth is enforced server-side via Supabase access tokens. Feature flags can disable them entirely.
34
-
 
17
  - `codex.py`: `/api/codex*` and Codex login helpers
18
  - `mcp.py`: `/api/mcp/*`
19
  - `terminal.py`: `/ws/terminal`
20
+ - `user.py`: `/api/me` and per-user persisted config (e.g., MCP registry)
21
  - `app/auth.py`: Supabase access-token verification (server-side) with small TTL cache.
22
+ - `app/storage.py`: per-user server-side persistence directory selection (`/data` preferred).
23
 
24
  ## Frontend layout
25
 
 
33
  - MCP tool calls
34
 
35
  Auth is enforced server-side via Supabase access tokens. Feature flags can disable them entirely.
 
static/dashboard.css ADDED
@@ -0,0 +1,370 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Common background utility overrides for light mode */
2
+ [data-theme="light"] .bg-gray-900 { background-color: var(--panel-bg-2) !important; }
3
+ [data-theme="light"] .bg-gray-800 { background-color: var(--panel-bg) !important; }
4
+ [data-theme="light"] .bg-black { background-color: #0b1220 !important; }
5
+ [data-theme="light"] #terminal-view { background-color: var(--panel-bg-2) !important; }
6
+ [data-theme="light"] .border-gray-700 { border-color: var(--border) !important; }
7
+ [data-theme="light"] .border-gray-600 { border-color: var(--border) !important; }
8
+
9
+ /* Text utility overrides for light mode */
10
+ [data-theme="light"] .text-white { color: var(--app-fg) !important; }
11
+ [data-theme="light"] .text-gray-300 { color: rgba(15, 23, 42, 0.92) !important; }
12
+ [data-theme="light"] .text-gray-400 { color: rgba(15, 23, 42, 0.78) !important; }
13
+ [data-theme="light"] .text-gray-500 { color: rgba(15, 23, 42, 0.66) !important; }
14
+ [data-theme="light"] .text-gray-200 { color: rgba(15, 23, 42, 0.96) !important; }
15
+
16
+ [data-theme="light"] a { color: #1d4ed8; }
17
+ [data-theme="light"] .prose a { color: #1d4ed8; }
18
+
19
+ /* Inputs in light mode */
20
+ [data-theme="light"] input,
21
+ [data-theme="light"] textarea,
22
+ [data-theme="light"] select {
23
+ background-color: #ffffff !important;
24
+ color: var(--app-fg) !important;
25
+ border-color: var(--border) !important;
26
+ }
27
+
28
+ /* Chat bubbles in light mode */
29
+ [data-theme="light"] .user-message {
30
+ background-color: rgba(37, 99, 235, 0.10);
31
+ border-color: rgba(37, 99, 235, 0.22);
32
+ color: rgba(2, 6, 23, 0.9);
33
+ }
34
+ [data-theme="light"] .ai-message {
35
+ background-color: rgba(15, 23, 42, 0.04);
36
+ border-color: rgba(15, 23, 42, 0.10);
37
+ color: rgba(2, 6, 23, 0.9);
38
+ }
39
+
40
+ /* Notes preview readability in light mode */
41
+ [data-theme="light"] #notes-preview {
42
+ color: rgba(2, 6, 23, 0.92);
43
+ }
44
+
45
+ /* Dashboard cards/surfaces in light mode (Tailwind arbitrary opacity classes) */
46
+ [data-theme="light"] .bg-gray-800\\/60 { background-color: rgba(255, 255, 255, 0.92) !important; }
47
+ [data-theme="light"] .bg-gray-900\\/40 { background-color: rgba(15, 23, 42, 0.03) !important; }
48
+ [data-theme="light"] .bg-gray-900\\/30 { background-color: rgba(15, 23, 42, 0.03) !important; }
49
+
50
+ [data-theme="light"] .shadow-xl { box-shadow: 0 18px 45px rgba(2, 6, 23, 0.08) !important; }
51
+ .view-section {
52
+ transition: opacity 0.2s;
53
+ }
54
+
55
+ ::-webkit-scrollbar {
56
+ width: 8px;
57
+ height: 8px;
58
+ }
59
+
60
+ ::-webkit-scrollbar-track {
61
+ bg: #1f2937;
62
+ }
63
+
64
+ ::-webkit-scrollbar-thumb {
65
+ background: #4b5563;
66
+ border-radius: 4px;
67
+ }
68
+
69
+ ::-webkit-scrollbar-thumb:hover {
70
+ background: #6b7280;
71
+ }
72
+
73
+ .katex {
74
+ font-size: 1.1em;
75
+ }
76
+
77
+ .chat-message pre {
78
+ margin: 0;
79
+ padding: 0.5rem;
80
+ background: #1a1b26;
81
+ border-radius: 0.5rem;
82
+ position: relative;
83
+ overflow-x: auto;
84
+ }
85
+
86
+ /* Ensure code is always readable (prose/prose-invert can override colors). */
87
+ .chat-message pre code {
88
+ color: #e5e7eb !important;
89
+ background: transparent !important;
90
+ font-size: 0.9em;
91
+ }
92
+
93
+ .chat-message code {
94
+ color: #e5e7eb !important;
95
+ }
96
+
97
+ .chat-message :not(pre) > code {
98
+ color: #e5e7eb;
99
+ background: rgba(255, 255, 255, 0.08);
100
+ border: 1px solid rgba(255, 255, 255, 0.12);
101
+ padding: 0.1rem 0.3rem;
102
+ border-radius: 0.35rem;
103
+ }
104
+
105
+ /* Tailwind typography adds backticks via pseudo-elements; disable to avoid confusion. */
106
+ .chat-message.prose code::before,
107
+ .chat-message.prose code::after {
108
+ content: "" !important;
109
+ }
110
+
111
+ .chat-message pre code .hljs-comment,
112
+ .chat-message pre code .hljs-quote {
113
+ color: #9ca3af;
114
+ }
115
+
116
+ .copy-btn {
117
+ position: absolute;
118
+ top: 5px;
119
+ right: 5px;
120
+ background: #374151;
121
+ color: white;
122
+ border-radius: 4px;
123
+ padding: 2px 6px;
124
+ font-size: 0.75rem;
125
+ opacity: 0;
126
+ transition: opacity 0.2s;
127
+ }
128
+
129
+ .chat-message pre:hover .copy-btn {
130
+ opacity: 1;
131
+ }
132
+
133
+ .run-btn {
134
+ position: absolute;
135
+ top: 5px;
136
+ right: 56px;
137
+ background: #2563eb;
138
+ color: white;
139
+ border-radius: 4px;
140
+ padding: 2px 6px;
141
+ font-size: 0.75rem;
142
+ opacity: 0;
143
+ transition: opacity 0.2s;
144
+ }
145
+
146
+ .chat-message pre:hover .run-btn {
147
+ opacity: 1;
148
+ }
149
+
150
+ .xterm-viewport {
151
+ overflow-y: hidden !important;
152
+ }
153
+
154
+ /* Ubuntu-ish terminal look */
155
+ .xterm, .xterm * {
156
+ font-family: "Ubuntu Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
157
+ }
158
+
159
+ /* Terminal chrome (borders + subtle depth) */
160
+ #terminals-container > div,
161
+ #agent-terminals-container > div {
162
+ border: 1px solid rgba(148, 163, 184, 0.18);
163
+ border-radius: 12px;
164
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.25);
165
+ background: #300a24;
166
+ }
167
+
168
+ [data-theme="light"] #terminals-container > div {
169
+ border-color: rgba(2, 6, 23, 0.12);
170
+ box-shadow: 0 10px 30px rgba(2, 6, 23, 0.10);
171
+ }
172
+
173
+ /* Make the xterm area blend with the chrome */
174
+ #terminals-container .xterm,
175
+ #agent-terminals-container .xterm {
176
+ padding: 10px;
177
+ }
178
+
179
+ /* Nicer scrollbars inside terminals */
180
+ #terminals-container .xterm-viewport::-webkit-scrollbar,
181
+ #agent-terminals-container .xterm-viewport::-webkit-scrollbar {
182
+ width: 10px;
183
+ }
184
+ #terminals-container .xterm-viewport::-webkit-scrollbar-thumb,
185
+ #agent-terminals-container .xterm-viewport::-webkit-scrollbar-thumb {
186
+ background: rgba(148, 163, 184, 0.25);
187
+ border-radius: 10px;
188
+ border: 2px solid rgba(0, 0, 0, 0);
189
+ background-clip: padding-box;
190
+ }
191
+
192
+ /* Resizable terminal viewport wrapper */
193
+ .terminal-resizable {
194
+ resize: both;
195
+ overflow: auto;
196
+ min-width: 320px;
197
+ min-height: 200px;
198
+ max-width: 100%;
199
+ max-height: 100%;
200
+ position: relative;
201
+ }
202
+
203
+ /* Terminal split layouts (Terminal tab only) */
204
+ .terminal-grid {
205
+ display: grid;
206
+ gap: 10px;
207
+ padding: 10px;
208
+ }
209
+ .terminal-grid.single { grid-template-columns: 1fr; grid-template-rows: 1fr; }
210
+ .terminal-grid.vsplit { grid-template-columns: 1fr 1fr; grid-template-rows: 1fr; }
211
+ .terminal-grid.hsplit { grid-template-columns: 1fr; grid-template-rows: 1fr 1fr; }
212
+ .terminal-grid.quad { grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1fr; }
213
+ .terminal-pane {
214
+ position: relative;
215
+ min-height: 0;
216
+ min-width: 0;
217
+ }
218
+
219
+ /* Agent split panes (chat | terminal) */
220
+ .split-root {
221
+ display: flex;
222
+ flex: 1 1 auto;
223
+ min-height: 0;
224
+ min-width: 0;
225
+ }
226
+
227
+ .split-pane {
228
+ min-width: 0;
229
+ min-height: 0;
230
+ display: flex;
231
+ flex-direction: column;
232
+ }
233
+
234
+ .split-divider {
235
+ width: 10px;
236
+ cursor: col-resize;
237
+ background: linear-gradient(to bottom, rgba(148, 163, 184, 0.15), rgba(148, 163, 184, 0.05));
238
+ border-left: 1px solid rgba(148, 163, 184, 0.15);
239
+ border-right: 1px solid rgba(148, 163, 184, 0.15);
240
+ flex: 0 0 auto;
241
+ user-select: none;
242
+ touch-action: none;
243
+ }
244
+
245
+ .split-divider::after {
246
+ content: "";
247
+ position: absolute;
248
+ width: 4px;
249
+ height: 42px;
250
+ left: 50%;
251
+ top: 50%;
252
+ transform: translate(-50%, -50%);
253
+ border-radius: 6px;
254
+ background: rgba(148, 163, 184, 0.25);
255
+ }
256
+
257
+ .split-divider {
258
+ position: relative;
259
+ }
260
+
261
+ .agent-terminal-chrome {
262
+ background: radial-gradient(1200px 600px at 20% 0%, rgba(114, 159, 207, 0.18), transparent 60%),
263
+ radial-gradient(1000px 500px at 80% 100%, rgba(173, 127, 168, 0.12), transparent 55%),
264
+ #0b1220;
265
+ }
266
+
267
+ .agent-pane-surface {
268
+ background: rgba(15, 23, 42, 0.35);
269
+ }
270
+
271
+ .agent-toolbar {
272
+ display: flex;
273
+ align-items: center;
274
+ justify-content: space-between;
275
+ gap: 8px;
276
+ padding: 10px 12px;
277
+ border-bottom: 1px solid rgba(75, 85, 99, 0.7);
278
+ background: rgba(17, 24, 39, 0.75);
279
+ backdrop-filter: blur(6px);
280
+ }
281
+
282
+ .agent-toolbar .hint {
283
+ font-size: 12px;
284
+ color: rgba(203, 213, 225, 0.75);
285
+ }
286
+
287
+ .attachment-strip {
288
+ display: flex;
289
+ gap: 8px;
290
+ flex-wrap: wrap;
291
+ }
292
+
293
+ .attachment-thumb {
294
+ width: 88px;
295
+ height: 64px;
296
+ border-radius: 10px;
297
+ overflow: hidden;
298
+ position: relative;
299
+ border: 1px solid rgba(148, 163, 184, 0.25);
300
+ background: rgba(15, 23, 42, 0.35);
301
+ }
302
+
303
+ .attachment-thumb img {
304
+ width: 100%;
305
+ height: 100%;
306
+ object-fit: cover;
307
+ display: block;
308
+ }
309
+
310
+ .attachment-thumb button {
311
+ position: absolute;
312
+ top: 4px;
313
+ right: 4px;
314
+ width: 20px;
315
+ height: 20px;
316
+ border-radius: 999px;
317
+ border: 0;
318
+ background: rgba(15, 23, 42, 0.75);
319
+ color: #e5e7eb;
320
+ cursor: pointer;
321
+ line-height: 20px;
322
+ font-size: 12px;
323
+ }
324
+
325
+ /* Unified Settings drawer */
326
+ .settings-overlay {
327
+ position: absolute;
328
+ inset: 0;
329
+ background: rgba(2, 6, 23, 0.6);
330
+ backdrop-filter: blur(2px);
331
+ z-index: 50;
332
+ }
333
+
334
+ .settings-drawer {
335
+ position: absolute;
336
+ top: 0;
337
+ right: 0;
338
+ height: 100%;
339
+ width: min(420px, 92vw);
340
+ background: rgba(17, 24, 39, 0.98);
341
+ border-left: 1px solid rgba(75, 85, 99, 0.7);
342
+ z-index: 60;
343
+ box-shadow: -20px 0 50px rgba(0, 0, 0, 0.35);
344
+ }
345
+
346
+ .settings-hidden {
347
+ display: none;
348
+ }
349
+
350
+ .chat-message {
351
+ max-width: 80%;
352
+ padding: 10px;
353
+ margin: 5px;
354
+ border-radius: 10px;
355
+ word-wrap: break-word;
356
+ }
357
+
358
+ .user-message {
359
+ background-color: rgba(96, 165, 250, 0.22);
360
+ border: 1px solid rgba(96, 165, 250, 0.35);
361
+ align-self: flex-end;
362
+ color: #e6f0ff;
363
+ }
364
+
365
+ .ai-message {
366
+ background-color: rgba(148, 163, 184, 0.12);
367
+ border: 1px solid rgba(148, 163, 184, 0.22);
368
+ align-self: flex-start;
369
+ color: #f1f5f9;
370
+ }
static/dashboard.html CHANGED
The diff for this file is too large to render. See raw diff
 
static/dashboard.js ADDED
The diff for this file is too large to render. See raw diff
 
static/index.html CHANGED
@@ -7,9 +7,10 @@
7
  <title>Login - autonomy-labs</title>
8
  <script src="https://cdn.tailwindcss.com"></script>
9
  <script src="https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2"></script>
 
10
  </head>
11
 
12
- <body class="bg-gray-900 text-white flex items-center justify-center min-h-screen">
13
  <div class="w-full max-w-md p-8 space-y-6 bg-gray-800 rounded-lg shadow-lg">
14
  <h1 class="text-3xl font-bold text-center text-blue-500">autonomy-labs Login</h1>
15
 
@@ -37,99 +38,7 @@
37
  </div>
38
  </form>
39
  </div>
40
-
41
- <script>
42
- let supabase;
43
-
44
- async function initSupabase() {
45
- try {
46
- const res = await fetch('/config');
47
- const config = await res.json();
48
- if (!config.supabase_url || !config.supabase_key) {
49
- throw new Error('Supabase configuration missing');
50
- }
51
- supabase = window.supabase.createClient(config.supabase_url, config.supabase_key);
52
-
53
- // Register toggle (default disabled)
54
- const allowSignup = localStorage.getItem('auth_allow_signup_v1') === '1';
55
- const registerBtn = document.getElementById('register-btn');
56
- if (!allowSignup && registerBtn) {
57
- registerBtn.style.display = 'none';
58
- }
59
-
60
- // Check if already logged in
61
- const { data: { session } } = await supabase.auth.getSession();
62
- if (session) {
63
- window.location.href = '/app';
64
- }
65
- } catch (error) {
66
- console.error('Error initializing Supabase:', error);
67
- showAlert('Error initializing application configuration', 'error');
68
- }
69
- }
70
-
71
- function showAlert(message, type = 'error') {
72
- const alert = document.getElementById('alert');
73
- alert.textContent = message;
74
- alert.classList.remove('hidden');
75
- if (type === 'success') {
76
- alert.classList.remove('text-red-500');
77
- alert.classList.add('text-green-500');
78
- } else {
79
- alert.classList.remove('text-green-500');
80
- alert.classList.add('text-red-500');
81
- }
82
- }
83
-
84
- async function handleAuth(type) {
85
- const email = document.getElementById('email').value;
86
- const password = document.getElementById('password').value;
87
-
88
- if (!supabase) {
89
- showAlert('Supabase not initialized');
90
- return;
91
- }
92
-
93
- try {
94
- let result;
95
- if (type === 'login') {
96
- result = await supabase.auth.signInWithPassword({ email, password });
97
- } else {
98
- result = await supabase.auth.signUp({ email, password });
99
- }
100
-
101
- if (result.error) throw result.error;
102
-
103
- if (type === 'register') {
104
- if (result.data && result.data.session) {
105
- // Email verification is disabled, user is logged in
106
- window.location.href = '/app';
107
- } else {
108
- showAlert('Registration successful! Please check your email to verify (if enabled) or try logging in.', 'success');
109
- }
110
- } else {
111
- window.location.href = '/app';
112
- }
113
- } catch (error) {
114
- showAlert(error.message);
115
- }
116
- }
117
-
118
- document.getElementById('auth-form').addEventListener('submit', (e) => {
119
- e.preventDefault();
120
- handleAuth('login');
121
- });
122
-
123
- document.getElementById('register-btn').addEventListener('click', () => {
124
- if (localStorage.getItem('auth_allow_signup_v1') === '1') {
125
- handleAuth('register');
126
- } else {
127
- showAlert('Registration is disabled by the administrator.');
128
- }
129
- });
130
-
131
- initSupabase();
132
- </script>
133
  </body>
134
 
135
  </html>
 
7
  <title>Login - autonomy-labs</title>
8
  <script src="https://cdn.tailwindcss.com"></script>
9
  <script src="https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2"></script>
10
+ <link rel="stylesheet" href="/static/theme.css">
11
  </head>
12
 
13
+ <body class="text-white flex items-center justify-center min-h-screen">
14
  <div class="w-full max-w-md p-8 space-y-6 bg-gray-800 rounded-lg shadow-lg">
15
  <h1 class="text-3xl font-bold text-center text-blue-500">autonomy-labs Login</h1>
16
 
 
38
  </div>
39
  </form>
40
  </div>
41
+ <script src="/static/login.js"></script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  </body>
43
 
44
  </html>
static/landing.html CHANGED
@@ -6,6 +6,7 @@
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
  <title>autonomy-labs</title>
8
  <script src="https://cdn.tailwindcss.com?plugins=typography"></script>
 
9
  </head>
10
 
11
  <body class="min-h-screen bg-gradient-to-b from-gray-950 via-gray-900 to-gray-950 text-white">
 
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
  <title>autonomy-labs</title>
8
  <script src="https://cdn.tailwindcss.com?plugins=typography"></script>
9
+ <link rel="stylesheet" href="/static/theme.css">
10
  </head>
11
 
12
  <body class="min-h-screen bg-gradient-to-b from-gray-950 via-gray-900 to-gray-950 text-white">
static/login.js ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ let supabase;
2
+
3
+ async function initSupabase() {
4
+ try {
5
+ const res = await fetch('/config');
6
+ const config = await res.json();
7
+ if (!config.supabase_url || !config.supabase_key) {
8
+ throw new Error('Supabase configuration missing');
9
+ }
10
+ supabase = window.supabase.createClient(config.supabase_url, config.supabase_key);
11
+
12
+ // Register toggle (default disabled)
13
+ const allowSignup = localStorage.getItem('auth_allow_signup_v1') === '1';
14
+ const registerBtn = document.getElementById('register-btn');
15
+ if (!allowSignup && registerBtn) {
16
+ registerBtn.style.display = 'none';
17
+ }
18
+
19
+ // Check if already logged in
20
+ const { data: { session } } = await supabase.auth.getSession();
21
+ if (session) {
22
+ window.location.href = '/app';
23
+ }
24
+ } catch (error) {
25
+ console.error('Error initializing Supabase:', error);
26
+ showAlert('Error initializing application configuration', 'error');
27
+ }
28
+ }
29
+
30
+ function showAlert(message, type = 'error') {
31
+ const alert = document.getElementById('alert');
32
+ alert.textContent = message;
33
+ alert.classList.remove('hidden');
34
+ if (type === 'success') {
35
+ alert.classList.remove('text-red-500');
36
+ alert.classList.add('text-green-500');
37
+ } else {
38
+ alert.classList.remove('text-green-500');
39
+ alert.classList.add('text-red-500');
40
+ }
41
+ }
42
+
43
+ async function handleAuth(type) {
44
+ const email = document.getElementById('email').value;
45
+ const password = document.getElementById('password').value;
46
+
47
+ if (!supabase) {
48
+ showAlert('Supabase not initialized');
49
+ return;
50
+ }
51
+
52
+ try {
53
+ let result;
54
+ if (type === 'login') {
55
+ result = await supabase.auth.signInWithPassword({ email, password });
56
+ } else {
57
+ result = await supabase.auth.signUp({ email, password });
58
+ }
59
+
60
+ if (result.error) throw result.error;
61
+
62
+ if (type === 'register') {
63
+ if (result.data && result.data.session) {
64
+ // Email verification is disabled, user is logged in
65
+ window.location.href = '/app';
66
+ } else {
67
+ showAlert('Registration successful! Please check your email to verify (if enabled) or try logging in.', 'success');
68
+ }
69
+ } else {
70
+ window.location.href = '/app';
71
+ }
72
+ } catch (error) {
73
+ showAlert(error.message);
74
+ }
75
+ }
76
+
77
+ document.getElementById('auth-form').addEventListener('submit', (e) => {
78
+ e.preventDefault();
79
+ handleAuth('login');
80
+ });
81
+
82
+ document.getElementById('register-btn').addEventListener('click', () => {
83
+ if (localStorage.getItem('auth_allow_signup_v1') === '1') {
84
+ handleAuth('register');
85
+ } else {
86
+ showAlert('Registration is disabled by the administrator.');
87
+ }
88
+ });
89
+
90
+ initSupabase();
static/theme.css ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ :root {
2
+ --app-bg: #111827;
3
+ --app-fg: #ffffff;
4
+ --panel-bg: #1f2937;
5
+ --panel-bg-2: #111827;
6
+ --border: rgba(75, 85, 99, 0.7);
7
+ --muted: rgba(203, 213, 225, 0.75);
8
+ }
9
+
10
+ [data-theme="light"] {
11
+ --app-bg: #f6f7fb;
12
+ --app-fg: #0b1220;
13
+ --panel-bg: #ffffff;
14
+ --panel-bg-2: #f3f5fa;
15
+ --border: rgba(2, 6, 23, 0.12);
16
+ --muted: rgba(30, 41, 59, 0.72);
17
+ }
18
+
19
+ html,
20
+ body {
21
+ background: var(--app-bg);
22
+ color: var(--app-fg);
23
+ }
24
+
25
+ nav,
26
+ footer {
27
+ background: var(--panel-bg) !important;
28
+ border-color: var(--border) !important;
29
+ }
30
+
31
+ [data-theme="light"] .bg-gray-900 {
32
+ background-color: var(--panel-bg-2) !important;
33
+ }
34
+ [data-theme="light"] .bg-gray-800 {
35
+ background-color: var(--panel-bg) !important;
36
+ }
37
+ [data-theme="light"] .bg-black {
38
+ background-color: #0b1220 !important;
39
+ }
40
+ [data-theme="light"] .border-gray-700,
41
+ [data-theme="light"] .border-gray-600 {
42
+ border-color: var(--border) !important;
43
+ }
44
+
45
+ [data-theme="light"] .text-white {
46
+ color: var(--app-fg) !important;
47
+ }
48
+ [data-theme="light"] .text-gray-300 {
49
+ color: rgba(15, 23, 42, 0.92) !important;
50
+ }
51
+ [data-theme="light"] .text-gray-400 {
52
+ color: rgba(15, 23, 42, 0.78) !important;
53
+ }
54
+ [data-theme="light"] .text-gray-500 {
55
+ color: rgba(15, 23, 42, 0.66) !important;
56
+ }
57
+ [data-theme="light"] .text-gray-200 {
58
+ color: rgba(15, 23, 42, 0.96) !important;
59
+ }
60
+
61
+ [data-theme="light"] a,
62
+ [data-theme="light"] .prose a {
63
+ color: #1d4ed8;
64
+ }
65
+
66
+ [data-theme="light"] input,
67
+ [data-theme="light"] textarea,
68
+ [data-theme="light"] select {
69
+ background-color: #ffffff !important;
70
+ color: var(--app-fg) !important;
71
+ border-color: var(--border) !important;
72
+ }
73
+