Compare commits
19 Commits
codex/issu
...
codex/issu
| Author | SHA1 | Date | |
|---|---|---|---|
| 6d78c119c0 | |||
| 374340d71a | |||
| 28c5e7955a | |||
| 5c4bf80eb0 | |||
| e0e408d1eb | |||
| c159c83a07 | |||
|
|
a1d415e449 | ||
|
|
0f5f9c5f91 | ||
| 096544f6e6 | |||
|
|
5a3dbc6252 | ||
| 9f2083a324 | |||
| dd08ecaf27 | |||
|
|
bc354e7bc5 | ||
| 1c2b48f588 | |||
|
|
a590bf62c2 | ||
| 6a9918bc98 | |||
|
|
e4834cd3cd | ||
|
|
c102017b16 | ||
|
|
2025ae09db |
@@ -18,7 +18,7 @@ Production-ready Crucix fork for Docker, Dockge, Pangolin, local OSINT sweeps, s
|
|||||||
- `npm run test:unit`
|
- `npm run test:unit`
|
||||||
- `npm test`
|
- `npm test`
|
||||||
- `docker compose config`
|
- `docker compose config`
|
||||||
- `docker build -t git.wilkensxl.de/mrsphay/intelligence-terminal:latest .`
|
- `docker build -t git.wilkensxl.de/code-inc/intelligence-terminal:latest .`
|
||||||
|
|
||||||
Heavy install/build/audit/release work should run on Gitea Ubuntu runners where possible. Local work should stay limited to targeted verification and Docker checks required for this deployment.
|
Heavy install/build/audit/release work should run on Gitea Ubuntu runners where possible. Local work should stay limited to targeted verification and Docker checks required for this deployment.
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ TERMINAL_ACTION_RATE_LIMIT_MAX=10
|
|||||||
BRIEF_VERBOSITY=standard
|
BRIEF_VERBOSITY=standard
|
||||||
|
|
||||||
# LLM layer
|
# LLM layer
|
||||||
# Providers: openrouter | openai-compatible | lmstudio | ollama | openai | anthropic | gemini | mistral | minimax | grok | codex
|
# Providers: litellm | openrouter | openai-compatible | lmstudio | ollama | openai | anthropic | gemini | mistral | minimax | grok | codex
|
||||||
LLM_PROVIDER=openrouter
|
LLM_PROVIDER=openrouter
|
||||||
LLM_BASE_URL=https://openrouter.ai/api/v1
|
LLM_BASE_URL=https://openrouter.ai/api/v1
|
||||||
LLM_API_KEY=
|
LLM_API_KEY=
|
||||||
@@ -24,10 +24,11 @@ LLM_MODEL=openrouter/free
|
|||||||
LLM_TEMPERATURE=0.2
|
LLM_TEMPERATURE=0.2
|
||||||
LLM_MAX_TOKENS=2000
|
LLM_MAX_TOKENS=2000
|
||||||
LLM_TIMEOUT_MS=90000
|
LLM_TIMEOUT_MS=90000
|
||||||
OPENROUTER_SITE_URL=https://git.wilkensxl.de/MrSphay/intelligence-terminal
|
OPENROUTER_SITE_URL=https://git.wilkensxl.de/Code-Inc/intelligence-terminal
|
||||||
OPENROUTER_APP_NAME=Intelligence Terminal
|
OPENROUTER_APP_NAME=Intelligence Terminal
|
||||||
|
|
||||||
# Local OpenAI-compatible examples
|
# Local OpenAI-compatible examples
|
||||||
|
# LiteLLM: LLM_PROVIDER=litellm, LLM_BASE_URL=https://llm.example.com/v1, LLM_API_KEY=your-proxy-key, LLM_MODEL=your-model-alias
|
||||||
# LM Studio: LLM_PROVIDER=lmstudio, LLM_BASE_URL=http://host.docker.internal:1234/v1, LLM_MODEL=local-model
|
# LM Studio: LLM_PROVIDER=lmstudio, LLM_BASE_URL=http://host.docker.internal:1234/v1, LLM_MODEL=local-model
|
||||||
# Ollama: LLM_PROVIDER=ollama, LLM_BASE_URL=http://host.docker.internal:11434, LLM_MODEL=llama3.1:8b
|
# Ollama: LLM_PROVIDER=ollama, LLM_BASE_URL=http://host.docker.internal:11434, LLM_MODEL=llama3.1:8b
|
||||||
# Generic: LLM_PROVIDER=openai-compatible, LLM_BASE_URL=http://host.docker.internal:8000/v1, LLM_MODEL=your-model
|
# Generic: LLM_PROVIDER=openai-compatible, LLM_BASE_URL=http://host.docker.internal:8000/v1, LLM_MODEL=your-model
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
REGISTRY_HOST: git.wilkensxl.de
|
REGISTRY_HOST: git.wilkensxl.de
|
||||||
REGISTRY_USERNAME: MrSphay
|
REGISTRY_USERNAME: MrSphay
|
||||||
REGISTRY_NAMESPACE: mrsphay
|
REGISTRY_NAMESPACE: code-inc
|
||||||
IMAGE_NAME: intelligence-terminal
|
IMAGE_NAME: intelligence-terminal
|
||||||
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
||||||
steps:
|
steps:
|
||||||
@@ -46,7 +46,7 @@ jobs:
|
|||||||
docker build -t "${image}:${build_tag}" .
|
docker build -t "${image}:${build_tag}" .
|
||||||
|
|
||||||
- name: Publish Docker image
|
- name: Publish Docker image
|
||||||
if: ${{ env.REGISTRY_TOKEN != '' }}
|
if: ${{ env.REGISTRY_TOKEN != '' && github.event_name == 'push' && github.ref == 'refs/heads/codex/production-intelligence-terminal' }}
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
image="${REGISTRY_HOST}/${REGISTRY_NAMESPACE}/${IMAGE_NAME}"
|
image="${REGISTRY_HOST}/${REGISTRY_NAMESPACE}/${IMAGE_NAME}"
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ Intelligence Terminal is a Docker-first Crucix fork for home-server OSINT, marke
|
|||||||
- Unit tests: `npm run test:unit`
|
- Unit tests: `npm run test:unit`
|
||||||
- Full tests: `npm test`
|
- Full tests: `npm test`
|
||||||
- Compose validation: `docker compose config`
|
- Compose validation: `docker compose config`
|
||||||
- Docker image: `docker build -t git.wilkensxl.de/mrsphay/intelligence-terminal:latest .`
|
- Docker image: `docker build -t git.wilkensxl.de/code-inc/intelligence-terminal:latest .`
|
||||||
|
|
||||||
## Release Target
|
## Release Target
|
||||||
|
|
||||||
Push source to `https://git.wilkensxl.de/MrSphay/intelligence-terminal.git` and publish the Docker image to `git.wilkensxl.de/mrsphay/intelligence-terminal`.
|
Push source to `https://git.wilkensxl.de/Code-Inc/intelligence-terminal.git` and publish the Docker image to `git.wilkensxl.de/code-inc/intelligence-terminal`.
|
||||||
|
|||||||
71
README.md
71
README.md
@@ -4,8 +4,8 @@
|
|||||||
|
|
||||||
**Self-hosted intelligence dashboard. 27 open sources. Docker-first. No telemetry.**
|
**Self-hosted intelligence dashboard. 27 open sources. Docker-first. No telemetry.**
|
||||||
|
|
||||||
[](https://git.wilkensxl.de/MrSphay/intelligence-terminal)
|
[](https://git.wilkensxl.de/Code-Inc/intelligence-terminal)
|
||||||
[](https://git.wilkensxl.de/MrSphay/-/packages/container/intelligence-terminal/latest)
|
[](https://git.wilkensxl.de/Code-Inc/-/packages/container/intelligence-terminal/latest)
|
||||||
|
|
||||||
[](#quick-start)
|
[](#quick-start)
|
||||||
[](LICENSE)
|
[](LICENSE)
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
|
|
||||||
> **Supported deployment:** private home-server or lab deployment through Docker, Dockge, Pangolin, or local Node.js.
|
> **Supported deployment:** private home-server or lab deployment through Docker, Dockge, Pangolin, or local Node.js.
|
||||||
> Runtime data stays in your configured `runs/` volume and API keys are operator-owned.
|
> Runtime data stays in your configured `runs/` volume and API keys are operator-owned.
|
||||||
> **Source:** [git.wilkensxl.de/MrSphay/intelligence-terminal](https://git.wilkensxl.de/MrSphay/intelligence-terminal)
|
> **Source:** [git.wilkensxl.de/Code-Inc/intelligence-terminal](https://git.wilkensxl.de/Code-Inc/intelligence-terminal)
|
||||||
> Pull the image or clone the repository to run Intelligence Terminal on your own infrastructure.
|
> Pull the image or clone the repository to run Intelligence Terminal on your own infrastructure.
|
||||||
|
|
||||||
Intelligence Terminal pulls satellite fire detection, flight tracking, radiation monitoring, satellite constellation tracking, economic indicators, live market prices, conflict data, sanctions lists, and social sentiment from 27 open-source intelligence feeds in parallel, every 15 minutes, and renders everything on a single self-contained dashboard.
|
Intelligence Terminal pulls satellite fire detection, flight tracking, radiation monitoring, satellite constellation tracking, economic indicators, live market prices, conflict data, sanctions lists, and social sentiment from 27 open-source intelligence feeds in parallel, every 15 minutes, and renders everything on a single self-contained dashboard.
|
||||||
@@ -66,7 +66,7 @@ It was built for anyone who wants to understand what's actually happening in the
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 1. Clone the repo
|
# 1. Clone the repo
|
||||||
git clone https://git.wilkensxl.de/MrSphay/intelligence-terminal.git
|
git clone https://git.wilkensxl.de/Code-Inc/intelligence-terminal.git
|
||||||
cd intelligence-terminal
|
cd intelligence-terminal
|
||||||
|
|
||||||
# 2. Install dependencies (just Express)
|
# 2. Install dependencies (just Express)
|
||||||
@@ -85,14 +85,14 @@ npm run dev
|
|||||||
> ```
|
> ```
|
||||||
> This bypasses npm's script runner, which can swallow errors on some systems (particularly PowerShell on Windows). You can also run `node diag.mjs` to diagnose the exact issue — it checks your Node version, tests each module import individually, and verifies port availability. See [Troubleshooting](#troubleshooting) for more.
|
> This bypasses npm's script runner, which can swallow errors on some systems (particularly PowerShell on Windows). You can also run `node diag.mjs` to diagnose the exact issue — it checks your Node version, tests each module import individually, and verifies port availability. See [Troubleshooting](#troubleshooting) for more.
|
||||||
|
|
||||||
The dashboard opens automatically at `http://localhost:3117` and immediately begins its first intelligence sweep. This initial sweep queries all 27 sources in parallel and typically takes 30–60 seconds — the dashboard will appear empty until the sweep completes and pushes the first data update. After that, it auto-refreshes every 15 minutes via SSE (Server-Sent Events). No manual page refresh needed.
|
The server starts at `http://localhost:3117` and immediately begins its first intelligence sweep. Browser auto-open is disabled by default; open the URL yourself or explicitly set `AUTO_OPEN_BROWSER=true` for a supported desktop environment. The initial sweep queries all 27 sources in parallel and typically takes 30–60 seconds — the dashboard will appear empty until the sweep completes and pushes the first data update. After that, it auto-refreshes every 15 minutes via SSE (Server-Sent Events). No manual page refresh is needed.
|
||||||
|
|
||||||
**Requirements:** Node.js 22+ (uses native `fetch`, top-level `await`, ESM)
|
**Requirements:** Node.js 22+ (uses native `fetch`, top-level `await`, ESM)
|
||||||
|
|
||||||
### Docker
|
### Docker
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker pull git.wilkensxl.de/mrsphay/intelligence-terminal:latest
|
docker pull git.wilkensxl.de/code-inc/intelligence-terminal:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
Dashboard at `http://localhost:3117`. Sweep data persists in `./runs/` via volume mount. The container disables browser auto-open by default, exposes `/api/health` and `/api/metrics`, and is suitable for Dockge/Pangolin.
|
Dashboard at `http://localhost:3117`. Sweep data persists in `./runs/` via volume mount. The container disables browser auto-open by default, exposes `/api/health` and `/api/metrics`, and is suitable for Dockge/Pangolin.
|
||||||
@@ -102,7 +102,7 @@ Dashboard at `http://localhost:3117`. Sweep data persists in `./runs/` via volum
|
|||||||
```yaml
|
```yaml
|
||||||
services:
|
services:
|
||||||
intelligence-terminal:
|
intelligence-terminal:
|
||||||
image: git.wilkensxl.de/mrsphay/intelligence-terminal:latest
|
image: git.wilkensxl.de/code-inc/intelligence-terminal:latest
|
||||||
container_name: intelligence-terminal
|
container_name: intelligence-terminal
|
||||||
env_file:
|
env_file:
|
||||||
- path: .env
|
- path: .env
|
||||||
@@ -146,7 +146,7 @@ LLM_MODEL=openrouter/free
|
|||||||
LLM_TEMPERATURE=0.2
|
LLM_TEMPERATURE=0.2
|
||||||
LLM_MAX_TOKENS=2000
|
LLM_MAX_TOKENS=2000
|
||||||
LLM_TIMEOUT_MS=90000
|
LLM_TIMEOUT_MS=90000
|
||||||
OPENROUTER_SITE_URL=https://git.wilkensxl.de/MrSphay/intelligence-terminal
|
OPENROUTER_SITE_URL=https://git.wilkensxl.de/Code-Inc/intelligence-terminal
|
||||||
OPENROUTER_APP_NAME=Intelligence Terminal
|
OPENROUTER_APP_NAME=Intelligence Terminal
|
||||||
|
|
||||||
FRED_API_KEY=
|
FRED_API_KEY=
|
||||||
@@ -157,9 +157,13 @@ ACLED_EMAIL=
|
|||||||
ACLED_PASSWORD=
|
ACLED_PASSWORD=
|
||||||
CLOUDFLARE_API_TOKEN=
|
CLOUDFLARE_API_TOKEN=
|
||||||
BLS_API_KEY=
|
BLS_API_KEY=
|
||||||
|
REDDIT_CLIENT_ID=
|
||||||
|
REDDIT_CLIENT_SECRET=
|
||||||
|
|
||||||
TELEGRAM_BOT_TOKEN=
|
TELEGRAM_BOT_TOKEN=
|
||||||
TELEGRAM_CHAT_ID=
|
TELEGRAM_CHAT_ID=
|
||||||
|
TELEGRAM_POLL_INTERVAL=5000
|
||||||
|
TELEGRAM_CHANNELS=
|
||||||
DISCORD_BOT_TOKEN=
|
DISCORD_BOT_TOKEN=
|
||||||
DISCORD_CHANNEL_ID=
|
DISCORD_CHANNEL_ID=
|
||||||
DISCORD_GUILD_ID=
|
DISCORD_GUILD_ID=
|
||||||
@@ -169,6 +173,12 @@ DISCORD_WEBHOOK_URL=
|
|||||||
Local LLM examples:
|
Local LLM examples:
|
||||||
|
|
||||||
```env
|
```env
|
||||||
|
# LiteLLM proxy (the URL must include the OpenAI-compatible /v1 path)
|
||||||
|
LLM_PROVIDER=litellm
|
||||||
|
LLM_BASE_URL=https://llm.example.com/v1
|
||||||
|
LLM_API_KEY=your-litellm-api-key
|
||||||
|
LLM_MODEL=your-model-alias
|
||||||
|
|
||||||
# LM Studio
|
# LM Studio
|
||||||
LLM_PROVIDER=lmstudio
|
LLM_PROVIDER=lmstudio
|
||||||
LLM_BASE_URL=http://host.docker.internal:1234/v1
|
LLM_BASE_URL=http://host.docker.internal:1234/v1
|
||||||
@@ -249,6 +259,8 @@ Recommended proxy settings:
|
|||||||
|
|
||||||
If you raise the heartbeat interval, keep it shorter than the lowest idle timeout in the proxy chain.
|
If you raise the heartbeat interval, keep it shorter than the lowest idle timeout in the proxy chain.
|
||||||
|
|
||||||
|
`/api/metrics` includes network health grouped by host and source/provider. Source modules should use `safeFetch(url, { source: 'SourceName' })`; when omitted, the shared helper infers a stable provider bucket from the URL host instead of grouping normal source traffic under `unknown`. Raw fetch exceptions are documented in [Source Fetch Instrumentation](docs/source-fetch-instrumentation.md).
|
||||||
|
|
||||||
#### Scenario Watchlist
|
#### Scenario Watchlist
|
||||||
|
|
||||||
Intelligence Terminal can track operator hypotheses across sweeps with a runtime scenario file at `runs/scenarios.json`. On first run, the server creates three disabled starter examples:
|
Intelligence Terminal can track operator hypotheses across sweeps with a runtime scenario file at `runs/scenarios.json`. On first run, the server creates three disabled starter examples:
|
||||||
@@ -286,10 +298,10 @@ Scenario states are `dormant`, `watching`, `building`, and `confirmed`. The dash
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker login git.wilkensxl.de -u MrSphay
|
docker login git.wilkensxl.de -u MrSphay
|
||||||
docker build -t git.wilkensxl.de/mrsphay/intelligence-terminal:latest .
|
docker build -t git.wilkensxl.de/code-inc/intelligence-terminal:latest .
|
||||||
docker tag git.wilkensxl.de/mrsphay/intelligence-terminal:latest git.wilkensxl.de/mrsphay/intelligence-terminal:20260516
|
docker tag git.wilkensxl.de/code-inc/intelligence-terminal:latest git.wilkensxl.de/code-inc/intelligence-terminal:YYYYMMDD
|
||||||
docker push git.wilkensxl.de/mrsphay/intelligence-terminal:latest
|
docker push git.wilkensxl.de/code-inc/intelligence-terminal:latest
|
||||||
docker push git.wilkensxl.de/mrsphay/intelligence-terminal:20260516
|
docker push git.wilkensxl.de/code-inc/intelligence-terminal:YYYYMMDD
|
||||||
```
|
```
|
||||||
|
|
||||||
Gitea Actions publishes the same image automatically when the repository secret `REGISTRY_TOKEN` is set with package read/write permissions. The workflow tags images as `latest`, the commit SHA, and a UTC `YYYYMMDD` release tag.
|
Gitea Actions publishes the same image automatically when the repository secret `REGISTRY_TOKEN` is set with package read/write permissions. The workflow tags images as `latest`, the commit SHA, and a UTC `YYYYMMDD` release tag.
|
||||||
@@ -378,7 +390,7 @@ Alerts are delivered as rich embeds with color-coded sidebars: red for FLASH, ye
|
|||||||
Connect cloud or local OpenAI-compatible LLM providers for enhanced analysis:
|
Connect cloud or local OpenAI-compatible LLM providers for enhanced analysis:
|
||||||
- **AI trade ideas** — quantitative analyst producing 5-8 actionable ideas citing specific data
|
- **AI trade ideas** — quantitative analyst producing 5-8 actionable ideas citing specific data
|
||||||
- **Smarter alert evaluation** — LLM classifies signals into FLASH/PRIORITY/ROUTINE tiers with cross-domain correlation and confidence scoring
|
- **Smarter alert evaluation** — LLM classifies signals into FLASH/PRIORITY/ROUTINE tiers with cross-domain correlation and confidence scoring
|
||||||
- Providers: OpenRouter, OpenAI-compatible APIs, LM Studio, Ollama, OpenAI, Anthropic Claude, Google Gemini, OpenAI Codex, MiniMax, Mistral, Grok
|
- Providers: LiteLLM, OpenRouter, OpenAI-compatible APIs, LM Studio, Ollama, OpenAI, Anthropic Claude, Google Gemini, OpenAI Codex, MiniMax, Mistral, Grok
|
||||||
- Graceful fallback — when LLM is unavailable, a rule-based engine takes over alert evaluation. LLM failures never crash the sweep cycle.
|
- Graceful fallback — when LLM is unavailable, a rule-based engine takes over alert evaluation. LLM failures never crash the sweep cycle.
|
||||||
|
|
||||||
Primary env keys:
|
Primary env keys:
|
||||||
@@ -428,14 +440,18 @@ Reddit is OAuth-only in this fork. If the Reddit credentials are missing or reje
|
|||||||
|
|
||||||
### LLM Provider (optional, for AI-enhanced ideas)
|
### LLM Provider (optional, for AI-enhanced ideas)
|
||||||
|
|
||||||
Set `LLM_PROVIDER` to one of: `anthropic`, `openai`, `gemini`, `codex`, `openrouter`, `minimax`, `mistral`, `grok`
|
Set `LLM_PROVIDER` to one of: `litellm`, `openrouter`, `openai-compatible`, `lmstudio`, `ollama`, `anthropic`, `openai`, `gemini`, `codex`, `minimax`, `mistral`, or `grok`.
|
||||||
|
|
||||||
| Provider | Key Required | Default Model |
|
| Provider | Key Required | Default Model / Requirement |
|
||||||
|----------|-------------|---------------|
|
|----------|--------------|-----------------------------|
|
||||||
|
| `litellm` | `LLM_API_KEY` | Explicit `LLM_BASE_URL` and `LLM_MODEL` required |
|
||||||
|
| `openrouter` | `LLM_API_KEY` | `openrouter/free` |
|
||||||
|
| `openai-compatible` | Endpoint-dependent | `local-model`; set `LLM_BASE_URL` |
|
||||||
|
| `lmstudio` | No | `local-model` |
|
||||||
|
| `ollama` | No | `llama3.1:8b` |
|
||||||
| `anthropic` | `LLM_API_KEY` | claude-sonnet-4-6 |
|
| `anthropic` | `LLM_API_KEY` | claude-sonnet-4-6 |
|
||||||
| `openai` | `LLM_API_KEY` | gpt-5.4 |
|
| `openai` | `LLM_API_KEY` | `gpt-4o-mini` |
|
||||||
| `gemini` | `LLM_API_KEY` | gemini-3.1-pro |
|
| `gemini` | `LLM_API_KEY` | gemini-3.1-pro |
|
||||||
| `openrouter` | `LLM_API_KEY` | openrouter/auto |
|
|
||||||
| `codex` | None (uses `~/.codex/auth.json`) | gpt-5.3-codex |
|
| `codex` | None (uses `~/.codex/auth.json`) | gpt-5.3-codex |
|
||||||
| `minimax` | `LLM_API_KEY` | MiniMax-M2.5 |
|
| `minimax` | `LLM_API_KEY` | MiniMax-M2.5 |
|
||||||
| `mistral` | `LLM_API_KEY` | mistral-large-latest |
|
| `mistral` | `LLM_API_KEY` | mistral-large-latest |
|
||||||
@@ -623,9 +639,22 @@ All settings are in `.env` with sensible defaults:
|
|||||||
| `STALE_DATA_MAX_AGE_MINUTES` | `60` | Data age threshold for stale health state |
|
| `STALE_DATA_MAX_AGE_MINUTES` | `60` | Data age threshold for stale health state |
|
||||||
| `STALE_ALERT_COOLDOWN_MINUTES` | `60` | Minimum time between repeated operator stale-data alerts |
|
| `STALE_ALERT_COOLDOWN_MINUTES` | `60` | Minimum time between repeated operator stale-data alerts |
|
||||||
| `DASHBOARD_URL` | local URL | Dashboard URL included in operator alerts |
|
| `DASHBOARD_URL` | local URL | Dashboard URL included in operator alerts |
|
||||||
| `LLM_PROVIDER` | disabled | `anthropic`, `openai`, `gemini`, `codex`, `openrouter`, `minimax`, `mistral`, or `grok` |
|
| `AUTO_OPEN_BROWSER` | `false` | Open the dashboard in a host browser; keep disabled in Docker |
|
||||||
| `LLM_API_KEY` | — | API key (not needed for codex) |
|
| `TERMINAL_ACTIONS_ENABLED` | environment-dependent | Enable guarded dashboard actions such as sweep and brief |
|
||||||
|
| `SWEEP_TOKEN` | disabled | Shared token required for remote action requests |
|
||||||
|
| `SSE_HEARTBEAT_INTERVAL_MS` | `25000` | Heartbeat interval for reverse-proxy SSE connections |
|
||||||
|
| `TERMINAL_ACTION_RATE_LIMIT_WINDOW_MS` | `60000` | Terminal action rate-limit window |
|
||||||
|
| `TERMINAL_ACTION_RATE_LIMIT_MAX` | `10` | Maximum terminal actions per client/window |
|
||||||
|
| `BRIEF_VERBOSITY` | `standard` | Briefing detail level |
|
||||||
|
| `LLM_PROVIDER` | disabled | `litellm`, `openrouter`, `openai-compatible`, `lmstudio`, `ollama`, `anthropic`, `openai`, `gemini`, `codex`, `minimax`, `mistral`, or `grok` |
|
||||||
|
| `LLM_BASE_URL` | provider default | API base URL; required for LiteLLM and custom endpoints |
|
||||||
|
| `LLM_API_KEY` | — | Provider or proxy API key; required for LiteLLM |
|
||||||
| `LLM_MODEL` | per-provider default | Override model selection |
|
| `LLM_MODEL` | per-provider default | Override model selection |
|
||||||
|
| `LLM_TEMPERATURE` | `0.2` | Sampling temperature for OpenAI-compatible providers |
|
||||||
|
| `LLM_MAX_TOKENS` | `2000` | Maximum completion token budget |
|
||||||
|
| `LLM_TIMEOUT_MS` | `90000` | LLM request timeout in milliseconds |
|
||||||
|
| `OPENROUTER_SITE_URL` | repository URL | OpenRouter attribution URL |
|
||||||
|
| `OPENROUTER_APP_NAME` | `Intelligence Terminal` | OpenRouter application title |
|
||||||
| `TELEGRAM_BOT_TOKEN` | disabled | For Telegram alerts + bot commands |
|
| `TELEGRAM_BOT_TOKEN` | disabled | For Telegram alerts + bot commands |
|
||||||
| `TELEGRAM_CHAT_ID` | — | Your Telegram chat ID |
|
| `TELEGRAM_CHAT_ID` | — | Your Telegram chat ID |
|
||||||
| `TELEGRAM_CHANNELS` | — | Extra channel IDs to monitor (comma-separated) |
|
| `TELEGRAM_CHANNELS` | — | Extra channel IDs to monitor (comma-separated) |
|
||||||
@@ -749,7 +778,7 @@ For contribution guidelines, review expectations, and source-add rules, see `CON
|
|||||||
|
|
||||||
For bugs, feature requests, and integration ideas, use the Gitea issue tracker so discussion stays visible and actionable:
|
For bugs, feature requests, and integration ideas, use the Gitea issue tracker so discussion stays visible and actionable:
|
||||||
|
|
||||||
https://git.wilkensxl.de/MrSphay/intelligence-terminal/issues
|
https://git.wilkensxl.de/Code-Inc/intelligence-terminal/issues
|
||||||
|
|
||||||
## Upstream And License
|
## Upstream And License
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,44 @@ const fetchMetrics = {
|
|||||||
recent: [],
|
recent: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const SOURCE_BY_HOST = [
|
||||||
|
[/api\.bls\.gov$/i, 'BLS'],
|
||||||
|
[/api\.fred\.stlouisfed\.org$/i, 'FRED'],
|
||||||
|
[/api\.eia\.gov$/i, 'EIA'],
|
||||||
|
[/api\.gdeltproject\.org$/i, 'GDELT'],
|
||||||
|
[/api\.weather\.gov$/i, 'NOAA'],
|
||||||
|
[/api\.open-notify\.org$/i, 'OpenNotify'],
|
||||||
|
[/opensky-network\.org$/i, 'OpenSky'],
|
||||||
|
[/firms\.modaps\.eosdis\.nasa\.gov$/i, 'FIRMS'],
|
||||||
|
[/api\.acleddata\.com$/i, 'ACLED'],
|
||||||
|
[/api\.reliefweb\.int$/i, 'ReliefWeb'],
|
||||||
|
[/receiverbook\.de$/i, 'KiwiSDR'],
|
||||||
|
[/safecast\.org$/i, 'Safecast'],
|
||||||
|
[/api\.patentsview\.org$/i, 'PatentsView'],
|
||||||
|
[/api\.trade\.gov$/i, 'Comtrade'],
|
||||||
|
[/api\.usaspending\.gov$/i, 'USASpending'],
|
||||||
|
[/api\.telegram\.org$/i, 'Telegram'],
|
||||||
|
[/oauth\.reddit\.com$/i, 'Reddit'],
|
||||||
|
[/reddit\.com$/i, 'Reddit'],
|
||||||
|
[/api\.bsky\.app$/i, 'Bluesky'],
|
||||||
|
[/api\.yahoo\.com$/i, 'YahooFinance'],
|
||||||
|
[/query\d?\.finance\.yahoo\.com$/i, 'YahooFinance'],
|
||||||
|
[/api\.cloudflare\.com$/i, 'CloudflareRadar'],
|
||||||
|
[/api\.opensanctions\.org$/i, 'OpenSanctions'],
|
||||||
|
[/home\.treasury\.gov$/i, 'Treasury'],
|
||||||
|
[/fiscaldata\.treasury\.gov$/i, 'Treasury'],
|
||||||
|
[/who\.int$/i, 'WHO'],
|
||||||
|
];
|
||||||
|
|
||||||
|
export function inferFetchSource(url) {
|
||||||
|
let host = 'unknown';
|
||||||
|
try { host = new URL(url).host.toLowerCase(); } catch { return 'unknown'; }
|
||||||
|
for (const [pattern, source] of SOURCE_BY_HOST) {
|
||||||
|
if (pattern.test(host)) return source;
|
||||||
|
}
|
||||||
|
return host;
|
||||||
|
}
|
||||||
|
|
||||||
function metricBucket(map, key) {
|
function metricBucket(map, key) {
|
||||||
if (!map[key]) map[key] = { requests: 0, ok: 0, failed: 0, bytes: 0, lastStatus: null, lastError: null, lastMs: 0 };
|
if (!map[key]) map[key] = { requests: 0, ok: 0, failed: 0, bytes: 0, lastStatus: null, lastError: null, lastMs: 0 };
|
||||||
return map[key];
|
return map[key];
|
||||||
@@ -38,7 +76,7 @@ export function getFetchMetrics() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function safeFetch(url, opts = {}) {
|
export async function safeFetch(url, opts = {}) {
|
||||||
const { timeout = 15000, retries = 1, headers = {}, source = undefined } = opts;
|
const { timeout = 15000, retries = 1, headers = {}, source = inferFetchSource(url) } = opts;
|
||||||
let lastError;
|
let lastError;
|
||||||
for (let i = 0; i <= retries; i++) {
|
for (let i = 0; i <= retries; i++) {
|
||||||
const started = Date.now();
|
const started = Date.now();
|
||||||
@@ -79,11 +117,11 @@ export async function safeFetch(url, opts = {}) {
|
|||||||
if (i < retries) await new Promise(r => setTimeout(r, 2000 * (i + 1)));
|
if (i < retries) await new Promise(r => setTimeout(r, 2000 * (i + 1)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { error: lastError?.message || 'Unknown error', source: url };
|
return { error: lastError?.message || 'Unknown error', source };
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function safeFetchText(url, opts = {}) {
|
export async function safeFetchText(url, opts = {}) {
|
||||||
const { timeout = 15000, retries = 1, headers = {}, source = undefined } = opts;
|
const { timeout = 15000, retries = 1, headers = {}, source = inferFetchSource(url) } = opts;
|
||||||
let lastError;
|
let lastError;
|
||||||
for (let i = 0; i <= retries; i++) {
|
for (let i = 0; i <= retries; i++) {
|
||||||
const started = Date.now();
|
const started = Date.now();
|
||||||
|
|||||||
@@ -32,14 +32,14 @@ export default {
|
|||||||
sseHeartbeatIntervalMs: intEnv('SSE_HEARTBEAT_INTERVAL_MS', 25000),
|
sseHeartbeatIntervalMs: intEnv('SSE_HEARTBEAT_INTERVAL_MS', 25000),
|
||||||
|
|
||||||
llm: {
|
llm: {
|
||||||
provider: process.env.LLM_PROVIDER || null, // anthropic | openai | gemini | codex | openrouter | minimax | mistral | ollama | grok
|
provider: process.env.LLM_PROVIDER || null, // litellm | openrouter | openai-compatible | lmstudio | ollama | other supported providers
|
||||||
apiKey: process.env.LLM_API_KEY || null,
|
apiKey: process.env.LLM_API_KEY || null,
|
||||||
model: process.env.LLM_MODEL || null,
|
model: process.env.LLM_MODEL || null,
|
||||||
baseUrl: process.env.LLM_BASE_URL || process.env.OPENAI_BASE_URL || process.env.OLLAMA_BASE_URL || null,
|
baseUrl: process.env.LLM_BASE_URL || process.env.OPENAI_BASE_URL || process.env.OLLAMA_BASE_URL || null,
|
||||||
temperature: floatEnv('LLM_TEMPERATURE', 0.2),
|
temperature: floatEnv('LLM_TEMPERATURE', 0.2),
|
||||||
maxTokens: intEnv('LLM_MAX_TOKENS', 2000),
|
maxTokens: intEnv('LLM_MAX_TOKENS', 2000),
|
||||||
timeoutMs: intEnv('LLM_TIMEOUT_MS', 90000),
|
timeoutMs: intEnv('LLM_TIMEOUT_MS', 90000),
|
||||||
openRouterSiteUrl: process.env.OPENROUTER_SITE_URL || 'https://git.wilkensxl.de/MrSphay/intelligence-terminal',
|
openRouterSiteUrl: process.env.OPENROUTER_SITE_URL || 'https://git.wilkensxl.de/Code-Inc/intelligence-terminal',
|
||||||
openRouterAppName: process.env.OPENROUTER_APP_NAME || 'Intelligence Terminal',
|
openRouterAppName: process.env.OPENROUTER_APP_NAME || 'Intelligence Terminal',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
services:
|
services:
|
||||||
intelligence-terminal:
|
intelligence-terminal:
|
||||||
image: git.wilkensxl.de/mrsphay/intelligence-terminal:latest
|
image: git.wilkensxl.de/code-inc/intelligence-terminal:latest
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
|
|||||||
@@ -1,6 +1,18 @@
|
|||||||
# Agent Handoff
|
# Agent Handoff
|
||||||
|
|
||||||
Last updated: 2026-05-17
|
Last updated: 2026-07-03
|
||||||
|
|
||||||
|
## Latest Completed Work
|
||||||
|
|
||||||
|
- Canonical repository: `https://git.wilkensxl.de/Code-Inc/intelligence-terminal`
|
||||||
|
- LiteLLM implementation merge: `5c4bf80eb0c19bd59080f5432a2a344798d7a3ce`
|
||||||
|
- Merged PR: `#48 feat: add LiteLLM provider and publish Code-Inc image`
|
||||||
|
- Completed issue: `#47 Add first-class LiteLLM provider and publish updated image`
|
||||||
|
- LiteLLM is implemented through the OpenAI-compatible `/chat/completions` API and requires `LLM_BASE_URL`, `LLM_API_KEY`, and `LLM_MODEL`.
|
||||||
|
- The build workflow now targets `git.wilkensxl.de/code-inc/intelligence-terminal` and publishes only from the production branch, not from pull requests.
|
||||||
|
- Gitea Actions runs 231-235 passed for the PR and production merge, including unit tests, Compose validation, Docker build, release dry-run, and template compliance.
|
||||||
|
- The first `code-inc` registry publication was verified through the Gitea Package API on 2026-07-03.
|
||||||
|
- Related maintenance: issue #21 tracks the failing security scan, #45 tracks the dependency workflow, and #46 tracks remaining namespace/handoff cleanup.
|
||||||
|
|
||||||
## Repository State
|
## Repository State
|
||||||
|
|
||||||
@@ -15,7 +27,7 @@ C:\Users\MrSphay\Documents\Codex\Crucix\intelligence-terminal
|
|||||||
Remotes:
|
Remotes:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
origin https://git.wilkensxl.de/MrSphay/intelligence-terminal.git
|
origin https://git.wilkensxl.de/Code-Inc/intelligence-terminal.git
|
||||||
upstream https://github.com/calesthio/Crucix.git
|
upstream https://github.com/calesthio/Crucix.git
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -25,23 +37,22 @@ Current branch tip:
|
|||||||
Run `git rev-parse HEAD` after clone/pull. This handoff was updated by the `docs: sync issue tracker and handoff` commit after the implementation commit below.
|
Run `git rev-parse HEAD` after clone/pull. This handoff was updated by the `docs: sync issue tracker and handoff` commit after the implementation commit below.
|
||||||
```
|
```
|
||||||
|
|
||||||
Latest implementation commit before issue-sync documentation:
|
Production baseline before the current LiteLLM work:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
53470cc701ec322080a89d220aef449b25850590
|
c159c83a0768486c8c6f445b458b760dba4ba385
|
||||||
```
|
```
|
||||||
|
|
||||||
Both pushed branches currently point to this commit:
|
The default production branch points to this commit before the current PR:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
origin/codex/production-intelligence-terminal
|
origin/codex/production-intelligence-terminal
|
||||||
origin/main
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Gitea repository:
|
Gitea repository:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
https://git.wilkensxl.de/MrSphay/intelligence-terminal
|
https://git.wilkensxl.de/Code-Inc/intelligence-terminal
|
||||||
```
|
```
|
||||||
|
|
||||||
Default branch observed through the Gitea API:
|
Default branch observed through the Gitea API:
|
||||||
@@ -79,7 +90,7 @@ Rules applied from the kit:
|
|||||||
- `docker-compose.yml` uses the Gitea Registry image by default:
|
- `docker-compose.yml` uses the Gitea Registry image by default:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
git.wilkensxl.de/mrsphay/intelligence-terminal:latest
|
git.wilkensxl.de/code-inc/intelligence-terminal:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
### API And Health
|
### API And Health
|
||||||
@@ -226,31 +237,40 @@ README includes:
|
|||||||
|
|
||||||
## Registry And Images
|
## Registry And Images
|
||||||
|
|
||||||
Registry image:
|
Production registry image:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
git.wilkensxl.de/mrsphay/intelligence-terminal
|
git.wilkensxl.de/code-inc/intelligence-terminal
|
||||||
```
|
```
|
||||||
|
|
||||||
Verified package tags through Gitea API:
|
The legacy `mrsphay` package remains available. Verified `code-inc` tags from the LiteLLM production merge:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
latest
|
latest
|
||||||
20260517
|
20260703
|
||||||
e933586b220656a2858d2215b934b22d1f08a908
|
5c4bf80eb0c19bd59080f5432a2a344798d7a3ce
|
||||||
53470cc701ec322080a89d220aef449b25850590
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Successful pull test:
|
Operator pull command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker pull git.wilkensxl.de/mrsphay/intelligence-terminal:latest
|
docker pull git.wilkensxl.de/code-inc/intelligence-terminal:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
Observed digest:
|
Published manifest digest:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
sha256:780a41413921bd9a676461eca1cd1372591f523be4b7c9513d9bc085cbe7922d
|
sha256:5e29483ebfd9baae368673adc790789f02aed2d5d5d3a550fe55a4b71b5b62dd
|
||||||
|
```
|
||||||
|
|
||||||
|
Relevant successful runner executions:
|
||||||
|
|
||||||
|
```text
|
||||||
|
PR build: https://git.wilkensxl.de/Code-Inc/intelligence-terminal/actions/runs/231
|
||||||
|
PR template check: https://git.wilkensxl.de/Code-Inc/intelligence-terminal/actions/runs/232
|
||||||
|
Production publish: https://git.wilkensxl.de/Code-Inc/intelligence-terminal/actions/runs/233
|
||||||
|
Release dry-run: https://git.wilkensxl.de/Code-Inc/intelligence-terminal/actions/runs/234
|
||||||
|
Template compliance: https://git.wilkensxl.de/Code-Inc/intelligence-terminal/actions/runs/235
|
||||||
```
|
```
|
||||||
|
|
||||||
## Gitea Actions
|
## Gitea Actions
|
||||||
@@ -280,12 +300,12 @@ template-compliance.yml on codex/production-intelligence-terminal: success
|
|||||||
Relevant run URLs:
|
Relevant run URLs:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
https://git.wilkensxl.de/MrSphay/intelligence-terminal/actions/runs/23
|
https://git.wilkensxl.de/Code-Inc/intelligence-terminal/actions/runs/23
|
||||||
https://git.wilkensxl.de/MrSphay/intelligence-terminal/actions/runs/24
|
https://git.wilkensxl.de/Code-Inc/intelligence-terminal/actions/runs/24
|
||||||
https://git.wilkensxl.de/MrSphay/intelligence-terminal/actions/runs/25
|
https://git.wilkensxl.de/Code-Inc/intelligence-terminal/actions/runs/25
|
||||||
https://git.wilkensxl.de/MrSphay/intelligence-terminal/actions/runs/26
|
https://git.wilkensxl.de/Code-Inc/intelligence-terminal/actions/runs/26
|
||||||
https://git.wilkensxl.de/MrSphay/intelligence-terminal/actions/runs/27
|
https://git.wilkensxl.de/Code-Inc/intelligence-terminal/actions/runs/27
|
||||||
https://git.wilkensxl.de/MrSphay/intelligence-terminal/actions/runs/28
|
https://git.wilkensxl.de/Code-Inc/intelligence-terminal/actions/runs/28
|
||||||
```
|
```
|
||||||
|
|
||||||
Repository secret expected by the registry publish workflow:
|
Repository secret expected by the registry publish workflow:
|
||||||
@@ -315,28 +335,28 @@ The following Gitea issues were created for real remaining work:
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
#1 Reddit source must stop unauthenticated .json scraping
|
#1 Reddit source must stop unauthenticated .json scraping
|
||||||
https://git.wilkensxl.de/MrSphay/intelligence-terminal/issues/1
|
https://git.wilkensxl.de/Code-Inc/intelligence-terminal/issues/1
|
||||||
|
|
||||||
#2 Send operator alerts when dashboard data remains stale
|
#2 Send operator alerts when dashboard data remains stale
|
||||||
https://git.wilkensxl.de/MrSphay/intelligence-terminal/issues/2
|
https://git.wilkensxl.de/Code-Inc/intelligence-terminal/issues/2
|
||||||
|
|
||||||
#3 ACLED credentialed integration needs regression test and diagnostics
|
#3 ACLED credentialed integration needs regression test and diagnostics
|
||||||
https://git.wilkensxl.de/MrSphay/intelligence-terminal/issues/3
|
https://git.wilkensxl.de/Code-Inc/intelligence-terminal/issues/3
|
||||||
|
|
||||||
#4 Complete memory and prediction loop beyond Phase-1 SQLite
|
#4 Complete memory and prediction loop beyond Phase-1 SQLite
|
||||||
https://git.wilkensxl.de/MrSphay/intelligence-terminal/issues/4
|
https://git.wilkensxl.de/Code-Inc/intelligence-terminal/issues/4
|
||||||
|
|
||||||
#5 Remove old inline dashboard snapshot from production builds
|
#5 Remove old inline dashboard snapshot from production builds
|
||||||
https://git.wilkensxl.de/MrSphay/intelligence-terminal/issues/5
|
https://git.wilkensxl.de/Code-Inc/intelligence-terminal/issues/5
|
||||||
|
|
||||||
#6 Harden Terminal Actions for public reverse-proxy deployments
|
#6 Harden Terminal Actions for public reverse-proxy deployments
|
||||||
https://git.wilkensxl.de/MrSphay/intelligence-terminal/issues/6
|
https://git.wilkensxl.de/Code-Inc/intelligence-terminal/issues/6
|
||||||
|
|
||||||
#7 Replace ADS-B stub with real disabled/degraded source handling
|
#7 Replace ADS-B stub with real disabled/degraded source handling
|
||||||
https://git.wilkensxl.de/MrSphay/intelligence-terminal/issues/7
|
https://git.wilkensxl.de/Code-Inc/intelligence-terminal/issues/7
|
||||||
|
|
||||||
#8 Clean inherited public-demo and upstream marketing references
|
#8 Clean inherited public-demo and upstream marketing references
|
||||||
https://git.wilkensxl.de/MrSphay/intelligence-terminal/issues/8
|
https://git.wilkensxl.de/Code-Inc/intelligence-terminal/issues/8
|
||||||
```
|
```
|
||||||
|
|
||||||
## Verification Already Performed
|
## Verification Already Performed
|
||||||
@@ -366,7 +386,7 @@ Audit result:
|
|||||||
0 high vulnerabilities
|
0 high vulnerabilities
|
||||||
```
|
```
|
||||||
|
|
||||||
Docker build and smoke test were performed locally earlier:
|
Docker build and smoke test were performed locally earlier against the now-legacy `mrsphay` image namespace. Current deployments must use the `code-inc` image documented above:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker build -t git.wilkensxl.de/mrsphay/intelligence-terminal:latest .
|
docker build -t git.wilkensxl.de/mrsphay/intelligence-terminal:latest .
|
||||||
@@ -420,7 +440,7 @@ origin/main
|
|||||||
1. Clone the Gitea repository:
|
1. Clone the Gitea repository:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://git.wilkensxl.de/MrSphay/intelligence-terminal.git
|
git clone https://git.wilkensxl.de/Code-Inc/intelligence-terminal.git
|
||||||
cd intelligence-terminal
|
cd intelligence-terminal
|
||||||
git checkout codex/production-intelligence-terminal
|
git checkout codex/production-intelligence-terminal
|
||||||
```
|
```
|
||||||
@@ -460,7 +480,7 @@ if ($env:GITEA_TOKEN) { "GITEA_TOKEN=set" } else { "GITEA_TOKEN=missing" }
|
|||||||
```bash
|
```bash
|
||||||
npm run test:unit
|
npm run test:unit
|
||||||
docker compose --env-file .env.example config
|
docker compose --env-file .env.example config
|
||||||
docker pull git.wilkensxl.de/mrsphay/intelligence-terminal:latest
|
docker pull git.wilkensxl.de/code-inc/intelligence-terminal:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
6. Start with Dockge/Pangolin using the README compose example and a `.env` based on `.env.example`.
|
6. Start with Dockge/Pangolin using the README compose example and a `.env` based on `.env.example`.
|
||||||
@@ -479,11 +499,11 @@ docker pull git.wilkensxl.de/mrsphay/intelligence-terminal:latest
|
|||||||
For deployment:
|
For deployment:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker pull git.wilkensxl.de/mrsphay/intelligence-terminal:latest
|
docker pull git.wilkensxl.de/code-inc/intelligence-terminal:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
For a pinned deployment:
|
For a pinned deployment:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker pull git.wilkensxl.de/mrsphay/intelligence-terminal:20260517
|
docker pull git.wilkensxl.de/code-inc/intelligence-terminal:20260703
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
1. Confirm `.env.example`, README compose sample, and registry image name match.
|
1. Confirm `.env.example`, README compose sample, and registry image name match.
|
||||||
2. Run `npm run test:unit`.
|
2. Run `npm run test:unit`.
|
||||||
3. Run `docker compose config`.
|
3. Run `docker compose config`.
|
||||||
4. Build `git.wilkensxl.de/mrsphay/intelligence-terminal:latest`.
|
4. Build `git.wilkensxl.de/code-inc/intelligence-terminal:latest`.
|
||||||
5. Start the image and verify `/api/health`.
|
5. Start the image and verify `/api/health`.
|
||||||
6. Push branch to Gitea.
|
6. Push branch to Gitea.
|
||||||
7. Push `latest` and a dated image tag to the Gitea Registry.
|
7. Push `latest` and a dated image tag to the Gitea Registry.
|
||||||
|
|||||||
21
docs/source-fetch-instrumentation.md
Normal file
21
docs/source-fetch-instrumentation.md
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# Source Fetch Instrumentation
|
||||||
|
|
||||||
|
`safeFetch()` and `safeFetchText()` attribute requests to `/api/metrics.fetch.bySource`.
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
|
||||||
|
- Prefer passing an explicit `source` option from source modules when the call has a clear Crucix source name.
|
||||||
|
- If `source` is omitted, the shared helper infers a stable provider name from the request host.
|
||||||
|
- Unknown hosts fall back to the lowercase host instead of the old `unknown` bucket.
|
||||||
|
- Raw `fetch()` calls should be limited to cases where the shared helper cannot represent the protocol cleanly.
|
||||||
|
|
||||||
|
Current raw-fetch exceptions:
|
||||||
|
|
||||||
|
| Area | Reason |
|
||||||
|
| --- | --- |
|
||||||
|
| OAuth/session handshakes | Token exchange calls often need custom form bodies, credential headers, or status-specific diagnostics. |
|
||||||
|
| Bot and alert delivery | Telegram/Discord alert calls are outbound operator notifications, not intelligence source health. |
|
||||||
|
| LLM providers | Provider clients already track model/provider status separately from source fetch health. |
|
||||||
|
| Dashboard browser calls | Browser-side `/api/*` and asset fetches are UI behavior, not source provider health. |
|
||||||
|
|
||||||
|
When adding a new intelligence source, use `safeFetch(url, { source: 'SourceName' })` unless there is a documented exception.
|
||||||
@@ -10,6 +10,7 @@ import { MistralProvider } from "./mistral.mjs";
|
|||||||
import { OllamaProvider } from "./ollama.mjs";
|
import { OllamaProvider } from "./ollama.mjs";
|
||||||
import { GrokProvider } from "./grok.mjs";
|
import { GrokProvider } from "./grok.mjs";
|
||||||
import { OpenAICompatibleProvider } from "./openai-compatible.mjs";
|
import { OpenAICompatibleProvider } from "./openai-compatible.mjs";
|
||||||
|
import { LiteLLMProvider } from "./litellm.mjs";
|
||||||
|
|
||||||
export { LLMProvider } from "./provider.mjs";
|
export { LLMProvider } from "./provider.mjs";
|
||||||
export { AnthropicProvider } from "./anthropic.mjs";
|
export { AnthropicProvider } from "./anthropic.mjs";
|
||||||
@@ -22,6 +23,7 @@ export { MistralProvider } from "./mistral.mjs";
|
|||||||
export { OllamaProvider } from "./ollama.mjs";
|
export { OllamaProvider } from "./ollama.mjs";
|
||||||
export { GrokProvider } from "./grok.mjs";
|
export { GrokProvider } from "./grok.mjs";
|
||||||
export { OpenAICompatibleProvider } from "./openai-compatible.mjs";
|
export { OpenAICompatibleProvider } from "./openai-compatible.mjs";
|
||||||
|
export { LiteLLMProvider } from "./litellm.mjs";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an LLM provider based on config.
|
* Create an LLM provider based on config.
|
||||||
@@ -66,6 +68,9 @@ export function createLLMProvider(llmConfig) {
|
|||||||
model: model || 'local-model',
|
model: model || 'local-model',
|
||||||
requiresApiKey: false,
|
requiresApiKey: false,
|
||||||
});
|
});
|
||||||
|
case "litellm":
|
||||||
|
case "lite-llm":
|
||||||
|
return new LiteLLMProvider(common);
|
||||||
case "openrouter":
|
case "openrouter":
|
||||||
return new OpenRouterProvider(common);
|
return new OpenRouterProvider(common);
|
||||||
case "gemini":
|
case "gemini":
|
||||||
|
|||||||
32
lib/llm/litellm.mjs
Normal file
32
lib/llm/litellm.mjs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
// LiteLLM proxy provider using the OpenAI-compatible chat completions API.
|
||||||
|
|
||||||
|
import { OpenAICompatibleProvider } from './openai-compatible.mjs';
|
||||||
|
|
||||||
|
export class LiteLLMProvider extends OpenAICompatibleProvider {
|
||||||
|
constructor(config = {}) {
|
||||||
|
const baseUrl = config.baseUrl?.replace(/\/+$/, '') || null;
|
||||||
|
const model = config.model || null;
|
||||||
|
|
||||||
|
super({
|
||||||
|
...config,
|
||||||
|
name: 'litellm',
|
||||||
|
baseUrl: baseUrl || 'http://localhost:4000/v1',
|
||||||
|
model: model || 'unconfigured',
|
||||||
|
requiresApiKey: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.baseUrl = baseUrl;
|
||||||
|
this.model = model;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isConfigured() {
|
||||||
|
return Boolean(this.baseUrl && this.apiKey && this.model);
|
||||||
|
}
|
||||||
|
|
||||||
|
get status() {
|
||||||
|
if (!this.baseUrl) return { state: 'misconfigured', reason: 'LLM_BASE_URL is required for LiteLLM' };
|
||||||
|
if (!this.apiKey) return { state: 'misconfigured', reason: 'LLM_API_KEY is required for LiteLLM' };
|
||||||
|
if (!this.model) return { state: 'misconfigured', reason: 'LLM_MODEL is required for LiteLLM' };
|
||||||
|
return { state: 'configured', provider: this.name, model: this.model, baseUrl: this.baseUrl };
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
"brief:save": "node apis/save-briefing.mjs",
|
"brief:save": "node apis/save-briefing.mjs",
|
||||||
"diag": "node diag.mjs",
|
"diag": "node diag.mjs",
|
||||||
"test": "npm run test:unit",
|
"test": "npm run test:unit",
|
||||||
"test:unit": "node --test test/llm-openrouter.test.mjs test/llm-ollama.test.mjs test/llm-openai-compatible.test.mjs test/fetch-utils.test.mjs test/reddit-source.test.mjs test/acled-source.test.mjs test/mojibake-text.test.mjs test/adsb.test.mjs test/dashboard-geotagging.test.mjs",
|
"test:unit": "node --test test/llm-openrouter.test.mjs test/llm-ollama.test.mjs test/llm-openai-compatible.test.mjs test/llm-litellm.test.mjs test/fetch-utils.test.mjs test/reddit-source.test.mjs test/acled-source.test.mjs test/mojibake-text.test.mjs test/adsb.test.mjs test/dashboard-geotagging.test.mjs",
|
||||||
"compose:config": "docker compose config",
|
"compose:config": "docker compose config",
|
||||||
"clean": "node scripts/clean.mjs",
|
"clean": "node scripts/clean.mjs",
|
||||||
"fresh-start": "npm run clean && npm start"
|
"fresh-start": "npm run clean && npm start"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import test from 'node:test';
|
import test from 'node:test';
|
||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import { readFileSync } from 'node:fs';
|
import { readFileSync } from 'node:fs';
|
||||||
import { safeFetch, safeFetchText, getFetchMetrics } from '../apis/utils/fetch.mjs';
|
import { safeFetch, safeFetchText, getFetchMetrics, inferFetchSource } from '../apis/utils/fetch.mjs';
|
||||||
import { formatStaleAlert, shouldSendStaleAlert } from '../lib/stale-alerts.mjs';
|
import { formatStaleAlert, shouldSendStaleAlert } from '../lib/stale-alerts.mjs';
|
||||||
|
|
||||||
test('safeFetch reports HTML as degraded JSON response', async () => {
|
test('safeFetch reports HTML as degraded JSON response', async () => {
|
||||||
@@ -101,6 +101,31 @@ test('safeFetchText returns text and byte count', async () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('safeFetch attributes unlabelled requests to a stable provider source', async () => {
|
||||||
|
const originalFetch = globalThis.fetch;
|
||||||
|
globalThis.fetch = async () => ({
|
||||||
|
ok: true,
|
||||||
|
status: 200,
|
||||||
|
headers: { get: () => 'application/json' },
|
||||||
|
text: async () => '{"observations":[]}',
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
const data = await safeFetch('https://api.fred.stlouisfed.org/fred/series/observations?series_id=VIXCLS', { retries: 0 });
|
||||||
|
assert.deepEqual(data, { observations: [] });
|
||||||
|
const bucket = getFetchMetrics().bySource.FRED;
|
||||||
|
assert.ok(bucket.requests >= 1);
|
||||||
|
assert.equal(bucket.lastStatus, 200);
|
||||||
|
} finally {
|
||||||
|
globalThis.fetch = originalFetch;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('inferFetchSource returns provider names and host fallback', () => {
|
||||||
|
assert.equal(inferFetchSource('https://api.bls.gov/publicAPI/v2/timeseries/data/CPI'), 'BLS');
|
||||||
|
assert.equal(inferFetchSource('https://query1.finance.yahoo.com/v8/finance/chart/%5EGSPC'), 'YahooFinance');
|
||||||
|
assert.equal(inferFetchSource('https://unknown.example.test/path'), 'unknown.example.test');
|
||||||
|
});
|
||||||
|
|
||||||
test('SSE endpoint sends reconnect guidance and clears heartbeat timer', () => {
|
test('SSE endpoint sends reconnect guidance and clears heartbeat timer', () => {
|
||||||
const server = readFileSync(new URL('../server.mjs', import.meta.url), 'utf8');
|
const server = readFileSync(new URL('../server.mjs', import.meta.url), 'utf8');
|
||||||
const config = readFileSync(new URL('../crucix.config.mjs', import.meta.url), 'utf8');
|
const config = readFileSync(new URL('../crucix.config.mjs', import.meta.url), 'utf8');
|
||||||
|
|||||||
76
test/llm-litellm.test.mjs
Normal file
76
test/llm-litellm.test.mjs
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import test from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import { LiteLLMProvider } from '../lib/llm/litellm.mjs';
|
||||||
|
import { createLLMProvider } from '../lib/llm/index.mjs';
|
||||||
|
|
||||||
|
test('factory creates a configured LiteLLM provider', () => {
|
||||||
|
const provider = createLLMProvider({
|
||||||
|
provider: 'litellm',
|
||||||
|
baseUrl: 'https://llm.example.test/v1/',
|
||||||
|
apiKey: 'proxy-key',
|
||||||
|
model: 'private-model',
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.ok(provider instanceof LiteLLMProvider);
|
||||||
|
assert.equal(provider.baseUrl, 'https://llm.example.test/v1');
|
||||||
|
assert.equal(provider.isConfigured, true);
|
||||||
|
assert.deepEqual(provider.status, {
|
||||||
|
state: 'configured',
|
||||||
|
provider: 'litellm',
|
||||||
|
model: 'private-model',
|
||||||
|
baseUrl: 'https://llm.example.test/v1',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('LiteLLM requires base URL, API key, and model', () => {
|
||||||
|
const missingBaseUrl = new LiteLLMProvider({ apiKey: 'key', model: 'model' });
|
||||||
|
assert.equal(missingBaseUrl.isConfigured, false);
|
||||||
|
assert.equal(missingBaseUrl.status.reason, 'LLM_BASE_URL is required for LiteLLM');
|
||||||
|
|
||||||
|
const missingKey = new LiteLLMProvider({ baseUrl: 'https://llm.example.test/v1', model: 'model' });
|
||||||
|
assert.equal(missingKey.status.reason, 'LLM_API_KEY is required for LiteLLM');
|
||||||
|
|
||||||
|
const missingModel = new LiteLLMProvider({ baseUrl: 'https://llm.example.test/v1', apiKey: 'key' });
|
||||||
|
assert.equal(missingModel.status.reason, 'LLM_MODEL is required for LiteLLM');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('LiteLLM sends bearer-authenticated OpenAI-compatible requests', async () => {
|
||||||
|
const provider = new LiteLLMProvider({
|
||||||
|
baseUrl: 'https://llm.example.test/v1',
|
||||||
|
apiKey: 'proxy-key',
|
||||||
|
model: 'private-model',
|
||||||
|
temperature: 0.15,
|
||||||
|
maxTokens: 512,
|
||||||
|
});
|
||||||
|
const originalFetch = globalThis.fetch;
|
||||||
|
|
||||||
|
globalThis.fetch = async (url, options) => {
|
||||||
|
assert.equal(url, 'https://llm.example.test/v1/chat/completions');
|
||||||
|
assert.equal(options.headers.Authorization, 'Bearer proxy-key');
|
||||||
|
assert.deepEqual(JSON.parse(options.body), {
|
||||||
|
model: 'private-model',
|
||||||
|
temperature: 0.15,
|
||||||
|
messages: [
|
||||||
|
{ role: 'system', content: 'system' },
|
||||||
|
{ role: 'user', content: 'user' },
|
||||||
|
],
|
||||||
|
max_tokens: 512,
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
json: async () => ({
|
||||||
|
choices: [{ message: { content: 'response' } }],
|
||||||
|
usage: { prompt_tokens: 7, completion_tokens: 11 },
|
||||||
|
model: 'private-model',
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await provider.complete('system', 'user');
|
||||||
|
assert.equal(result.text, 'response');
|
||||||
|
assert.deepEqual(result.usage, { inputTokens: 7, outputTokens: 11 });
|
||||||
|
} finally {
|
||||||
|
globalThis.fetch = originalFetch;
|
||||||
|
}
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user