opencode-dashboard-server 0.1.1__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (19) hide show
  1. opencode_dashboard_server-0.3.0/PKG-INFO +84 -0
  2. opencode_dashboard_server-0.3.0/README.md +70 -0
  3. {opencode_dashboard_server-0.1.1 → opencode_dashboard_server-0.3.0}/app.py +29 -12
  4. opencode_dashboard_server-0.3.0/cli.py +126 -0
  5. {opencode_dashboard_server-0.1.1 → opencode_dashboard_server-0.3.0}/db.py +6 -2
  6. opencode_dashboard_server-0.3.0/opencode_dashboard_server.egg-info/PKG-INFO +84 -0
  7. {opencode_dashboard_server-0.1.1 → opencode_dashboard_server-0.3.0}/opencode_dashboard_server.egg-info/SOURCES.txt +2 -0
  8. opencode_dashboard_server-0.3.0/opencode_dashboard_server.egg-info/entry_points.txt +2 -0
  9. {opencode_dashboard_server-0.1.1 → opencode_dashboard_server-0.3.0}/opencode_dashboard_server.egg-info/requires.txt +2 -0
  10. {opencode_dashboard_server-0.1.1 → opencode_dashboard_server-0.3.0}/opencode_dashboard_server.egg-info/top_level.txt +1 -0
  11. {opencode_dashboard_server-0.1.1 → opencode_dashboard_server-0.3.0}/pyproject.toml +9 -2
  12. {opencode_dashboard_server-0.1.1 → opencode_dashboard_server-0.3.0}/tests/test_app.py +12 -0
  13. opencode_dashboard_server-0.1.1/PKG-INFO +0 -54
  14. opencode_dashboard_server-0.1.1/README.md +0 -44
  15. opencode_dashboard_server-0.1.1/opencode_dashboard_server.egg-info/PKG-INFO +0 -54
  16. {opencode_dashboard_server-0.1.1 → opencode_dashboard_server-0.3.0}/aggregate.py +0 -0
  17. {opencode_dashboard_server-0.1.1 → opencode_dashboard_server-0.3.0}/opencode_dashboard_server.egg-info/dependency_links.txt +0 -0
  18. {opencode_dashboard_server-0.1.1 → opencode_dashboard_server-0.3.0}/setup.cfg +0 -0
  19. {opencode_dashboard_server-0.1.1 → opencode_dashboard_server-0.3.0}/tests/test_aggregate.py +0 -0
@@ -0,0 +1,84 @@
1
+ Metadata-Version: 2.4
2
+ Name: opencode-dashboard-server
3
+ Version: 0.3.0
4
+ Summary: FastAPI aggregator over opencode's SQLite storage
5
+ Project-URL: Homepage, https://github.com/GCS-ZHN/opencode-dashboard
6
+ Project-URL: Repository, https://github.com/GCS-ZHN/opencode-dashboard
7
+ Project-URL: Issues, https://github.com/GCS-ZHN/opencode-dashboard/issues
8
+ Requires-Python: >=3.10
9
+ Description-Content-Type: text/markdown
10
+ Requires-Dist: fastapi<1,>=0.115
11
+ Requires-Dist: uvicorn<1,>=0.30
12
+ Requires-Dist: platformdirs<5,>=4
13
+ Requires-Dist: PyYAML<7,>=6
14
+
15
+ # opencode-dashboard-server
16
+
17
+ FastAPI aggregation backend for the [opencode-dashboard](https://github.com/GCS-ZHN/opencode-dashboard)
18
+ project. Reads opencode's local SQLite storage via the `opencode db` CLI and exposes a JSON + SSE
19
+ API for the dashboard front end.
20
+
21
+ ## Install (PyPI)
22
+
23
+ ```bash
24
+ pip install opencode-dashboard-server
25
+ # or with uv:
26
+ uv tool install opencode-dashboard-server
27
+ ```
28
+
29
+ Requires:
30
+
31
+ - **Python ≥ 3.10**
32
+ - **`opencode` CLI** on the same host — the server shells out to `opencode db "<SQL>"` and never
33
+ opens the SQLite file directly (it's WAL-mode and actively written while opencode runs).
34
+
35
+ ## Quick start
36
+
37
+ Configure once (interactive; writes `~/.config/opencode-dashboard/server.yaml`), then serve:
38
+
39
+ ```bash
40
+ opencode-dashboard-server configure
41
+ opencode-dashboard-server serve
42
+ ```
43
+
44
+ `configure` walks you through `port` (default `8791`), `host` (default `0.0.0.0`),
45
+ `cors_origins` (comma-separated; empty = built-in loopback whitelist), `poll_seconds`
46
+ (default `5`), and `opencode_bin` (default `opencode`). Empty input keeps the default.
47
+
48
+ `serve` accepts overrides: `--port N`, `--host H`, `--config PATH` (instead of the XDG file).
49
+ Precedence for port/host: CLI flag > env `PORT`/`HOST` > config file > default.
50
+
51
+ Run one aggregator per opencode host:
52
+
53
+ ```bash
54
+ uvicorn app:app --port 8791
55
+ ```
56
+
57
+ or directly with uvicorn (still works — reads env/defaults only). Run on more hosts with
58
+ different ports (`8792`, `8793`, …) and list each in the front end's config (`opencode-dashboard configure`).
59
+ Environment switches (used when no config file value is set):
60
+
61
+ - `DASHBOARD_CORS_ORIGINS` — comma-separated CORS allow-list (defaults to the loopback dev origins `localhost/127.0.0.1:5173` and `:4173`; tighten or widen for your deployment).
62
+ - `DASHBOARD_POLL_SECONDS` — SSE poll interval in seconds (default `5`).
63
+ - `OPENCODE_BIN` — path to the `opencode` binary if it's not on `PATH`.
64
+
65
+ ## API
66
+
67
+ - `GET /health` — liveness
68
+ - `GET /overview` — whole-host aggregate (tokens by type + cost, session counts, `updatedAt`)
69
+ - `GET /projects` — per-project rollups
70
+ - `GET /projects/{id}` — one project's sessions (parent/child tree)
71
+ - `GET /sessions/{id}` — per-model breakdown for one session (handles mid-conversation model switches)
72
+ - `GET /stream` — SSE: `updated` events when the DB changes (polled ~5s)
73
+
74
+ JSON is camelCase, timestamps are epoch-ms. Full contract in the repo's `API.md`.
75
+
76
+ ## From source (development)
77
+
78
+ ```bash
79
+ git clone https://github.com/GCS-ZHN/opencode-dashboard
80
+ cd opencode-dashboard/server
81
+ uv sync # install deps (fastapi, uvicorn) + dev (pytest, httpx)
82
+ uv run pytest # run tests
83
+ uvx ruff check . # lint
84
+ ```
@@ -0,0 +1,70 @@
1
+ # opencode-dashboard-server
2
+
3
+ FastAPI aggregation backend for the [opencode-dashboard](https://github.com/GCS-ZHN/opencode-dashboard)
4
+ project. Reads opencode's local SQLite storage via the `opencode db` CLI and exposes a JSON + SSE
5
+ API for the dashboard front end.
6
+
7
+ ## Install (PyPI)
8
+
9
+ ```bash
10
+ pip install opencode-dashboard-server
11
+ # or with uv:
12
+ uv tool install opencode-dashboard-server
13
+ ```
14
+
15
+ Requires:
16
+
17
+ - **Python ≥ 3.10**
18
+ - **`opencode` CLI** on the same host — the server shells out to `opencode db "<SQL>"` and never
19
+ opens the SQLite file directly (it's WAL-mode and actively written while opencode runs).
20
+
21
+ ## Quick start
22
+
23
+ Configure once (interactive; writes `~/.config/opencode-dashboard/server.yaml`), then serve:
24
+
25
+ ```bash
26
+ opencode-dashboard-server configure
27
+ opencode-dashboard-server serve
28
+ ```
29
+
30
+ `configure` walks you through `port` (default `8791`), `host` (default `0.0.0.0`),
31
+ `cors_origins` (comma-separated; empty = built-in loopback whitelist), `poll_seconds`
32
+ (default `5`), and `opencode_bin` (default `opencode`). Empty input keeps the default.
33
+
34
+ `serve` accepts overrides: `--port N`, `--host H`, `--config PATH` (instead of the XDG file).
35
+ Precedence for port/host: CLI flag > env `PORT`/`HOST` > config file > default.
36
+
37
+ Run one aggregator per opencode host:
38
+
39
+ ```bash
40
+ uvicorn app:app --port 8791
41
+ ```
42
+
43
+ or directly with uvicorn (still works — reads env/defaults only). Run on more hosts with
44
+ different ports (`8792`, `8793`, …) and list each in the front end's config (`opencode-dashboard configure`).
45
+ Environment switches (used when no config file value is set):
46
+
47
+ - `DASHBOARD_CORS_ORIGINS` — comma-separated CORS allow-list (defaults to the loopback dev origins `localhost/127.0.0.1:5173` and `:4173`; tighten or widen for your deployment).
48
+ - `DASHBOARD_POLL_SECONDS` — SSE poll interval in seconds (default `5`).
49
+ - `OPENCODE_BIN` — path to the `opencode` binary if it's not on `PATH`.
50
+
51
+ ## API
52
+
53
+ - `GET /health` — liveness
54
+ - `GET /overview` — whole-host aggregate (tokens by type + cost, session counts, `updatedAt`)
55
+ - `GET /projects` — per-project rollups
56
+ - `GET /projects/{id}` — one project's sessions (parent/child tree)
57
+ - `GET /sessions/{id}` — per-model breakdown for one session (handles mid-conversation model switches)
58
+ - `GET /stream` — SSE: `updated` events when the DB changes (polled ~5s)
59
+
60
+ JSON is camelCase, timestamps are epoch-ms. Full contract in the repo's `API.md`.
61
+
62
+ ## From source (development)
63
+
64
+ ```bash
65
+ git clone https://github.com/GCS-ZHN/opencode-dashboard
66
+ cd opencode-dashboard/server
67
+ uv sync # install deps (fastapi, uvicorn) + dev (pytest, httpx)
68
+ uv run pytest # run tests
69
+ uvx ruff check . # lint
70
+ ```
@@ -6,6 +6,7 @@ Run: uv run uvicorn app:app --reload
6
6
  import asyncio
7
7
  import json
8
8
  import logging
9
+ import os
9
10
  import socket
10
11
  import subprocess
11
12
  from functools import lru_cache
@@ -19,28 +20,44 @@ from db import CliRunner
19
20
 
20
21
  logger = logging.getLogger("dashboard")
21
22
 
23
+ # Comma-separated CORS allow-list; defaults to the loopback dev origins. Tight
24
+ # by default so a random webpage can't exfiltrate local project/session data.
25
+ def default_cors_origins() -> list[str]:
26
+ env = os.environ.get("DASHBOARD_CORS_ORIGINS", "").strip()
27
+ if env:
28
+ return [o.strip() for o in env.split(",") if o.strip()]
29
+ return [
30
+ "http://localhost:5173", "http://127.0.0.1:5173",
31
+ "http://localhost:4173", "http://127.0.0.1:4173",
32
+ ]
22
33
 
23
- @lru_cache(maxsize=1)
24
- def opencode_version() -> str:
34
+
35
+ @lru_cache(maxsize=8)
36
+ def opencode_version(executable: str = "opencode") -> str:
25
37
  try:
26
38
  return subprocess.run(
27
- ["opencode", "--version"], capture_output=True, text=True, check=True
39
+ [executable, "--version"], capture_output=True, text=True, check=True
28
40
  ).stdout.strip()
29
41
  except (OSError, subprocess.CalledProcessError):
30
42
  return "unknown" # e.g. CI without the opencode CLI; don't fail the request
31
43
 
32
44
 
33
- def create_app(runner=None) -> FastAPI:
34
- runner = runner or CliRunner()
45
+ def create_app(runner=None, cors_origins=None, poll_seconds=None, opencode_bin=None) -> FastAPI:
46
+ bin = opencode_bin or os.environ.get("OPENCODE_BIN") or "opencode"
47
+ if runner is None:
48
+ runner = CliRunner(executable=bin)
49
+ origins = cors_origins if cors_origins is not None else default_cors_origins()
50
+ if poll_seconds is None:
51
+ try:
52
+ poll_seconds = float(os.environ.get("DASHBOARD_POLL_SECONDS", "5"))
53
+ except ValueError:
54
+ poll_seconds = 5.0
35
55
  app = FastAPI(title="opencode token dashboard")
36
56
  # Loopback-only API; restrict origins so a random webpage can't exfiltrate
37
57
  # local project/session data from the browser (the client runs from Vite).
38
58
  app.add_middleware(
39
59
  CORSMiddleware,
40
- allow_origins=[
41
- "http://localhost:5173", "http://127.0.0.1:5173",
42
- "http://localhost:4173", "http://127.0.0.1:4173",
43
- ],
60
+ allow_origins=origins,
44
61
  allow_methods=["*"],
45
62
  allow_headers=["*"],
46
63
  )
@@ -56,14 +73,14 @@ def create_app(runner=None) -> FastAPI:
56
73
 
57
74
  @app.get("/health")
58
75
  def health():
59
- return {"status": "ok", "version": opencode_version()}
76
+ return {"status": "ok", "version": opencode_version(bin)}
60
77
 
61
78
  @app.get("/overview")
62
79
  def overview():
63
80
  def run():
64
81
  data = aggregate.overview(runner)
65
82
  data["host"] = socket.gethostname()
66
- data["opencodeVersion"] = opencode_version()
83
+ data["opencodeVersion"] = opencode_version(bin)
67
84
  return data
68
85
 
69
86
  return handle(run)
@@ -116,7 +133,7 @@ def create_app(runner=None) -> FastAPI:
116
133
  except Exception:
117
134
  logger.exception("stream poll failed")
118
135
  try:
119
- await asyncio.wait_for(stop.wait(), 5)
136
+ await asyncio.wait_for(stop.wait(), poll_seconds)
120
137
  except asyncio.TimeoutError:
121
138
  pass
122
139
 
@@ -0,0 +1,126 @@
1
+ """opencode-dashboard-server CLI: interactive configure + serve.
2
+
3
+ Run: uv tool install opencode-dashboard-server
4
+ opencode-dashboard-server configure
5
+ opencode-dashboard-server serve [--port N] [--host H] [--config PATH]
6
+ """
7
+
8
+ import argparse
9
+ import os
10
+ import sys
11
+ from pathlib import Path
12
+
13
+ import yaml
14
+ from platformdirs import user_config_path
15
+
16
+ from app import create_app
17
+
18
+
19
+ def config_path() -> Path:
20
+ # Force XDG semantics so the backend shares ~/.config/opencode-dashboard
21
+ # with the front end; platformdirs would otherwise use ~/Library/Application
22
+ # Support on macOS. setdefault keeps a user-set XDG_CONFIG_HOME intact.
23
+ os.environ.setdefault("XDG_CONFIG_HOME", str(Path.home() / ".config"))
24
+ return user_config_path("opencode-dashboard") / "server.yaml"
25
+
26
+
27
+ def load_config(path: Path) -> dict:
28
+ if not path.exists():
29
+ return {}
30
+ return yaml.safe_load(path.read_text()) or {}
31
+
32
+
33
+ def _prompt(label: str, current, default) -> str:
34
+ hint = current if current != "" else default
35
+ try:
36
+ value = input(f"{label} [{hint}]: ").strip()
37
+ except EOFError:
38
+ return hint
39
+ return value if value else hint
40
+
41
+
42
+ def _prompt_num(label: str, current, default, conv):
43
+ while True:
44
+ try:
45
+ return conv(_prompt(label, current, default))
46
+ except ValueError:
47
+ print(f"Invalid {label}; enter a number.")
48
+
49
+
50
+ def cmd_configure(args) -> None:
51
+ path = config_path()
52
+ existing = load_config(path)
53
+ cur = {
54
+ "port": existing.get("port", 8791),
55
+ "host": existing.get("host", "0.0.0.0"),
56
+ "cors_origins": existing.get("cors_origins", ""),
57
+ "poll_seconds": existing.get("poll_seconds", 5),
58
+ "opencode_bin": existing.get("opencode_bin", "opencode"),
59
+ }
60
+ if existing:
61
+ print(f"Current config at {path}:")
62
+ print(yaml.safe_dump(cur, sort_keys=False).rstrip())
63
+ print()
64
+ cur["port"] = _prompt_num("port", cur["port"], 8791, int)
65
+ cur["host"] = _prompt("host", cur["host"], "0.0.0.0")
66
+ cur["cors_origins"] = _prompt(
67
+ "cors_origins (comma-separated; empty = built-in loopback whitelist)",
68
+ cur["cors_origins"], "",
69
+ )
70
+ cur["poll_seconds"] = _prompt_num("poll_seconds", cur["poll_seconds"], 5, float)
71
+ cur["opencode_bin"] = _prompt("opencode_bin", cur["opencode_bin"], "opencode")
72
+ path.parent.mkdir(parents=True, exist_ok=True)
73
+ path.write_text(yaml.safe_dump(cur, sort_keys=False))
74
+ print(f"Wrote {path}")
75
+
76
+
77
+ def cmd_serve(args) -> None:
78
+ path = args.config or config_path()
79
+ cfg = load_config(path)
80
+ try:
81
+ port = args.port or int(os.environ.get("PORT") or cfg.get("port") or 8791)
82
+ except ValueError:
83
+ print(f"invalid port: {os.environ.get('PORT') or cfg.get('port')!r}")
84
+ sys.exit(2)
85
+ host = args.host or os.environ.get("HOST") or cfg.get("host") or "0.0.0.0"
86
+
87
+ import uvicorn
88
+
89
+ # cors_origins is stored comma-separated; create_app expects a list.
90
+ cors = [o.strip() for o in str(cfg.get("cors_origins", "")).split(",") if o.strip()]
91
+
92
+ uvicorn.run(
93
+ create_app(
94
+ cors_origins=cors or None,
95
+ poll_seconds=cfg.get("poll_seconds"),
96
+ opencode_bin=cfg.get("opencode_bin") or None,
97
+ ),
98
+ host=host,
99
+ port=port,
100
+ )
101
+
102
+
103
+ def main(argv=None) -> int:
104
+ parser = argparse.ArgumentParser(
105
+ prog="opencode-dashboard-server",
106
+ description="opencode token dashboard aggregation backend",
107
+ )
108
+ sub = parser.add_subparsers(dest="cmd")
109
+ sub.add_parser("configure", help="interactively write the XDG config file")
110
+ serve = sub.add_parser("serve", help="run the server (FastAPI + uvicorn)")
111
+ serve.add_argument("--config", metavar="PATH", help="config file (default: XDG server.yaml)")
112
+ serve.add_argument("--port", type=int, help="listen port")
113
+ serve.add_argument("--host", help="bind host")
114
+ args = parser.parse_args(argv)
115
+ if args.cmd == "configure":
116
+ cmd_configure(args)
117
+ elif args.cmd == "serve":
118
+ cmd_serve(args)
119
+ else:
120
+ parser.print_usage()
121
+ return 2
122
+ return 0
123
+
124
+
125
+ if __name__ == "__main__":
126
+ sys.exit(main())
@@ -11,8 +11,12 @@ Two interchangeable runners over the same SQL surface:
11
11
  """
12
12
 
13
13
  import json
14
+ import os
14
15
  import subprocess
15
16
 
17
+ # opencode CLI binary; override for a non-PATH install (e.g. a brew cellar path).
18
+ OPENCODE_BIN = os.environ.get("OPENCODE_BIN", "opencode")
19
+
16
20
 
17
21
  def _inline(sql: str, params: tuple) -> str:
18
22
  """Inline bound params into SQL as safely-quoted literals (CliRunner has no
@@ -29,8 +33,8 @@ def _inline(sql: str, params: tuple) -> str:
29
33
 
30
34
 
31
35
  class CliRunner:
32
- def __init__(self, executable: str = "opencode"):
33
- self._cmd = [executable, "db"]
36
+ def __init__(self, executable: str | None = None):
37
+ self._cmd = [executable or OPENCODE_BIN, "db"]
34
38
 
35
39
  def query(self, sql: str, params: tuple = ()) -> list[dict]:
36
40
  raw = subprocess.run(
@@ -0,0 +1,84 @@
1
+ Metadata-Version: 2.4
2
+ Name: opencode-dashboard-server
3
+ Version: 0.3.0
4
+ Summary: FastAPI aggregator over opencode's SQLite storage
5
+ Project-URL: Homepage, https://github.com/GCS-ZHN/opencode-dashboard
6
+ Project-URL: Repository, https://github.com/GCS-ZHN/opencode-dashboard
7
+ Project-URL: Issues, https://github.com/GCS-ZHN/opencode-dashboard/issues
8
+ Requires-Python: >=3.10
9
+ Description-Content-Type: text/markdown
10
+ Requires-Dist: fastapi<1,>=0.115
11
+ Requires-Dist: uvicorn<1,>=0.30
12
+ Requires-Dist: platformdirs<5,>=4
13
+ Requires-Dist: PyYAML<7,>=6
14
+
15
+ # opencode-dashboard-server
16
+
17
+ FastAPI aggregation backend for the [opencode-dashboard](https://github.com/GCS-ZHN/opencode-dashboard)
18
+ project. Reads opencode's local SQLite storage via the `opencode db` CLI and exposes a JSON + SSE
19
+ API for the dashboard front end.
20
+
21
+ ## Install (PyPI)
22
+
23
+ ```bash
24
+ pip install opencode-dashboard-server
25
+ # or with uv:
26
+ uv tool install opencode-dashboard-server
27
+ ```
28
+
29
+ Requires:
30
+
31
+ - **Python ≥ 3.10**
32
+ - **`opencode` CLI** on the same host — the server shells out to `opencode db "<SQL>"` and never
33
+ opens the SQLite file directly (it's WAL-mode and actively written while opencode runs).
34
+
35
+ ## Quick start
36
+
37
+ Configure once (interactive; writes `~/.config/opencode-dashboard/server.yaml`), then serve:
38
+
39
+ ```bash
40
+ opencode-dashboard-server configure
41
+ opencode-dashboard-server serve
42
+ ```
43
+
44
+ `configure` walks you through `port` (default `8791`), `host` (default `0.0.0.0`),
45
+ `cors_origins` (comma-separated; empty = built-in loopback whitelist), `poll_seconds`
46
+ (default `5`), and `opencode_bin` (default `opencode`). Empty input keeps the default.
47
+
48
+ `serve` accepts overrides: `--port N`, `--host H`, `--config PATH` (instead of the XDG file).
49
+ Precedence for port/host: CLI flag > env `PORT`/`HOST` > config file > default.
50
+
51
+ Run one aggregator per opencode host:
52
+
53
+ ```bash
54
+ uvicorn app:app --port 8791
55
+ ```
56
+
57
+ or directly with uvicorn (still works — reads env/defaults only). Run on more hosts with
58
+ different ports (`8792`, `8793`, …) and list each in the front end's config (`opencode-dashboard configure`).
59
+ Environment switches (used when no config file value is set):
60
+
61
+ - `DASHBOARD_CORS_ORIGINS` — comma-separated CORS allow-list (defaults to the loopback dev origins `localhost/127.0.0.1:5173` and `:4173`; tighten or widen for your deployment).
62
+ - `DASHBOARD_POLL_SECONDS` — SSE poll interval in seconds (default `5`).
63
+ - `OPENCODE_BIN` — path to the `opencode` binary if it's not on `PATH`.
64
+
65
+ ## API
66
+
67
+ - `GET /health` — liveness
68
+ - `GET /overview` — whole-host aggregate (tokens by type + cost, session counts, `updatedAt`)
69
+ - `GET /projects` — per-project rollups
70
+ - `GET /projects/{id}` — one project's sessions (parent/child tree)
71
+ - `GET /sessions/{id}` — per-model breakdown for one session (handles mid-conversation model switches)
72
+ - `GET /stream` — SSE: `updated` events when the DB changes (polled ~5s)
73
+
74
+ JSON is camelCase, timestamps are epoch-ms. Full contract in the repo's `API.md`.
75
+
76
+ ## From source (development)
77
+
78
+ ```bash
79
+ git clone https://github.com/GCS-ZHN/opencode-dashboard
80
+ cd opencode-dashboard/server
81
+ uv sync # install deps (fastapi, uvicorn) + dev (pytest, httpx)
82
+ uv run pytest # run tests
83
+ uvx ruff check . # lint
84
+ ```
@@ -1,11 +1,13 @@
1
1
  README.md
2
2
  aggregate.py
3
3
  app.py
4
+ cli.py
4
5
  db.py
5
6
  pyproject.toml
6
7
  opencode_dashboard_server.egg-info/PKG-INFO
7
8
  opencode_dashboard_server.egg-info/SOURCES.txt
8
9
  opencode_dashboard_server.egg-info/dependency_links.txt
10
+ opencode_dashboard_server.egg-info/entry_points.txt
9
11
  opencode_dashboard_server.egg-info/requires.txt
10
12
  opencode_dashboard_server.egg-info/top_level.txt
11
13
  tests/test_aggregate.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ opencode-dashboard-server = cli:main
@@ -1,2 +1,4 @@
1
1
  fastapi<1,>=0.115
2
2
  uvicorn<1,>=0.30
3
+ platformdirs<5,>=4
4
+ PyYAML<7,>=6
@@ -4,20 +4,27 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "opencode-dashboard-server"
7
- version = "0.1.1"
7
+ version = "0.3.0"
8
8
  description = "FastAPI aggregator over opencode's SQLite storage"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
11
11
  dependencies = [
12
12
  "fastapi>=0.115,<1",
13
13
  "uvicorn>=0.30,<1",
14
+ "platformdirs>=4,<5",
15
+ "PyYAML>=6,<7",
14
16
  ]
15
17
 
16
18
  [project.urls]
17
19
  Homepage = "https://github.com/GCS-ZHN/opencode-dashboard"
20
+ Repository = "https://github.com/GCS-ZHN/opencode-dashboard"
21
+ Issues = "https://github.com/GCS-ZHN/opencode-dashboard/issues"
22
+
23
+ [project.scripts]
24
+ opencode-dashboard-server = "cli:main"
18
25
 
19
26
  [tool.setuptools]
20
- py-modules = ["app", "aggregate", "db"]
27
+ py-modules = ["app", "aggregate", "db", "cli"]
21
28
 
22
29
  [dependency-groups]
23
30
  dev = [
@@ -46,3 +46,15 @@ def test_foreign_origin_not_allowed_by_cors():
46
46
  "Access-Control-Request-Method": "GET",
47
47
  })
48
48
  assert "access-control-allow-origin" not in r.headers
49
+
50
+
51
+ def test_cors_origins_injection():
52
+ conn = sqlite3.connect(":memory:", check_same_thread=False)
53
+ conn.executescript(SCHEMA)
54
+ seed(conn)
55
+ app = appmod.create_app(SqliteRunner(conn), cors_origins=["http://a.com"])
56
+ c = TestClient(app)
57
+ ok = c.options("/projects", headers={"Origin": "http://a.com", "Access-Control-Request-Method": "GET"})
58
+ assert ok.headers.get("access-control-allow-origin") == "http://a.com"
59
+ nope = c.options("/projects", headers={"Origin": "http://a.co", "Access-Control-Request-Method": "GET"})
60
+ assert "access-control-allow-origin" not in nope.headers
@@ -1,54 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: opencode-dashboard-server
3
- Version: 0.1.1
4
- Summary: FastAPI aggregator over opencode's SQLite storage
5
- Project-URL: Homepage, https://github.com/GCS-ZHN/opencode-dashboard
6
- Requires-Python: >=3.10
7
- Description-Content-Type: text/markdown
8
- Requires-Dist: fastapi<1,>=0.115
9
- Requires-Dist: uvicorn<1,>=0.30
10
-
11
- # opencode-dashboard-server
12
-
13
- FastAPI aggregation backend for the [opencode-dashboard](https://github.com/GCS-ZHN/opencode-dashboard)
14
- project. Reads opencode's local SQLite storage via the `opencode db` CLI and exposes a JSON + SSE
15
- API for the dashboard front end.
16
-
17
- ## Install
18
-
19
- ```bash
20
- pip install opencode-dashboard-server
21
- ```
22
-
23
- Requires Python ≥ 3.10 and the `opencode` CLI on the same host (the server shells out to
24
- `opencode db "<SQL>"` — it never opens the SQLite file directly, which is WAL-mode and actively
25
- written while opencode runs).
26
-
27
- ## Usage
28
-
29
- On each host that runs opencode:
30
-
31
- ```bash
32
- uvicorn app:app --port 8791
33
- ```
34
-
35
- Run on more hosts with different ports (`8792`, `8793`, …) and point the front end at each.
36
-
37
- ## API
38
-
39
- - `GET /health` — liveness
40
- - `GET /overview` — whole-host aggregate (tokens by type + cost, session counts, `updatedAt`)
41
- - `GET /projects` — per-project rollups
42
- - `GET /projects/{id}` — one project's sessions (parent/child tree)
43
- - `GET /sessions/{id}` — per-model breakdown for one session (handles mid-conversation model switches)
44
- - `GET /stream` — SSE: `updated` events when the DB changes (polled ~5s)
45
-
46
- JSON is camelCase, timestamps are epoch-ms. Full contract in the repo's `API.md`.
47
-
48
- ## Development
49
-
50
- ```bash
51
- uv sync # install deps (fastapi, uvicorn) + dev (pytest, httpx)
52
- uv run pytest # run tests
53
- uvx ruff check . # lint
54
- ```
@@ -1,44 +0,0 @@
1
- # opencode-dashboard-server
2
-
3
- FastAPI aggregation backend for the [opencode-dashboard](https://github.com/GCS-ZHN/opencode-dashboard)
4
- project. Reads opencode's local SQLite storage via the `opencode db` CLI and exposes a JSON + SSE
5
- API for the dashboard front end.
6
-
7
- ## Install
8
-
9
- ```bash
10
- pip install opencode-dashboard-server
11
- ```
12
-
13
- Requires Python ≥ 3.10 and the `opencode` CLI on the same host (the server shells out to
14
- `opencode db "<SQL>"` — it never opens the SQLite file directly, which is WAL-mode and actively
15
- written while opencode runs).
16
-
17
- ## Usage
18
-
19
- On each host that runs opencode:
20
-
21
- ```bash
22
- uvicorn app:app --port 8791
23
- ```
24
-
25
- Run on more hosts with different ports (`8792`, `8793`, …) and point the front end at each.
26
-
27
- ## API
28
-
29
- - `GET /health` — liveness
30
- - `GET /overview` — whole-host aggregate (tokens by type + cost, session counts, `updatedAt`)
31
- - `GET /projects` — per-project rollups
32
- - `GET /projects/{id}` — one project's sessions (parent/child tree)
33
- - `GET /sessions/{id}` — per-model breakdown for one session (handles mid-conversation model switches)
34
- - `GET /stream` — SSE: `updated` events when the DB changes (polled ~5s)
35
-
36
- JSON is camelCase, timestamps are epoch-ms. Full contract in the repo's `API.md`.
37
-
38
- ## Development
39
-
40
- ```bash
41
- uv sync # install deps (fastapi, uvicorn) + dev (pytest, httpx)
42
- uv run pytest # run tests
43
- uvx ruff check . # lint
44
- ```
@@ -1,54 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: opencode-dashboard-server
3
- Version: 0.1.1
4
- Summary: FastAPI aggregator over opencode's SQLite storage
5
- Project-URL: Homepage, https://github.com/GCS-ZHN/opencode-dashboard
6
- Requires-Python: >=3.10
7
- Description-Content-Type: text/markdown
8
- Requires-Dist: fastapi<1,>=0.115
9
- Requires-Dist: uvicorn<1,>=0.30
10
-
11
- # opencode-dashboard-server
12
-
13
- FastAPI aggregation backend for the [opencode-dashboard](https://github.com/GCS-ZHN/opencode-dashboard)
14
- project. Reads opencode's local SQLite storage via the `opencode db` CLI and exposes a JSON + SSE
15
- API for the dashboard front end.
16
-
17
- ## Install
18
-
19
- ```bash
20
- pip install opencode-dashboard-server
21
- ```
22
-
23
- Requires Python ≥ 3.10 and the `opencode` CLI on the same host (the server shells out to
24
- `opencode db "<SQL>"` — it never opens the SQLite file directly, which is WAL-mode and actively
25
- written while opencode runs).
26
-
27
- ## Usage
28
-
29
- On each host that runs opencode:
30
-
31
- ```bash
32
- uvicorn app:app --port 8791
33
- ```
34
-
35
- Run on more hosts with different ports (`8792`, `8793`, …) and point the front end at each.
36
-
37
- ## API
38
-
39
- - `GET /health` — liveness
40
- - `GET /overview` — whole-host aggregate (tokens by type + cost, session counts, `updatedAt`)
41
- - `GET /projects` — per-project rollups
42
- - `GET /projects/{id}` — one project's sessions (parent/child tree)
43
- - `GET /sessions/{id}` — per-model breakdown for one session (handles mid-conversation model switches)
44
- - `GET /stream` — SSE: `updated` events when the DB changes (polled ~5s)
45
-
46
- JSON is camelCase, timestamps are epoch-ms. Full contract in the repo's `API.md`.
47
-
48
- ## Development
49
-
50
- ```bash
51
- uv sync # install deps (fastapi, uvicorn) + dev (pytest, httpx)
52
- uv run pytest # run tests
53
- uvx ruff check . # lint
54
- ```