skopos-fleet 0.1.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.
- skopos_fleet-0.1.0/LICENSE +21 -0
- skopos_fleet-0.1.0/PKG-INFO +195 -0
- skopos_fleet-0.1.0/README.md +167 -0
- skopos_fleet-0.1.0/pyproject.toml +47 -0
- skopos_fleet-0.1.0/setup.cfg +4 -0
- skopos_fleet-0.1.0/skopos/__init__.py +2 -0
- skopos_fleet-0.1.0/skopos/agent/__init__.py +18 -0
- skopos_fleet-0.1.0/skopos/agent/analyst.py +98 -0
- skopos_fleet-0.1.0/skopos/agent/config.py +103 -0
- skopos_fleet-0.1.0/skopos/agent/context.py +239 -0
- skopos_fleet-0.1.0/skopos/agent/ecosystem_briefing.py +485 -0
- skopos_fleet-0.1.0/skopos/agent/injection_guard.py +115 -0
- skopos_fleet-0.1.0/skopos/agent/providers.py +369 -0
- skopos_fleet-0.1.0/skopos/agent/rate_limit.py +31 -0
- skopos_fleet-0.1.0/skopos/agent/redact.py +25 -0
- skopos_fleet-0.1.0/skopos/agent/security_report.py +157 -0
- skopos_fleet-0.1.0/skopos/agent/service.py +145 -0
- skopos_fleet-0.1.0/skopos/agent_portal.py +52 -0
- skopos_fleet-0.1.0/skopos/agent_token.py +109 -0
- skopos_fleet-0.1.0/skopos/agent_widget.py +493 -0
- skopos_fleet-0.1.0/skopos/analytics_filters.py +218 -0
- skopos_fleet-0.1.0/skopos/apache.py +7 -0
- skopos_fleet-0.1.0/skopos/apache_logs.py +149 -0
- skopos_fleet-0.1.0/skopos/app_auth.py +192 -0
- skopos_fleet-0.1.0/skopos/app_shell.py +352 -0
- skopos_fleet-0.1.0/skopos/auth_store.py +320 -0
- skopos_fleet-0.1.0/skopos/backfill.py +205 -0
- skopos_fleet-0.1.0/skopos/brand.py +69 -0
- skopos_fleet-0.1.0/skopos/charts.py +766 -0
- skopos_fleet-0.1.0/skopos/collector.py +185 -0
- skopos_fleet-0.1.0/skopos/components/voice_input/__init__.py +19 -0
- skopos_fleet-0.1.0/skopos/config.py +234 -0
- skopos_fleet-0.1.0/skopos/config_io.py +167 -0
- skopos_fleet-0.1.0/skopos/config_paths.py +22 -0
- skopos_fleet-0.1.0/skopos/countries.py +263 -0
- skopos_fleet-0.1.0/skopos/db.py +373 -0
- skopos_fleet-0.1.0/skopos/db_connection.py +123 -0
- skopos_fleet-0.1.0/skopos/db_dialect.py +103 -0
- skopos_fleet-0.1.0/skopos/db_migrate.py +188 -0
- skopos_fleet-0.1.0/skopos/db_settings.py +109 -0
- skopos_fleet-0.1.0/skopos/doc_i18n.py +141 -0
- skopos_fleet-0.1.0/skopos/docs_i18n_render.py +792 -0
- skopos_fleet-0.1.0/skopos/docs_i18n_translations.py +527 -0
- skopos_fleet-0.1.0/skopos/docs_i18n_translations_rest.py +2630 -0
- skopos_fleet-0.1.0/skopos/docs_i18n_ui.py +369 -0
- skopos_fleet-0.1.0/skopos/docs_viewer.py +181 -0
- skopos_fleet-0.1.0/skopos/economy/__init__.py +14 -0
- skopos_fleet-0.1.0/skopos/economy/capabilities.py +109 -0
- skopos_fleet-0.1.0/skopos/economy/config.py +56 -0
- skopos_fleet-0.1.0/skopos/economy/consumer.py +62 -0
- skopos_fleet-0.1.0/skopos/economy/handlers.py +162 -0
- skopos_fleet-0.1.0/skopos/economy/invoke.py +58 -0
- skopos_fleet-0.1.0/skopos/economy/manifest.py +95 -0
- skopos_fleet-0.1.0/skopos/ecosystem.py +60 -0
- skopos_fleet-0.1.0/skopos/enrich.py +62 -0
- skopos_fleet-0.1.0/skopos/env.py +5 -0
- skopos_fleet-0.1.0/skopos/env_io.py +65 -0
- skopos_fleet-0.1.0/skopos/geo_centroids.py +263 -0
- skopos_fleet-0.1.0/skopos/geo_earth.py +147 -0
- skopos_fleet-0.1.0/skopos/geoip.py +196 -0
- skopos_fleet-0.1.0/skopos/host_infer.py +108 -0
- skopos_fleet-0.1.0/skopos/i18n.py +265 -0
- skopos_fleet-0.1.0/skopos/log_sources.py +164 -0
- skopos_fleet-0.1.0/skopos/nav_chrome.py +40 -0
- skopos_fleet-0.1.0/skopos/nginx.py +110 -0
- skopos_fleet-0.1.0/skopos/nginx_logs.py +104 -0
- skopos_fleet-0.1.0/skopos/password_policy.py +93 -0
- skopos_fleet-0.1.0/skopos/period_picker.py +326 -0
- skopos_fleet-0.1.0/skopos/premium_chrome.py +642 -0
- skopos_fleet-0.1.0/skopos/public_status.py +49 -0
- skopos_fleet-0.1.0/skopos/security/__init__.py +11 -0
- skopos_fleet-0.1.0/skopos/security/alert_i18n.py +38 -0
- skopos_fleet-0.1.0/skopos/security/audit.py +312 -0
- skopos_fleet-0.1.0/skopos/security/auto_scan.py +92 -0
- skopos_fleet-0.1.0/skopos/security/charts.py +738 -0
- skopos_fleet-0.1.0/skopos/security/collector.py +63 -0
- skopos_fleet-0.1.0/skopos/security/docker_insights.py +263 -0
- skopos_fleet-0.1.0/skopos/security/fail2ban_status.py +111 -0
- skopos_fleet-0.1.0/skopos/security/history_charts.py +131 -0
- skopos_fleet-0.1.0/skopos/security/knock_analyzer.py +181 -0
- skopos_fleet-0.1.0/skopos/security/knock_charts.py +156 -0
- skopos_fleet-0.1.0/skopos/security/port_knocks.py +204 -0
- skopos_fleet-0.1.0/skopos/security/posture.py +265 -0
- skopos_fleet-0.1.0/skopos/security/posture_loader.py +118 -0
- skopos_fleet-0.1.0/skopos/security/probe.py +324 -0
- skopos_fleet-0.1.0/skopos/security/project_audit.py +154 -0
- skopos_fleet-0.1.0/skopos/security/report_builder.py +164 -0
- skopos_fleet-0.1.0/skopos/security/store.py +691 -0
- skopos_fleet-0.1.0/skopos/security/tls_certs.py +340 -0
- skopos_fleet-0.1.0/skopos/setup_state.py +187 -0
- skopos_fleet-0.1.0/skopos/shell_safe.py +58 -0
- skopos_fleet-0.1.0/skopos/ssh.py +76 -0
- skopos_fleet-0.1.0/skopos/ssh_setup.py +124 -0
- skopos_fleet-0.1.0/skopos/telegram_notify.py +271 -0
- skopos_fleet-0.1.0/skopos/theme_prefs.py +76 -0
- skopos_fleet-0.1.0/skopos/themes.py +2803 -0
- skopos_fleet-0.1.0/skopos/traffic.py +33 -0
- skopos_fleet-0.1.0/skopos/ui.py +259 -0
- skopos_fleet-0.1.0/skopos/ui_agent.py +65 -0
- skopos_fleet-0.1.0/skopos/ui_agent_settings.py +127 -0
- skopos_fleet-0.1.0/skopos/ui_briefing.py +268 -0
- skopos_fleet-0.1.0/skopos/ui_dashboard_auth.py +207 -0
- skopos_fleet-0.1.0/skopos/ui_database_settings.py +179 -0
- skopos_fleet-0.1.0/skopos/ui_onboarding.py +86 -0
- skopos_fleet-0.1.0/skopos/ui_password_gate.py +97 -0
- skopos_fleet-0.1.0/skopos/ui_security.py +126 -0
- skopos_fleet-0.1.0/skopos/ui_security_report.py +170 -0
- skopos_fleet-0.1.0/skopos/ui_sidebar.py +5 -0
- skopos_fleet-0.1.0/skopos/ui_topbar.py +333 -0
- skopos_fleet-0.1.0/skopos/ui_wizard.py +389 -0
- skopos_fleet-0.1.0/skopos/uvicorn_log.py +59 -0
- skopos_fleet-0.1.0/skopos_fleet.egg-info/PKG-INFO +195 -0
- skopos_fleet-0.1.0/skopos_fleet.egg-info/SOURCES.txt +161 -0
- skopos_fleet-0.1.0/skopos_fleet.egg-info/dependency_links.txt +1 -0
- skopos_fleet-0.1.0/skopos_fleet.egg-info/entry_points.txt +2 -0
- skopos_fleet-0.1.0/skopos_fleet.egg-info/requires.txt +14 -0
- skopos_fleet-0.1.0/skopos_fleet.egg-info/top_level.txt +2 -0
- skopos_fleet-0.1.0/skoposctl.py +57 -0
- skopos_fleet-0.1.0/tests/test_agent_config.py +27 -0
- skopos_fleet-0.1.0/tests/test_agent_injection.py +55 -0
- skopos_fleet-0.1.0/tests/test_agent_service.py +47 -0
- skopos_fleet-0.1.0/tests/test_agent_token.py +47 -0
- skopos_fleet-0.1.0/tests/test_alert_i18n.py +17 -0
- skopos_fleet-0.1.0/tests/test_analytics_filters.py +13 -0
- skopos_fleet-0.1.0/tests/test_apache_logs.py +199 -0
- skopos_fleet-0.1.0/tests/test_app_smoke.py +34 -0
- skopos_fleet-0.1.0/tests/test_auth_store.py +113 -0
- skopos_fleet-0.1.0/tests/test_chart_labels.py +43 -0
- skopos_fleet-0.1.0/tests/test_collector_status.py +21 -0
- skopos_fleet-0.1.0/tests/test_db_backend.py +92 -0
- skopos_fleet-0.1.0/tests/test_db_migrate.py +80 -0
- skopos_fleet-0.1.0/tests/test_display_df.py +42 -0
- skopos_fleet-0.1.0/tests/test_docker_insights.py +51 -0
- skopos_fleet-0.1.0/tests/test_docs_public.py +12 -0
- skopos_fleet-0.1.0/tests/test_docs_viewer.py +77 -0
- skopos_fleet-0.1.0/tests/test_economy.py +82 -0
- skopos_fleet-0.1.0/tests/test_ecosystem_briefing.py +114 -0
- skopos_fleet-0.1.0/tests/test_ecosystem_connectivity.py +146 -0
- skopos_fleet-0.1.0/tests/test_env_io.py +21 -0
- skopos_fleet-0.1.0/tests/test_fullscreen_plot.py +32 -0
- skopos_fleet-0.1.0/tests/test_geoip_fallback.py +49 -0
- skopos_fleet-0.1.0/tests/test_globe_chart.py +45 -0
- skopos_fleet-0.1.0/tests/test_i18n.py +129 -0
- skopos_fleet-0.1.0/tests/test_llm_fallback.py +84 -0
- skopos_fleet-0.1.0/tests/test_llm_rate_limit.py +13 -0
- skopos_fleet-0.1.0/tests/test_openrouter_providers.py +64 -0
- skopos_fleet-0.1.0/tests/test_password_policy.py +67 -0
- skopos_fleet-0.1.0/tests/test_period_picker.py +78 -0
- skopos_fleet-0.1.0/tests/test_port_knocks.py +49 -0
- skopos_fleet-0.1.0/tests/test_posture.py +62 -0
- skopos_fleet-0.1.0/tests/test_scan_history.py +88 -0
- skopos_fleet-0.1.0/tests/test_security_audit.py +38 -0
- skopos_fleet-0.1.0/tests/test_security_charts.py +138 -0
- skopos_fleet-0.1.0/tests/test_security_hardening.py +73 -0
- skopos_fleet-0.1.0/tests/test_security_probe.py +21 -0
- skopos_fleet-0.1.0/tests/test_security_report.py +39 -0
- skopos_fleet-0.1.0/tests/test_security_scan_config.py +30 -0
- skopos_fleet-0.1.0/tests/test_settings_ssh.py +70 -0
- skopos_fleet-0.1.0/tests/test_setup_wizard.py +72 -0
- skopos_fleet-0.1.0/tests/test_telegram_notify.py +173 -0
- skopos_fleet-0.1.0/tests/test_theme_prefs.py +14 -0
- skopos_fleet-0.1.0/tests/test_theme_state.py +40 -0
- skopos_fleet-0.1.0/tests/test_themes.py +153 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Stats Fleet Center contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: skopos-fleet
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: SKOPOS (Σκοπός) — self-hosted nginx analytics and fleet security for the AICOM ecosystem.
|
|
5
|
+
Author: AICOM (alexar76)
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: nginx,analytics,security,streamlit,aimarket,aicom,fleet,ssh
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Requires-Python: >=3.11
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Requires-Dist: streamlit>=1.33
|
|
16
|
+
Requires-Dist: pandas>=2.0
|
|
17
|
+
Requires-Dist: plotly>=5.18
|
|
18
|
+
Requires-Dist: pyyaml>=6.0
|
|
19
|
+
Requires-Dist: paramiko>=3.4
|
|
20
|
+
Requires-Dist: python-dotenv>=1.0
|
|
21
|
+
Requires-Dist: httpx>=0.27
|
|
22
|
+
Requires-Dist: geoip2>=4.8
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
25
|
+
Provides-Extra: postgres
|
|
26
|
+
Requires-Dist: psycopg[binary]>=3.2; extra == "postgres"
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
# SKOPOS — fleet observability satellite of the AICOM ecosystem
|
|
30
|
+
|
|
31
|
+
<!-- aicom-readme-badges -->
|
|
32
|
+
<p align="center">
|
|
33
|
+
<a href="https://github.com/alexar76/skopos/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/alexar76/skopos/ci.yml?branch=main&label=CI" alt="CI" /></a>
|
|
34
|
+
<a href="https://github.com/alexar76/skopos/actions/workflows/pages.yml"><img src="https://img.shields.io/github/actions/workflow/status/alexar76/skopos/pages.yml?branch=main&label=Pages" alt="Pages deploy" /></a>
|
|
35
|
+
<a href="https://skopos.modelmarket.dev/"><img src="https://img.shields.io/badge/demo-live-brightgreen" alt="Live demo" /></a>
|
|
36
|
+
<a href="https://alexar76.github.io/skopos/"><img src="https://img.shields.io/badge/landing-cosmic-4de8ff" alt="Cosmic landing" /></a>
|
|
37
|
+
<img src="https://img.shields.io/badge/python-%3E%3D3.11-brightgreen.svg" alt="Python >=3.11" />
|
|
38
|
+
<img src="https://img.shields.io/badge/tests-226_passing-brightgreen" alt="226 tests passing" />
|
|
39
|
+
<img src="https://img.shields.io/badge/stack-Streamlit%20%2B%20PostgreSQL-6e40c9" alt="Streamlit + PostgreSQL" />
|
|
40
|
+
<a href="#testing--coverage"><img src="docs/badges/coverage.svg" alt="Test coverage" /></a>
|
|
41
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="License: MIT" /></a>
|
|
42
|
+
</p>
|
|
43
|
+
<!-- /aicom-readme-badges -->
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
> **Self-hosted nginx & Apache analytics and AI security for your server fleet** — GA-like HTTP dashboards, Security Center with 3D threat map, scan history, and an AI agent with voice input. No third-party trackers; data stays on your infrastructure.
|
|
48
|
+
|
|
49
|
+
<p align="center">
|
|
50
|
+
<a href="https://skopos.modelmarket.dev/app/">
|
|
51
|
+
<img src="docs/screenshots/readme/hero-banner.png" alt="SKOPOS — Web Analytics and Security Center with 3D threat map" width="100%" />
|
|
52
|
+
</a>
|
|
53
|
+
</p>
|
|
54
|
+
|
|
55
|
+
<p align="center">
|
|
56
|
+
<strong><a href="https://skopos.modelmarket.dev">Live demo</a></strong>
|
|
57
|
+
·
|
|
58
|
+
<strong><a href="https://alexar76.github.io/skopos/">Landing</a></strong>
|
|
59
|
+
·
|
|
60
|
+
nine built-in themes
|
|
61
|
+
</p>
|
|
62
|
+
|
|
63
|
+
In Greek, **skopos** (σκοπός) means *watcher* or *scout*. **SKOPOS** is the fleet observability satellite of the [AICOM / AIMarket ecosystem](https://magic-ai-factory.com): nginx traffic over SSH, security posture across factory / metis / oracle hosts, and an LLM analyst in the sidebar.
|
|
64
|
+
|
|
65
|
+
| | |
|
|
66
|
+
|---|---|
|
|
67
|
+
| **Role** | SSH log collection → SQLite/PostgreSQL analytics → Security Center + AI agent |
|
|
68
|
+
| **Live demo** | [skopos.modelmarket.dev](https://skopos.modelmarket.dev) |
|
|
69
|
+
| **Landing (GitHub Pages)** | [alexar76.github.io/skopos](https://alexar76.github.io/skopos/) |
|
|
70
|
+
| **Monitors** | **nginx** access logs (primary), **Apache** combined, CPU/RAM/disk, ports, fail2ban, port knocks |
|
|
71
|
+
| **Charter** | Read-only SSH probes · self-hosted data · optional dashboard password |
|
|
72
|
+
|
|
73
|
+
### Features
|
|
74
|
+
|
|
75
|
+
- **Analytics** — nginx access logs over SSH, SQLite, Streamlit charts, traffic globe
|
|
76
|
+
- **Security** — CPU/RAM/disk/network, port audit, firewall, cyberpunk 3D threat map, consolidated **Security Report**
|
|
77
|
+
- **Scan History** — score timeline, findings trends, snapshot comparison
|
|
78
|
+
- **Auto-scan** — configurable background security scans (default every 60 min)
|
|
79
|
+
- **AI Agent** — OpenRouter (default), DeepSeek, OpenAI, Anthropic, Ollama, LM Studio; sidebar chat with voice input
|
|
80
|
+
- **i18n** — English (default), Russian, Spanish (`locales/`)
|
|
81
|
+
|
|
82
|
+
### Quick start
|
|
83
|
+
|
|
84
|
+
See [docs/quickstart.md](docs/quickstart.md).
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
cp servers.example.yaml servers.yaml
|
|
88
|
+
cp agent.example.yaml agent.yaml
|
|
89
|
+
export OPENROUTER_API_KEY=sk-or-...
|
|
90
|
+
python skoposctl.py collect
|
|
91
|
+
python skoposctl.py security-scan
|
|
92
|
+
streamlit run dashboard.py
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
| Page | URL |
|
|
96
|
+
|------|-----|
|
|
97
|
+
| Analytics | `http://localhost:8501` |
|
|
98
|
+
| Security | sidebar → **Security** |
|
|
99
|
+
| Scan History | sidebar → **Scan History** |
|
|
100
|
+
| Settings | sidebar → **Settings** |
|
|
101
|
+
|
|
102
|
+
### Security
|
|
103
|
+
|
|
104
|
+
- **Security Score** (0–100, grade A–F) in sidebar on every page
|
|
105
|
+
- **Threat alerts** banner when critical/high issues exist
|
|
106
|
+
- **Dashboard password** — set/change/regenerate in the admin panel; only a
|
|
107
|
+
salted PBKDF2 **hash** is stored in the DB (env `SKOPOS_DASHBOARD_PASSWORD`
|
|
108
|
+
still supported for bootstrap). A post-deploy modal prompts you to set one, and
|
|
109
|
+
passwords expire after 90 days with a Telegram reminder. See
|
|
110
|
+
[configuration → Dashboard password & rotation](docs/en/guide/configuration.md)
|
|
111
|
+
- **SKOPOS_SSH_STRICT_HOST_KEYS=1** — verify SSH host keys (recommended)
|
|
112
|
+
- See [docs/audit-findings.md](docs/audit-findings.md)
|
|
113
|
+
|
|
114
|
+
### Documentation
|
|
115
|
+
|
|
116
|
+
| Doc | Description |
|
|
117
|
+
|-----|-------------|
|
|
118
|
+
| **[In-app Documentation](http://localhost:8501/Documentation)** | Guides with screenshots (also in sidebar / top bar) |
|
|
119
|
+
| [Quick Start](docs/quickstart.md) | 5-minute setup |
|
|
120
|
+
| [User Guide](docs/user-guide.md) | Full UI reference |
|
|
121
|
+
| [Use Cases](docs/use-cases.md) | Common workflows |
|
|
122
|
+
| [Security Module](docs/security.md) | Architecture & extension |
|
|
123
|
+
| [nginx scope](docs/en/guide/nginx.md) | **Analytics are nginx-only** — disclaimer & log format |
|
|
124
|
+
| [CHANGELOG](CHANGELOG.md) | Release notes |
|
|
125
|
+
|
|
126
|
+
> **Analytics scope:** traffic dashboards parse **nginx access logs** by default. **Apache combined** logs are supported when `apache.enabled: true`. Security probes are stack-agnostic.
|
|
127
|
+
|
|
128
|
+
### Configure servers
|
|
129
|
+
|
|
130
|
+
Edit `servers.yaml` — SSH host, nginx log paths. See `servers.example.yaml`.
|
|
131
|
+
|
|
132
|
+
For per-domain analytics, include `$host` in nginx `log_format`.
|
|
133
|
+
|
|
134
|
+
### AI agent
|
|
135
|
+
|
|
136
|
+
Default provider: **OpenRouter** via `OPENROUTER_API_KEY`. Edit `agent.yaml` for DeepSeek, OpenAI, Anthropic, Ollama, or LM Studio.
|
|
137
|
+
|
|
138
|
+
For a full prioritized remediation report, open **Security → Security Report**. The sidebar agent supports follow-up chat (including voice) on every page.
|
|
139
|
+
|
|
140
|
+
### Docker
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
docker compose up -d --build
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Open `http://localhost:8501`
|
|
147
|
+
|
|
148
|
+
### License
|
|
149
|
+
|
|
150
|
+
MIT — see [LICENSE](LICENSE).
|
|
151
|
+
|
|
152
|
+
### Testing & coverage
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
pip install -r requirements.txt pytest pytest-cov
|
|
156
|
+
bash scripts/ci_coverage_badge.sh -- tests/ -q --cov=skopos
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**226** pytest cases · line coverage on `skopos/` tracked in [`docs/badges/coverage.svg`](docs/badges/coverage.svg) (regenerate with `bash scripts/ci_coverage_badge.sh`, verified by [CI](.github/workflows/ci.yml)).
|
|
160
|
+
|
|
161
|
+
### Screenshots
|
|
162
|
+
|
|
163
|
+
Captured from [skopos.modelmarket.dev](https://skopos.modelmarket.dev/app/) — Analytics page in six themes (sidebar **Theme** picker):
|
|
164
|
+
|
|
165
|
+
<p align="center">
|
|
166
|
+
<img src="docs/screenshots/themes/analytics-light.png" alt="Light theme" width="48%" />
|
|
167
|
+
<img src="docs/screenshots/themes/analytics-premium.png" alt="Premium theme" width="48%" />
|
|
168
|
+
</p>
|
|
169
|
+
<p align="center"><sub>Light · Premium</sub></p>
|
|
170
|
+
|
|
171
|
+
<p align="center">
|
|
172
|
+
<img src="docs/screenshots/themes/analytics-midnight.png" alt="Cosmos theme" width="48%" />
|
|
173
|
+
<img src="docs/screenshots/themes/analytics-ocean.png" alt="Ocean theme" width="48%" />
|
|
174
|
+
</p>
|
|
175
|
+
<p align="center"><sub>Cosmos (midnight) · Ocean</sub></p>
|
|
176
|
+
|
|
177
|
+
<p align="center">
|
|
178
|
+
<img src="docs/screenshots/themes/analytics-aurora.png" alt="Aurora theme" width="48%" />
|
|
179
|
+
<img src="docs/screenshots/themes/analytics-slate.png" alt="Slate theme" width="48%" />
|
|
180
|
+
</p>
|
|
181
|
+
<p align="center"><sub>Aurora · Slate</sub></p>
|
|
182
|
+
|
|
183
|
+
| View | Screenshot |
|
|
184
|
+
|------|------------|
|
|
185
|
+
| Security — 3D Threat Map |  |
|
|
186
|
+
| Sidebar & navigation |  |
|
|
187
|
+
| Floating AI agent |  |
|
|
188
|
+
|
|
189
|
+
Regenerate hero + theme gallery from production:
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
export SKOPOS_DASHBOARD_PASSWORD='…'
|
|
193
|
+
pip install playwright pillow && playwright install chromium
|
|
194
|
+
python scripts/capture_readme_screenshots.py --base-url https://skopos.modelmarket.dev/app/
|
|
195
|
+
```
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# SKOPOS — fleet observability satellite of the AICOM ecosystem
|
|
2
|
+
|
|
3
|
+
<!-- aicom-readme-badges -->
|
|
4
|
+
<p align="center">
|
|
5
|
+
<a href="https://github.com/alexar76/skopos/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/alexar76/skopos/ci.yml?branch=main&label=CI" alt="CI" /></a>
|
|
6
|
+
<a href="https://github.com/alexar76/skopos/actions/workflows/pages.yml"><img src="https://img.shields.io/github/actions/workflow/status/alexar76/skopos/pages.yml?branch=main&label=Pages" alt="Pages deploy" /></a>
|
|
7
|
+
<a href="https://skopos.modelmarket.dev/"><img src="https://img.shields.io/badge/demo-live-brightgreen" alt="Live demo" /></a>
|
|
8
|
+
<a href="https://alexar76.github.io/skopos/"><img src="https://img.shields.io/badge/landing-cosmic-4de8ff" alt="Cosmic landing" /></a>
|
|
9
|
+
<img src="https://img.shields.io/badge/python-%3E%3D3.11-brightgreen.svg" alt="Python >=3.11" />
|
|
10
|
+
<img src="https://img.shields.io/badge/tests-226_passing-brightgreen" alt="226 tests passing" />
|
|
11
|
+
<img src="https://img.shields.io/badge/stack-Streamlit%20%2B%20PostgreSQL-6e40c9" alt="Streamlit + PostgreSQL" />
|
|
12
|
+
<a href="#testing--coverage"><img src="docs/badges/coverage.svg" alt="Test coverage" /></a>
|
|
13
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="License: MIT" /></a>
|
|
14
|
+
</p>
|
|
15
|
+
<!-- /aicom-readme-badges -->
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
> **Self-hosted nginx & Apache analytics and AI security for your server fleet** — GA-like HTTP dashboards, Security Center with 3D threat map, scan history, and an AI agent with voice input. No third-party trackers; data stays on your infrastructure.
|
|
20
|
+
|
|
21
|
+
<p align="center">
|
|
22
|
+
<a href="https://skopos.modelmarket.dev/app/">
|
|
23
|
+
<img src="docs/screenshots/readme/hero-banner.png" alt="SKOPOS — Web Analytics and Security Center with 3D threat map" width="100%" />
|
|
24
|
+
</a>
|
|
25
|
+
</p>
|
|
26
|
+
|
|
27
|
+
<p align="center">
|
|
28
|
+
<strong><a href="https://skopos.modelmarket.dev">Live demo</a></strong>
|
|
29
|
+
·
|
|
30
|
+
<strong><a href="https://alexar76.github.io/skopos/">Landing</a></strong>
|
|
31
|
+
·
|
|
32
|
+
nine built-in themes
|
|
33
|
+
</p>
|
|
34
|
+
|
|
35
|
+
In Greek, **skopos** (σκοπός) means *watcher* or *scout*. **SKOPOS** is the fleet observability satellite of the [AICOM / AIMarket ecosystem](https://magic-ai-factory.com): nginx traffic over SSH, security posture across factory / metis / oracle hosts, and an LLM analyst in the sidebar.
|
|
36
|
+
|
|
37
|
+
| | |
|
|
38
|
+
|---|---|
|
|
39
|
+
| **Role** | SSH log collection → SQLite/PostgreSQL analytics → Security Center + AI agent |
|
|
40
|
+
| **Live demo** | [skopos.modelmarket.dev](https://skopos.modelmarket.dev) |
|
|
41
|
+
| **Landing (GitHub Pages)** | [alexar76.github.io/skopos](https://alexar76.github.io/skopos/) |
|
|
42
|
+
| **Monitors** | **nginx** access logs (primary), **Apache** combined, CPU/RAM/disk, ports, fail2ban, port knocks |
|
|
43
|
+
| **Charter** | Read-only SSH probes · self-hosted data · optional dashboard password |
|
|
44
|
+
|
|
45
|
+
### Features
|
|
46
|
+
|
|
47
|
+
- **Analytics** — nginx access logs over SSH, SQLite, Streamlit charts, traffic globe
|
|
48
|
+
- **Security** — CPU/RAM/disk/network, port audit, firewall, cyberpunk 3D threat map, consolidated **Security Report**
|
|
49
|
+
- **Scan History** — score timeline, findings trends, snapshot comparison
|
|
50
|
+
- **Auto-scan** — configurable background security scans (default every 60 min)
|
|
51
|
+
- **AI Agent** — OpenRouter (default), DeepSeek, OpenAI, Anthropic, Ollama, LM Studio; sidebar chat with voice input
|
|
52
|
+
- **i18n** — English (default), Russian, Spanish (`locales/`)
|
|
53
|
+
|
|
54
|
+
### Quick start
|
|
55
|
+
|
|
56
|
+
See [docs/quickstart.md](docs/quickstart.md).
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
cp servers.example.yaml servers.yaml
|
|
60
|
+
cp agent.example.yaml agent.yaml
|
|
61
|
+
export OPENROUTER_API_KEY=sk-or-...
|
|
62
|
+
python skoposctl.py collect
|
|
63
|
+
python skoposctl.py security-scan
|
|
64
|
+
streamlit run dashboard.py
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
| Page | URL |
|
|
68
|
+
|------|-----|
|
|
69
|
+
| Analytics | `http://localhost:8501` |
|
|
70
|
+
| Security | sidebar → **Security** |
|
|
71
|
+
| Scan History | sidebar → **Scan History** |
|
|
72
|
+
| Settings | sidebar → **Settings** |
|
|
73
|
+
|
|
74
|
+
### Security
|
|
75
|
+
|
|
76
|
+
- **Security Score** (0–100, grade A–F) in sidebar on every page
|
|
77
|
+
- **Threat alerts** banner when critical/high issues exist
|
|
78
|
+
- **Dashboard password** — set/change/regenerate in the admin panel; only a
|
|
79
|
+
salted PBKDF2 **hash** is stored in the DB (env `SKOPOS_DASHBOARD_PASSWORD`
|
|
80
|
+
still supported for bootstrap). A post-deploy modal prompts you to set one, and
|
|
81
|
+
passwords expire after 90 days with a Telegram reminder. See
|
|
82
|
+
[configuration → Dashboard password & rotation](docs/en/guide/configuration.md)
|
|
83
|
+
- **SKOPOS_SSH_STRICT_HOST_KEYS=1** — verify SSH host keys (recommended)
|
|
84
|
+
- See [docs/audit-findings.md](docs/audit-findings.md)
|
|
85
|
+
|
|
86
|
+
### Documentation
|
|
87
|
+
|
|
88
|
+
| Doc | Description |
|
|
89
|
+
|-----|-------------|
|
|
90
|
+
| **[In-app Documentation](http://localhost:8501/Documentation)** | Guides with screenshots (also in sidebar / top bar) |
|
|
91
|
+
| [Quick Start](docs/quickstart.md) | 5-minute setup |
|
|
92
|
+
| [User Guide](docs/user-guide.md) | Full UI reference |
|
|
93
|
+
| [Use Cases](docs/use-cases.md) | Common workflows |
|
|
94
|
+
| [Security Module](docs/security.md) | Architecture & extension |
|
|
95
|
+
| [nginx scope](docs/en/guide/nginx.md) | **Analytics are nginx-only** — disclaimer & log format |
|
|
96
|
+
| [CHANGELOG](CHANGELOG.md) | Release notes |
|
|
97
|
+
|
|
98
|
+
> **Analytics scope:** traffic dashboards parse **nginx access logs** by default. **Apache combined** logs are supported when `apache.enabled: true`. Security probes are stack-agnostic.
|
|
99
|
+
|
|
100
|
+
### Configure servers
|
|
101
|
+
|
|
102
|
+
Edit `servers.yaml` — SSH host, nginx log paths. See `servers.example.yaml`.
|
|
103
|
+
|
|
104
|
+
For per-domain analytics, include `$host` in nginx `log_format`.
|
|
105
|
+
|
|
106
|
+
### AI agent
|
|
107
|
+
|
|
108
|
+
Default provider: **OpenRouter** via `OPENROUTER_API_KEY`. Edit `agent.yaml` for DeepSeek, OpenAI, Anthropic, Ollama, or LM Studio.
|
|
109
|
+
|
|
110
|
+
For a full prioritized remediation report, open **Security → Security Report**. The sidebar agent supports follow-up chat (including voice) on every page.
|
|
111
|
+
|
|
112
|
+
### Docker
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
docker compose up -d --build
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Open `http://localhost:8501`
|
|
119
|
+
|
|
120
|
+
### License
|
|
121
|
+
|
|
122
|
+
MIT — see [LICENSE](LICENSE).
|
|
123
|
+
|
|
124
|
+
### Testing & coverage
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
pip install -r requirements.txt pytest pytest-cov
|
|
128
|
+
bash scripts/ci_coverage_badge.sh -- tests/ -q --cov=skopos
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
**226** pytest cases · line coverage on `skopos/` tracked in [`docs/badges/coverage.svg`](docs/badges/coverage.svg) (regenerate with `bash scripts/ci_coverage_badge.sh`, verified by [CI](.github/workflows/ci.yml)).
|
|
132
|
+
|
|
133
|
+
### Screenshots
|
|
134
|
+
|
|
135
|
+
Captured from [skopos.modelmarket.dev](https://skopos.modelmarket.dev/app/) — Analytics page in six themes (sidebar **Theme** picker):
|
|
136
|
+
|
|
137
|
+
<p align="center">
|
|
138
|
+
<img src="docs/screenshots/themes/analytics-light.png" alt="Light theme" width="48%" />
|
|
139
|
+
<img src="docs/screenshots/themes/analytics-premium.png" alt="Premium theme" width="48%" />
|
|
140
|
+
</p>
|
|
141
|
+
<p align="center"><sub>Light · Premium</sub></p>
|
|
142
|
+
|
|
143
|
+
<p align="center">
|
|
144
|
+
<img src="docs/screenshots/themes/analytics-midnight.png" alt="Cosmos theme" width="48%" />
|
|
145
|
+
<img src="docs/screenshots/themes/analytics-ocean.png" alt="Ocean theme" width="48%" />
|
|
146
|
+
</p>
|
|
147
|
+
<p align="center"><sub>Cosmos (midnight) · Ocean</sub></p>
|
|
148
|
+
|
|
149
|
+
<p align="center">
|
|
150
|
+
<img src="docs/screenshots/themes/analytics-aurora.png" alt="Aurora theme" width="48%" />
|
|
151
|
+
<img src="docs/screenshots/themes/analytics-slate.png" alt="Slate theme" width="48%" />
|
|
152
|
+
</p>
|
|
153
|
+
<p align="center"><sub>Aurora · Slate</sub></p>
|
|
154
|
+
|
|
155
|
+
| View | Screenshot |
|
|
156
|
+
|------|------------|
|
|
157
|
+
| Security — 3D Threat Map |  |
|
|
158
|
+
| Sidebar & navigation |  |
|
|
159
|
+
| Floating AI agent |  |
|
|
160
|
+
|
|
161
|
+
Regenerate hero + theme gallery from production:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
export SKOPOS_DASHBOARD_PASSWORD='…'
|
|
165
|
+
pip install playwright pillow && playwright install chromium
|
|
166
|
+
python scripts/capture_readme_screenshots.py --base-url https://skopos.modelmarket.dev/app/
|
|
167
|
+
```
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "skopos-fleet"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "SKOPOS (Σκοπός) — self-hosted nginx analytics and fleet security for the AICOM ecosystem."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
requires-python = ">=3.11"
|
|
12
|
+
authors = [{ name = "AICOM (alexar76)" }]
|
|
13
|
+
keywords = ["nginx", "analytics", "security", "streamlit", "aimarket", "aicom", "fleet", "ssh"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Programming Language :: Python :: 3.11",
|
|
18
|
+
"Programming Language :: Python :: 3.12",
|
|
19
|
+
]
|
|
20
|
+
dependencies = [
|
|
21
|
+
"streamlit>=1.33",
|
|
22
|
+
"pandas>=2.0",
|
|
23
|
+
"plotly>=5.18",
|
|
24
|
+
"pyyaml>=6.0",
|
|
25
|
+
"paramiko>=3.4",
|
|
26
|
+
"python-dotenv>=1.0",
|
|
27
|
+
"httpx>=0.27",
|
|
28
|
+
"geoip2>=4.8",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.optional-dependencies]
|
|
32
|
+
dev = ["pytest>=8.0"]
|
|
33
|
+
postgres = ["psycopg[binary]>=3.2"]
|
|
34
|
+
|
|
35
|
+
[project.scripts]
|
|
36
|
+
skoposctl = "skoposctl:main"
|
|
37
|
+
|
|
38
|
+
[tool.setuptools]
|
|
39
|
+
py-modules = ["skoposctl"]
|
|
40
|
+
|
|
41
|
+
[tool.setuptools.packages.find]
|
|
42
|
+
where = ["."]
|
|
43
|
+
include = ["skopos*"]
|
|
44
|
+
|
|
45
|
+
[tool.pytest.ini_options]
|
|
46
|
+
testpaths = ["tests"]
|
|
47
|
+
pythonpath = ["."]
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from .analyst import AnalysisResult, chat_with_agent, chat_with_agent_stream, run_security_analysis
|
|
2
|
+
from .config import AgentConfig, load_agent_config
|
|
3
|
+
from .context import build_agent_context, build_server_context
|
|
4
|
+
from .providers import ChatMessage, LLMProviderError, chat_completion
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"AgentConfig",
|
|
8
|
+
"AnalysisResult",
|
|
9
|
+
"ChatMessage",
|
|
10
|
+
"LLMProviderError",
|
|
11
|
+
"build_agent_context",
|
|
12
|
+
"build_server_context",
|
|
13
|
+
"chat_completion",
|
|
14
|
+
"chat_with_agent",
|
|
15
|
+
"chat_with_agent_stream",
|
|
16
|
+
"load_agent_config",
|
|
17
|
+
"run_security_analysis",
|
|
18
|
+
]
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
from ..config import AppConfig
|
|
6
|
+
from ..db import connect_for_config, init_db
|
|
7
|
+
from .config import AgentConfig, load_agent_config
|
|
8
|
+
from .context import build_server_context
|
|
9
|
+
from .providers import ChatMessage, chat_completion, chat_completion_stream
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(frozen=True)
|
|
13
|
+
class AnalysisResult:
|
|
14
|
+
provider: str
|
|
15
|
+
model: str
|
|
16
|
+
report: str
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
ANALYSIS_PROMPT = """Perform a comprehensive security audit based on the server context below.
|
|
20
|
+
|
|
21
|
+
Structure your report with:
|
|
22
|
+
1. Executive summary (risk level: Critical/High/Medium/Low)
|
|
23
|
+
2. Open ports & exposure analysis
|
|
24
|
+
3. Resource health (CPU, memory, disk)
|
|
25
|
+
4. Docker containers — role, ports, CPU/RAM per container, stopped vs running
|
|
26
|
+
5. Authentication & SSH hardening
|
|
27
|
+
6. HTTP attack surface from traffic logs
|
|
28
|
+
7. Prioritized remediation checklist (numbered, actionable)
|
|
29
|
+
|
|
30
|
+
Be specific to the actual data. Reference server names and ports."""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def run_security_analysis(
|
|
34
|
+
app_cfg: AppConfig,
|
|
35
|
+
agent_cfg: AgentConfig,
|
|
36
|
+
*,
|
|
37
|
+
server_name: str | None = None,
|
|
38
|
+
provider_id: str | None = None,
|
|
39
|
+
) -> AnalysisResult:
|
|
40
|
+
con = connect_for_config(app_cfg)
|
|
41
|
+
init_db(con)
|
|
42
|
+
try:
|
|
43
|
+
context = build_server_context(app_cfg, con, server_name=server_name)
|
|
44
|
+
context = context[: agent_cfg.max_context_chars]
|
|
45
|
+
prov_id = provider_id or agent_cfg.default_provider
|
|
46
|
+
prov = agent_cfg.providers[prov_id]
|
|
47
|
+
messages = [
|
|
48
|
+
ChatMessage(role="system", content=agent_cfg.system_prompt),
|
|
49
|
+
ChatMessage(
|
|
50
|
+
role="user",
|
|
51
|
+
content=f"{ANALYSIS_PROMPT}\n\n---\n\n{context}",
|
|
52
|
+
),
|
|
53
|
+
]
|
|
54
|
+
report = chat_completion(agent_cfg, messages, provider_id=prov_id)
|
|
55
|
+
return AnalysisResult(provider=prov_id, model=prov.model, report=report)
|
|
56
|
+
finally:
|
|
57
|
+
con.close()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def chat_with_agent(
|
|
61
|
+
agent_cfg: AgentConfig,
|
|
62
|
+
history: list[ChatMessage],
|
|
63
|
+
user_message: str,
|
|
64
|
+
*,
|
|
65
|
+
context: str,
|
|
66
|
+
provider_id: str | None = None,
|
|
67
|
+
) -> str:
|
|
68
|
+
messages = [
|
|
69
|
+
ChatMessage(role="system", content=agent_cfg.system_prompt),
|
|
70
|
+
ChatMessage(
|
|
71
|
+
role="user",
|
|
72
|
+
content=f"Server context (for reference):\n\n{context[: agent_cfg.max_context_chars]}",
|
|
73
|
+
),
|
|
74
|
+
ChatMessage(role="assistant", content="Understood. I have the server context. How can I help?"),
|
|
75
|
+
]
|
|
76
|
+
messages.extend(history)
|
|
77
|
+
messages.append(ChatMessage(role="user", content=user_message))
|
|
78
|
+
return chat_completion(agent_cfg, messages, provider_id=provider_id)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def chat_with_agent_stream(
|
|
82
|
+
agent_cfg: AgentConfig,
|
|
83
|
+
history: list[ChatMessage],
|
|
84
|
+
user_message: str,
|
|
85
|
+
*,
|
|
86
|
+
context: str,
|
|
87
|
+
provider_id: str | None = None,
|
|
88
|
+
):
|
|
89
|
+
messages = [
|
|
90
|
+
ChatMessage(role="system", content=agent_cfg.system_prompt),
|
|
91
|
+
ChatMessage(
|
|
92
|
+
role="user",
|
|
93
|
+
content=f"Server context (for reference):\n\n{context[: agent_cfg.max_context_chars]}",
|
|
94
|
+
),
|
|
95
|
+
]
|
|
96
|
+
messages.extend(history)
|
|
97
|
+
messages.append(ChatMessage(role="user", content=user_message))
|
|
98
|
+
yield from chat_completion_stream(agent_cfg, messages, provider_id=provider_id)
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any, Literal
|
|
7
|
+
|
|
8
|
+
import yaml
|
|
9
|
+
|
|
10
|
+
ProviderKind = Literal["openai_compatible", "anthropic_compatible", "ollama", "lmstudio"]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(frozen=True)
|
|
14
|
+
class ProviderConfig:
|
|
15
|
+
id: str
|
|
16
|
+
kind: ProviderKind
|
|
17
|
+
base_url: str
|
|
18
|
+
model: str
|
|
19
|
+
api_key: str | None = None
|
|
20
|
+
api_key_env: str | None = None
|
|
21
|
+
extra_headers: dict[str, str] | None = None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass(frozen=True)
|
|
25
|
+
class AgentConfig:
|
|
26
|
+
default_provider: str
|
|
27
|
+
providers: dict[str, ProviderConfig]
|
|
28
|
+
system_prompt: str
|
|
29
|
+
max_context_chars: int = 120_000
|
|
30
|
+
briefing_provider: str | None = None
|
|
31
|
+
briefing_model: str | None = None
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _resolve_api_key(raw: dict[str, Any]) -> str | None:
|
|
35
|
+
if raw.get("api_key"):
|
|
36
|
+
return str(raw["api_key"])
|
|
37
|
+
env_name = raw.get("api_key_env")
|
|
38
|
+
if env_name:
|
|
39
|
+
return os.environ.get(str(env_name))
|
|
40
|
+
return None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def load_agent_config(path: str = "./agent.yaml") -> AgentConfig:
|
|
44
|
+
p = Path(path).expanduser()
|
|
45
|
+
if not p.exists():
|
|
46
|
+
p = Path(path.replace(".yaml", ".example.yaml")).expanduser()
|
|
47
|
+
if not p.exists():
|
|
48
|
+
raise FileNotFoundError(f"Agent config not found: {path}")
|
|
49
|
+
|
|
50
|
+
raw = yaml.safe_load(p.read_text(encoding="utf-8"))
|
|
51
|
+
if not isinstance(raw, dict):
|
|
52
|
+
raise ValueError("agent.yaml must be a mapping")
|
|
53
|
+
|
|
54
|
+
default = str(raw.get("default_provider", "deepseek"))
|
|
55
|
+
providers_raw = raw.get("providers") or {}
|
|
56
|
+
providers: dict[str, ProviderConfig] = {}
|
|
57
|
+
for pid, pr in providers_raw.items():
|
|
58
|
+
if not isinstance(pr, dict):
|
|
59
|
+
continue
|
|
60
|
+
providers[str(pid)] = ProviderConfig(
|
|
61
|
+
id=str(pid),
|
|
62
|
+
kind=str(pr.get("kind", "openai_compatible")), # type: ignore[arg-type]
|
|
63
|
+
base_url=str(pr.get("base_url", "")),
|
|
64
|
+
model=str(pr.get("model", "")),
|
|
65
|
+
api_key=_resolve_api_key(pr),
|
|
66
|
+
api_key_env=str(pr["api_key_env"]) if pr.get("api_key_env") else None,
|
|
67
|
+
extra_headers=pr.get("extra_headers"),
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
return AgentConfig(
|
|
71
|
+
default_provider=default,
|
|
72
|
+
providers=providers,
|
|
73
|
+
system_prompt=str(
|
|
74
|
+
raw.get(
|
|
75
|
+
"system_prompt",
|
|
76
|
+
"You are a senior security analyst for production Linux servers.",
|
|
77
|
+
)
|
|
78
|
+
),
|
|
79
|
+
max_context_chars=int(raw.get("max_context_chars", 120_000)),
|
|
80
|
+
briefing_provider=str(raw["briefing_provider"]).strip() if raw.get("briefing_provider") else None,
|
|
81
|
+
briefing_model=str(raw["briefing_model"]).strip() if raw.get("briefing_model") else None,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def get_provider(cfg: AgentConfig, provider_id: str | None = None) -> ProviderConfig:
|
|
86
|
+
pid = provider_id or cfg.default_provider
|
|
87
|
+
if pid not in cfg.providers:
|
|
88
|
+
raise KeyError(f"Unknown provider '{pid}'. Available: {', '.join(cfg.providers)}")
|
|
89
|
+
prov = cfg.providers[pid]
|
|
90
|
+
if not prov.api_key and prov.kind in ("openai_compatible", "anthropic_compatible"):
|
|
91
|
+
if prov.api_key_env:
|
|
92
|
+
key = os.environ.get(prov.api_key_env)
|
|
93
|
+
if key:
|
|
94
|
+
return ProviderConfig(
|
|
95
|
+
id=prov.id,
|
|
96
|
+
kind=prov.kind,
|
|
97
|
+
base_url=prov.base_url,
|
|
98
|
+
model=prov.model,
|
|
99
|
+
api_key=key,
|
|
100
|
+
api_key_env=prov.api_key_env,
|
|
101
|
+
extra_headers=prov.extra_headers,
|
|
102
|
+
)
|
|
103
|
+
return prov
|