Add `DEVIN_MODE` configuration supporting `enterprise` (default) and `organization_sessions` modes. Organization sessions mode uses `/v3/organizations/{org_id}/sessions` API with `ViewOrgSessions` permission, suitable for non-Enterprise organizations. Sums session ACUs consumed within configurable rolling window (1-365 days via `DEVIN_SESSION_WINDOW_DAYS`). Mode does not show quota, percentage, credit, or reset fields as these are unav
223 lines
12 KiB
Markdown
223 lines
12 KiB
Markdown
# AI Usage Dashboard
|
|
|
|
AI Usage Dashboard is a small FastAPI monitoring kiosk for the official local Codex app-server mechanism and Devin API v3 Enterprise consumption endpoints. Provider refreshes are independent: an unavailable provider never hides a successful result from the other provider.
|
|
|
|
## Requirements
|
|
|
|
- Python 3.12
|
|
- Codex CLI installed and authenticated locally when Codex data is enabled
|
|
- Devin API v3 Enterprise access and a service user with `ViewAccountConsumption` when Devin data is enabled
|
|
- Chromium/Chrome only for the optional kiosk launcher
|
|
- Docker and Compose are optional
|
|
|
|
## Install and run directly
|
|
|
|
Unix:
|
|
|
|
```sh
|
|
python3.12 -m venv .venv
|
|
. .venv/bin/activate
|
|
python -m pip install -r requirements.txt
|
|
cp .env.example .env
|
|
python -m app.main
|
|
```
|
|
|
|
`python -m app.main` is the simplest direct command and honors `HOST` and `PORT` from `.env`. The explicit Uvicorn alternative is:
|
|
|
|
```sh
|
|
python -m uvicorn app.main:app --host 0.0.0.0 --port 8080
|
|
```
|
|
|
|
Windows PowerShell:
|
|
|
|
```powershell
|
|
py -3.12 -m venv .venv
|
|
.\.venv\Scripts\Activate.ps1
|
|
python -m pip install -r requirements.txt
|
|
Copy-Item .env.example .env
|
|
python -m app.main
|
|
```
|
|
|
|
`python -m app.main` honors `HOST` and `PORT` from `.env`. The explicit Uvicorn alternative is:
|
|
|
|
```powershell
|
|
python -m uvicorn app.main:app --host 0.0.0.0 --port 8080
|
|
```
|
|
|
|
Open `http://localhost:8080`. The application also runs in deterministic demo mode with `DEMO_MODE=true` and no provider credentials.
|
|
|
|
## Configuration
|
|
|
|
Copy `.env.example` to `.env`. Unknown environment variables are ignored and names are case-insensitive.
|
|
|
|
| Variable | Default | Meaning |
|
|
| --- | --- | --- |
|
|
| `HOST` | `0.0.0.0` | Bind host when using a launcher that reads settings |
|
|
| `PORT` | `8080` | Application port |
|
|
| `CODEX_ENABLED` | `true` | Enable the local Codex provider |
|
|
| `CODEX_COMMAND` | `codex` | One executable path; it is never shell-split |
|
|
| `CODEX_TIMEOUT` | `8` | Codex request timeout in seconds |
|
|
| `DEVIN_ENABLED` | `true` | Enable Devin |
|
|
| `DEVIN_API_KEY` | empty | Devin Bearer token, kept as a secret value |
|
|
| `DEVIN_API_URL` | `https://api.devin.ai` | Devin API base URL |
|
|
| `DEVIN_MODE` | `enterprise` | `enterprise` billing consumption or `organization_sessions` rolling session usage |
|
|
| `DEVIN_ORG_ID` | empty | Organization scope, required by organization sessions mode |
|
|
| `DEVIN_USER_ID` | empty | Enterprise user scope; optional user filter in organization sessions mode |
|
|
| `DEVIN_SESSION_WINDOW_DAYS` | `30` | Rolling organization-session window from 1 to 365 days |
|
|
| `DEVIN_TIMEOUT` | `8` | Devin HTTP timeout in seconds |
|
|
| `PROVIDER_CACHE_TTL` | `8` | Successful provider cache duration in seconds |
|
|
| `REFRESH_INTERVAL` | `10` | Browser refresh period in seconds |
|
|
| `TIMEZONE` | `Europe/Vienna` | ZoneInfo name used for reset timestamps |
|
|
| `DEMO_MODE` | `false` | Use deterministic local demo values for both cards |
|
|
|
|
## Provider mechanisms
|
|
|
|
### Codex
|
|
|
|
The dashboard invokes the official local CLI exactly as `codex app-server --listen stdio://`, without a shell. It sends the official line-delimited `initialize`, `initialized`, and `account/rateLimits/read` messages and parses primary and secondary windows, credits, and individual limits. Install and log in to the official CLI first:
|
|
|
|
```sh
|
|
codex login
|
|
```
|
|
|
|
The app-server interface is official, but is documented as a development/debug interface and may change. If the executable is not on the service `PATH`, configure `CODEX_COMMAND` with its absolute path. ChatGPT plan rate limits are distinct from OpenAI API billing. `CODEX_API_KEY` is intentionally not used or included because an API key does not expose ChatGPT Codex plan limits.
|
|
|
|
### Devin
|
|
|
|
The provider has two official Devin modes. `enterprise` uses the API v3 Enterprise consumption endpoints: it selects the active billing cycle from `/v3/enterprise/consumption/cycles`, then requests daily consumption using user scope, organization scope, or the account endpoint in that precedence order. It can show true billing-cycle usage and limits when available. Enterprise consumption requires a service user with `ViewAccountConsumption`; reading the organization max limit may require extra organization-read permissions.
|
|
|
|
`organization_sessions` uses `GET /v3/organizations/{org_id}/sessions` with a `cog_` service-user key, `DEVIN_ORG_ID`, and the `ViewOrgSessions` permission. It is suitable for non-Enterprise/self-serve organizations when that endpoint is permitted. The mode sums each returned session's cumulative `acus_consumed` for sessions whose `created_at` falls inside the rolling N-day query. It is not billing-cycle usage, does not apportion a long session's ACUs by date, and cannot show self-serve quota remaining, credit balance, percentage, or reset because Devin does not expose those values through this API. The endpoint's `created_after` filter defines which sessions are included.
|
|
|
|
Example setup:
|
|
|
|
```env
|
|
DEVIN_ENABLED=true
|
|
DEVIN_MODE=organization_sessions
|
|
DEVIN_API_KEY=cog_your_service_user_key
|
|
DEVIN_ORG_ID=org-your-id
|
|
DEVIN_USER_ID=
|
|
DEVIN_SESSION_WINDOW_DAYS=30
|
|
```
|
|
|
|
A successful `200` response from `/v3/organizations/{org_id}/sessions?first=1` confirms that the organization-session endpoint is permitted for the configured service user. Do not claim real-time freshness beyond vendor behavior: the dashboard cache and refresh schedule only control when it asks the vendors again.
|
|
|
|
## Security and behavior
|
|
|
|
- The Devin Bearer token is read from environment settings. Codex uses the official CLI's local credential store instead of an API key setting. Neither credential reaches the browser, and neither is returned, logged, or included in error messages.
|
|
- Upstream error bodies and process stderr are discarded; users see only a generic provider error.
|
|
- Provider calls have independent timeouts and successful results use a monotonic TTL cache. Only successful results are cached.
|
|
- The browser makes one `/api/usage` request immediately and repeats using the response refresh interval. A network failure keeps the previous cards visible and marks the header connection state.
|
|
- `GET /health` returns exactly `{"status":"ok"}`.
|
|
- `GET /api/usage` returns normalized Codex and Devin usage, `server_time`, `demo_mode`, and `refresh_interval`; it sends `Cache-Control: no-store`.
|
|
- `GET /` serves the dashboard HTML with `Cache-Control: no-store`. Static assets are under `/static`.
|
|
|
|
## Docker Compose
|
|
|
|
Create `.env`, then build and run:
|
|
|
|
```sh
|
|
cp .env.example .env
|
|
docker compose up --build -d
|
|
curl http://localhost:8080/health
|
|
```
|
|
|
|
The container uses Python 3.12 slim, a non-root user, read-only root storage, a `/tmp` tmpfs, `no-new-privileges`, and a healthcheck. `docker-compose.yml` uses `restart: unless-stopped` and maps port 8080. The image does not bundle the Codex CLI or a personal Codex credential store: Docker works out of the box for demo mode and Devin, while real Codex should normally run directly on the authenticated host. An operator may deliberately build and securely provision the official CLI and credential store, but credentials should never be baked into an image.
|
|
|
|
## Kiosk mode
|
|
|
|
`start-kiosk.sh` accepts `DASHBOARD_URL` (default `http://localhost:8080`) and searches in this order: `chromium`, `chromium-browser`, `google-chrome`, `google-chrome-stable`. It launches the first available browser with:
|
|
|
|
```text
|
|
--kiosk --noerrdialogs --disable-infobars --disable-session-crashed-bubble
|
|
```
|
|
|
|
The script exits clearly if no supported browser is installed.
|
|
|
|
### systemd dashboard service
|
|
|
|
Install the project at `/opt/usage-kiosk`, create `/opt/usage-kiosk/.env`, and adjust `User` if necessary:
|
|
|
|
```ini
|
|
[Unit]
|
|
Description=AI Usage Dashboard
|
|
After=network-online.target
|
|
Wants=network-online.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
User=usagekiosk
|
|
Group=usagekiosk
|
|
WorkingDirectory=/opt/usage-kiosk
|
|
EnvironmentFile=/opt/usage-kiosk/.env
|
|
ExecStart=/opt/usage-kiosk/.venv/bin/uvicorn app.main:app --host 0.0.0.0 --port 8080
|
|
Restart=on-failure
|
|
RestartSec=5
|
|
NoNewPrivileges=true
|
|
PrivateTmp=true
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
```
|
|
|
|
Save as `/etc/systemd/system/usage-kiosk.service`, then run `sudo systemctl daemon-reload`, `sudo systemctl enable --now usage-kiosk`, and inspect `sudo journalctl -u usage-kiosk` if needed.
|
|
|
|
### systemd browser launch
|
|
|
|
For systems where the display manager and X authority are ready before the browser unit starts, save this as `/etc/systemd/system/usage-kiosk-browser.service`:
|
|
|
|
```ini
|
|
[Unit]
|
|
Description=AI Usage Dashboard Chromium Kiosk
|
|
After=display-manager.service usage-kiosk.service
|
|
Requires=usage-kiosk.service
|
|
|
|
[Service]
|
|
Type=simple
|
|
User=usagekiosk
|
|
WorkingDirectory=/opt/usage-kiosk
|
|
Environment=DISPLAY=:0
|
|
Environment=XAUTHORITY=/home/usagekiosk/.Xauthority
|
|
ExecStart=/opt/usage-kiosk/start-kiosk.sh
|
|
Restart=on-failure
|
|
RestartSec=5
|
|
|
|
[Install]
|
|
WantedBy=graphical.target
|
|
```
|
|
|
|
Enable it with `sudo systemctl daemon-reload` and `sudo systemctl enable --now usage-kiosk-browser`. The `DISPLAY`, `XAUTHORITY`, user home, and display-manager readiness vary by distribution and graphical setup; a desktop-session autostart entry is preferable when systemd starts too early for the X session.
|
|
|
|
### Kiosk X session/autostart
|
|
|
|
For a dedicated graphical user, install the launcher at `/opt/usage-kiosk/start-kiosk.sh`, make it executable, and use an X session autostart entry such as `~/.config/autostart/usage-kiosk.desktop`:
|
|
|
|
```ini
|
|
[Desktop Entry]
|
|
Type=Application
|
|
Name=Usage Dashboard Kiosk
|
|
Exec=/opt/usage-kiosk/start-kiosk.sh
|
|
Terminal=false
|
|
X-GNOME-Autostart-enabled=true
|
|
```
|
|
|
|
A window-manager session may instead run `/opt/usage-kiosk/start-kiosk.sh` from its session startup. Configure automatic graphical login for the kiosk user only on a physically secured display. Boot ordering is: network, `usage-kiosk.service`, graphical session, then Chromium kiosk. Keep the dashboard service bound to the required network interfaces and use a firewall or reverse proxy when it is not on a trusted display network.
|
|
|
|
## Troubleshooting
|
|
|
|
- **Codex error:** verify `codex` is installed, `codex login` completed, and `CODEX_COMMAND` points to one executable. Run the CLI manually outside the dashboard to confirm its availability.
|
|
- **Devin Enterprise error:** verify Enterprise entitlement, service-user `ViewAccountConsumption` permission, token scope, API URL, and configured user or organization scope. A 401/403/404 on optional organization metadata only removes the max-limit fields.
|
|
- **Devin organization sessions 403:** verify that `DEVIN_MODE=organization_sessions`, `DEVIN_ORG_ID`, the `cog_` service-user key, and `ViewOrgSessions` are correct. Confirm access with `/v3/organizations/{org_id}/sessions?first=1`; this mode does not use Enterprise consumption permissions.
|
|
- **No Devin limit:** Enterprise consumption remains valid, but no numeric limit is invented when the organization max limit is unavailable. Organization sessions mode intentionally has no quota, percentage, credit, or reset fields.
|
|
- **Connection error in the header:** the browser could not complete the dashboard request; existing provider rows remain on screen and retries continue on the configured interval.
|
|
- **Stale-looking values:** successful provider results are cached for `PROVIDER_CACHE_TTL`; vendor processing and reporting delays are outside this dashboard's control.
|
|
- **Container health failure:** inspect `docker compose logs dashboard`, verify `.env`, and test `http://localhost:8080/health`.
|
|
- **Kiosk failure:** confirm one of the four supported Chromium executable names is on `PATH`, then check `DASHBOARD_URL` and the graphical session environment.
|
|
|
|
## Development checks
|
|
|
|
```sh
|
|
python -m pytest -q
|
|
python -m compileall app
|
|
docker compose config
|
|
```
|