radhiops 0.0.1__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.
- radhiops-0.0.1/.gitignore +32 -0
- radhiops-0.0.1/PKG-INFO +220 -0
- radhiops-0.0.1/README.md +191 -0
- radhiops-0.0.1/pyproject.toml +49 -0
- radhiops-0.0.1/radhiops/__init__.py +59 -0
- radhiops-0.0.1/radhiops/agents/__init__.py +19 -0
- radhiops-0.0.1/radhiops/agents/base.py +52 -0
- radhiops-0.0.1/radhiops/agents/cyberdefense.py +134 -0
- radhiops-0.0.1/radhiops/agents/deployment.py +132 -0
- radhiops-0.0.1/radhiops/agents/monitor.py +169 -0
- radhiops-0.0.1/radhiops/agents/repo.py +247 -0
- radhiops-0.0.1/radhiops/agents/soc.py +53 -0
- radhiops-0.0.1/radhiops/backend.py +146 -0
- radhiops-0.0.1/radhiops/cli.py +226 -0
- radhiops-0.0.1/radhiops/client.py +258 -0
- radhiops-0.0.1/radhiops/config.py +64 -0
- radhiops-0.0.1/radhiops/credits.py +91 -0
- radhiops-0.0.1/radhiops/defense/__init__.py +21 -0
- radhiops-0.0.1/radhiops/defense/behavior.py +86 -0
- radhiops-0.0.1/radhiops/defense/engine.py +102 -0
- radhiops-0.0.1/radhiops/defense/request.py +34 -0
- radhiops-0.0.1/radhiops/defense/signatures.py +103 -0
- radhiops-0.0.1/radhiops/deploy/__init__.py +23 -0
- radhiops-0.0.1/radhiops/deploy/base.py +157 -0
- radhiops-0.0.1/radhiops/deploy/diagnose.py +119 -0
- radhiops-0.0.1/radhiops/deploy/netlify.py +80 -0
- radhiops-0.0.1/radhiops/deploy/railway.py +89 -0
- radhiops-0.0.1/radhiops/deploy/registry.py +72 -0
- radhiops-0.0.1/radhiops/deploy/render.py +86 -0
- radhiops-0.0.1/radhiops/deploy/surge.py +70 -0
- radhiops-0.0.1/radhiops/deploy/vercel.py +74 -0
- radhiops-0.0.1/radhiops/exceptions.py +58 -0
- radhiops-0.0.1/radhiops/monitor/__init__.py +24 -0
- radhiops-0.0.1/radhiops/monitor/anomaly.py +90 -0
- radhiops-0.0.1/radhiops/monitor/crash.py +38 -0
- radhiops-0.0.1/radhiops/monitor/health.py +75 -0
- radhiops-0.0.1/radhiops/monitor/incident.py +97 -0
- radhiops-0.0.1/radhiops/monitor/metrics.py +69 -0
- radhiops-0.0.1/radhiops/orchestrator/__init__.py +9 -0
- radhiops-0.0.1/radhiops/orchestrator/autonomy.py +30 -0
- radhiops-0.0.1/radhiops/orchestrator/core.py +314 -0
- radhiops-0.0.1/radhiops/orchestrator/playbooks.py +83 -0
- radhiops-0.0.1/radhiops/plans.py +43 -0
- radhiops-0.0.1/radhiops/providers/__init__.py +20 -0
- radhiops-0.0.1/radhiops/providers/anthropic_provider.py +51 -0
- radhiops-0.0.1/radhiops/providers/base.py +92 -0
- radhiops-0.0.1/radhiops/providers/google_provider.py +40 -0
- radhiops-0.0.1/radhiops/providers/huggingface_provider.py +50 -0
- radhiops-0.0.1/radhiops/providers/ollama_provider.py +48 -0
- radhiops-0.0.1/radhiops/providers/openai_provider.py +42 -0
- radhiops-0.0.1/radhiops/providers/registry.py +85 -0
- radhiops-0.0.1/radhiops/repo/__init__.py +8 -0
- radhiops-0.0.1/radhiops/repo/git.py +149 -0
- radhiops-0.0.1/radhiops/repo/intents.py +100 -0
- radhiops-0.0.1/radhiops/security/__init__.py +19 -0
- radhiops-0.0.1/radhiops/security/secrets.py +199 -0
- radhiops-0.0.1/radhiops/userconfig.py +58 -0
- radhiops-0.0.1/tests/conftest.py +14 -0
- radhiops-0.0.1/tests/test_backend.py +102 -0
- radhiops-0.0.1/tests/test_client.py +55 -0
- radhiops-0.0.1/tests/test_defense.py +158 -0
- radhiops-0.0.1/tests/test_deploy.py +125 -0
- radhiops-0.0.1/tests/test_diagnose.py +34 -0
- radhiops-0.0.1/tests/test_intents.py +56 -0
- radhiops-0.0.1/tests/test_monitor.py +143 -0
- radhiops-0.0.1/tests/test_orchestrator.py +121 -0
- radhiops-0.0.1/tests/test_repo_agent.py +92 -0
- radhiops-0.0.1/tests/test_secrets.py +34 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
.eggs/
|
|
6
|
+
build/
|
|
7
|
+
dist/
|
|
8
|
+
.venv/
|
|
9
|
+
venv/
|
|
10
|
+
env/
|
|
11
|
+
.pytest_cache/
|
|
12
|
+
.mypy_cache/
|
|
13
|
+
.ruff_cache/
|
|
14
|
+
.coverage
|
|
15
|
+
htmlcov/
|
|
16
|
+
|
|
17
|
+
# Node
|
|
18
|
+
node_modules/
|
|
19
|
+
*.log
|
|
20
|
+
npm-debug.log*
|
|
21
|
+
|
|
22
|
+
# Env / secrets — never commit these
|
|
23
|
+
.env
|
|
24
|
+
.env.*
|
|
25
|
+
*.pem
|
|
26
|
+
*.key
|
|
27
|
+
secrets/
|
|
28
|
+
|
|
29
|
+
# IDE / OS
|
|
30
|
+
.DS_Store
|
|
31
|
+
.idea/
|
|
32
|
+
*.swp
|
radhiops-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: radhiops
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: RadhiOps — BYOE AI Engineering Platform SDK
|
|
5
|
+
Author: RadhiOps
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Keywords: agents,ai,byoe,devops,mcp,security
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Requires-Dist: httpx>=0.27
|
|
10
|
+
Requires-Dist: pydantic>=2.6
|
|
11
|
+
Provides-Extra: all
|
|
12
|
+
Requires-Dist: anthropic>=0.34; extra == 'all'
|
|
13
|
+
Requires-Dist: google-generativeai>=0.7; extra == 'all'
|
|
14
|
+
Requires-Dist: huggingface-hub>=0.24; extra == 'all'
|
|
15
|
+
Requires-Dist: openai>=1.30; extra == 'all'
|
|
16
|
+
Provides-Extra: anthropic
|
|
17
|
+
Requires-Dist: anthropic>=0.34; extra == 'anthropic'
|
|
18
|
+
Provides-Extra: dev
|
|
19
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
20
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
21
|
+
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
22
|
+
Provides-Extra: google
|
|
23
|
+
Requires-Dist: google-generativeai>=0.7; extra == 'google'
|
|
24
|
+
Provides-Extra: huggingface
|
|
25
|
+
Requires-Dist: huggingface-hub>=0.24; extra == 'huggingface'
|
|
26
|
+
Provides-Extra: openai
|
|
27
|
+
Requires-Dist: openai>=1.30; extra == 'openai'
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
# radhiops
|
|
31
|
+
|
|
32
|
+
The Python SDK for **RadhiOps** — the BYOE (Bring Your Own Everything) AI
|
|
33
|
+
Engineering Platform.
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install radhiops
|
|
37
|
+
# providers are optional extras — install only what you use:
|
|
38
|
+
pip install 'radhiops[openai]' # or [anthropic], [google], [huggingface], [all]
|
|
39
|
+
# ollama needs no extra (talks to your local server)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Quick start
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
from radhiops import RadhiOps
|
|
46
|
+
|
|
47
|
+
# New here? A demo key seeds 500 free credits, fully offline.
|
|
48
|
+
ops = RadhiOps(access_key="radhi_demo_yourtrialkey123")
|
|
49
|
+
|
|
50
|
+
# Run a local security audit (no AI, no network needed).
|
|
51
|
+
report = ops.soc.audit("./my-project")
|
|
52
|
+
print(report.counts()) # {'low': 0, 'medium': 1, 'high': 0, 'critical': 0}
|
|
53
|
+
print(report.passed) # gate result for pre-push / pre-deploy
|
|
54
|
+
|
|
55
|
+
# Bring your own model for AI-assisted remediation.
|
|
56
|
+
ops.use_model("openai", model="gpt-4o-mini", api_key="sk-...")
|
|
57
|
+
print(ops.soc.remediate(report))
|
|
58
|
+
|
|
59
|
+
# Or run a fully local model via Ollama — no API key:
|
|
60
|
+
ops.use_model("ollama", model="llama3.1")
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Repo agent — Git in plain English
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
repo = ops.repo("./my-project")
|
|
67
|
+
|
|
68
|
+
repo.command("what changed?")
|
|
69
|
+
repo.command('commit "fix: handle null user"')
|
|
70
|
+
repo.command("create a new branch feature/login")
|
|
71
|
+
repo.command("merge dev into main")
|
|
72
|
+
|
|
73
|
+
# push() runs a RadhiSOC security gate first and BLOCKS on high/critical
|
|
74
|
+
# findings unless you explicitly override.
|
|
75
|
+
result = repo.push()
|
|
76
|
+
if not result.ok:
|
|
77
|
+
print(result.message) # e.g. "Security gate blocked the push: 1 finding"
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Common verbs are parsed by rules (free, offline). Ambiguous phrasing falls back
|
|
81
|
+
to your BYOE model. Protected branches (main/master/prod) require
|
|
82
|
+
`allow_protected=True`, and force pushes use `--force-with-lease`.
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
radhiops repo "push my changes" --path ./my-project
|
|
86
|
+
radhiops repo "merge dev into main"
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Deployment agent — BYOE deploy targets
|
|
90
|
+
|
|
91
|
+
Bring your own platform token. RadhiOps never stores it.
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
# Vercel / Netlify / Render / Railway / Surge
|
|
95
|
+
dep = ops.deploy("vercel", token="<vercel-token>", team_id="team_...")
|
|
96
|
+
|
|
97
|
+
dep.list(limit=5) # recent deployments (normalized)
|
|
98
|
+
d = dep.trigger() # kick a new deployment (where supported)
|
|
99
|
+
final = dep.watch(d.id) # poll until ready/failed
|
|
100
|
+
if final.status.failed:
|
|
101
|
+
diag = dep.diagnose(d.id) # rule-based + AI root-cause from the logs
|
|
102
|
+
print(diag.rule_based["suggestion"])
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
| Platform | name | Scope option | Triggers deploys |
|
|
106
|
+
|----------|------|--------------|------------------|
|
|
107
|
+
| Vercel | `vercel` | `team_id` (optional) | via git integration |
|
|
108
|
+
| Netlify | `netlify` | `site_id` | yes (build) |
|
|
109
|
+
| Render | `render` | `service_id` | yes |
|
|
110
|
+
| Railway | `railway` | `service_id` | (monitor) |
|
|
111
|
+
| Surge | `surge` | `domain` | yes (CLI) |
|
|
112
|
+
|
|
113
|
+
The log diagnoser recognises common failures (missing modules, unset env vars,
|
|
114
|
+
OOM, port conflicts, lockfile mismatches) with zero credits; anything it can't
|
|
115
|
+
classify is escalated to your BYOE model.
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
radhiops deploy render status --id dep_123 --token $RENDER_TOKEN --opt service_id=srv_abc
|
|
119
|
+
radhiops deploy vercel diagnose --id dpl_123 --token $VERCEL_TOKEN
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Runtime Monitor — production health & incidents
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
mon = ops.monitor()
|
|
126
|
+
mon.add_target("api", "https://myapp.com/health", contains="ok")
|
|
127
|
+
mon.add_target("web", "https://myapp.com")
|
|
128
|
+
|
|
129
|
+
mon.poll() # probe every target once
|
|
130
|
+
for inc in mon.evaluate(): # anomalies -> incidents
|
|
131
|
+
print(inc.severity, inc.summary, "->", inc.escalate_to)
|
|
132
|
+
|
|
133
|
+
# crash detection from a runtime log stream
|
|
134
|
+
crash = mon.ingest_logs("api", ["Traceback (most recent call last):", "MemoryError"])
|
|
135
|
+
|
|
136
|
+
# or run a monitoring loop with a callback per incident
|
|
137
|
+
mon.watch(rounds=10, interval=30, on_incident=lambda i: print(i.to_dict()))
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Detects: endpoint down (consecutive failures), error-rate spikes, latency
|
|
141
|
+
degradation (p95), and runtime crashes (OOM, segfault, unhandled exceptions,
|
|
142
|
+
restart loops, DB connection failures). Each incident is tagged with the agent
|
|
143
|
+
that should handle it next (`DeploymentAgent`, `CyberDefenseAgent`), ready for
|
|
144
|
+
the autonomous loop. Thresholds are tunable via `Thresholds`.
|
|
145
|
+
|
|
146
|
+
## Cyber Defense agent — runtime attack detection
|
|
147
|
+
|
|
148
|
+
Feed inbound requests through the guard; it returns an allow/challenge/block
|
|
149
|
+
verdict and auto-blocklists serious offenders.
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
guard = ops.defense()
|
|
153
|
+
|
|
154
|
+
verdict = guard.analyze({
|
|
155
|
+
"ip": "203.0.113.9",
|
|
156
|
+
"path": "/search",
|
|
157
|
+
"query": "q=' UNION SELECT password FROM users--",
|
|
158
|
+
})
|
|
159
|
+
if verdict.blocked:
|
|
160
|
+
return Response(status=403)
|
|
161
|
+
|
|
162
|
+
# behavioral: repeated failed logins from one IP -> brute force / stuffing
|
|
163
|
+
verdict, incident = guard.inspect({"ip": "203.0.113.9", "auth_failed": True, "user": "admin"})
|
|
164
|
+
|
|
165
|
+
# framework hook
|
|
166
|
+
guard_fn = guard.middleware(on_block=lambda v: log.warning("blocked %s", v.ip))
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Detects: SQL injection, XSS, SSRF, path traversal, command injection, header
|
|
170
|
+
(CRLF) injection, brute force, credential stuffing, rate-limit abuse, and DDoS.
|
|
171
|
+
Code-level attacks escalate to RadhiSOC (fix the code); volumetric attacks are
|
|
172
|
+
blocked directly. Scoring/thresholds are tunable via `DefenseConfig`.
|
|
173
|
+
|
|
174
|
+
## CLI
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
export RADHIOPS_ACCESS_KEY=radhi_demo_yourtrialkey123
|
|
178
|
+
radhiops audit ./my-project
|
|
179
|
+
radhiops audit . --json
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
The `audit` command exits non-zero when high/critical findings are present, so
|
|
183
|
+
it drops straight into CI or a Kiro pre-push hook.
|
|
184
|
+
|
|
185
|
+
## Supported model providers (BYOE)
|
|
186
|
+
|
|
187
|
+
| Provider | name | Needs key? | Extra |
|
|
188
|
+
|----------|------|-----------|-------|
|
|
189
|
+
| OpenAI / compatible | `openai` | yes | `[openai]` |
|
|
190
|
+
| Anthropic | `anthropic` | yes | `[anthropic]` |
|
|
191
|
+
| Google Gemini | `google` | yes | `[google]` |
|
|
192
|
+
| Ollama (local) | `ollama` | no | — |
|
|
193
|
+
| Hugging Face | `huggingface` | yes | `[huggingface]` |
|
|
194
|
+
|
|
195
|
+
Register your own (e.g. an MCP-backed model) with
|
|
196
|
+
`radhiops.register_provider("mymodel", MyProviderClass)`.
|
|
197
|
+
|
|
198
|
+
## Status
|
|
199
|
+
|
|
200
|
+
Phase 0–5: BYOE model layer, access-key client, credit ledger (local + hosted),
|
|
201
|
+
and all five agents — RadhiSOC (security), Repo (Git + pre-push gate),
|
|
202
|
+
Deployment (multi-platform + log diagnosis), Runtime Monitor (health, anomalies,
|
|
203
|
+
crashes), and Cyber Defense (runtime attack detection) — plus the Supabase
|
|
204
|
+
backend. The autonomous cross-agent loop (incidents routing Monitor → Deployment
|
|
205
|
+
→ RadhiSOC → Repo automatically) is next.
|
|
206
|
+
|
|
207
|
+
### Online vs offline
|
|
208
|
+
|
|
209
|
+
```python
|
|
210
|
+
# Offline (default): local credit ledger, demo keys seeded with 500 credits.
|
|
211
|
+
ops = RadhiOps(access_key="radhi_demo_...")
|
|
212
|
+
|
|
213
|
+
# Online: validate + meter against the hosted backend (Supabase edge functions).
|
|
214
|
+
ops = RadhiOps(
|
|
215
|
+
access_key="radhi_live_...",
|
|
216
|
+
offline=False,
|
|
217
|
+
api_base="https://<project-ref>.supabase.co",
|
|
218
|
+
anon_key="<supabase-anon-key>",
|
|
219
|
+
)
|
|
220
|
+
```
|
radhiops-0.0.1/README.md
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# radhiops
|
|
2
|
+
|
|
3
|
+
The Python SDK for **RadhiOps** — the BYOE (Bring Your Own Everything) AI
|
|
4
|
+
Engineering Platform.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
pip install radhiops
|
|
8
|
+
# providers are optional extras — install only what you use:
|
|
9
|
+
pip install 'radhiops[openai]' # or [anthropic], [google], [huggingface], [all]
|
|
10
|
+
# ollama needs no extra (talks to your local server)
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick start
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
from radhiops import RadhiOps
|
|
17
|
+
|
|
18
|
+
# New here? A demo key seeds 500 free credits, fully offline.
|
|
19
|
+
ops = RadhiOps(access_key="radhi_demo_yourtrialkey123")
|
|
20
|
+
|
|
21
|
+
# Run a local security audit (no AI, no network needed).
|
|
22
|
+
report = ops.soc.audit("./my-project")
|
|
23
|
+
print(report.counts()) # {'low': 0, 'medium': 1, 'high': 0, 'critical': 0}
|
|
24
|
+
print(report.passed) # gate result for pre-push / pre-deploy
|
|
25
|
+
|
|
26
|
+
# Bring your own model for AI-assisted remediation.
|
|
27
|
+
ops.use_model("openai", model="gpt-4o-mini", api_key="sk-...")
|
|
28
|
+
print(ops.soc.remediate(report))
|
|
29
|
+
|
|
30
|
+
# Or run a fully local model via Ollama — no API key:
|
|
31
|
+
ops.use_model("ollama", model="llama3.1")
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Repo agent — Git in plain English
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
repo = ops.repo("./my-project")
|
|
38
|
+
|
|
39
|
+
repo.command("what changed?")
|
|
40
|
+
repo.command('commit "fix: handle null user"')
|
|
41
|
+
repo.command("create a new branch feature/login")
|
|
42
|
+
repo.command("merge dev into main")
|
|
43
|
+
|
|
44
|
+
# push() runs a RadhiSOC security gate first and BLOCKS on high/critical
|
|
45
|
+
# findings unless you explicitly override.
|
|
46
|
+
result = repo.push()
|
|
47
|
+
if not result.ok:
|
|
48
|
+
print(result.message) # e.g. "Security gate blocked the push: 1 finding"
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Common verbs are parsed by rules (free, offline). Ambiguous phrasing falls back
|
|
52
|
+
to your BYOE model. Protected branches (main/master/prod) require
|
|
53
|
+
`allow_protected=True`, and force pushes use `--force-with-lease`.
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
radhiops repo "push my changes" --path ./my-project
|
|
57
|
+
radhiops repo "merge dev into main"
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Deployment agent — BYOE deploy targets
|
|
61
|
+
|
|
62
|
+
Bring your own platform token. RadhiOps never stores it.
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
# Vercel / Netlify / Render / Railway / Surge
|
|
66
|
+
dep = ops.deploy("vercel", token="<vercel-token>", team_id="team_...")
|
|
67
|
+
|
|
68
|
+
dep.list(limit=5) # recent deployments (normalized)
|
|
69
|
+
d = dep.trigger() # kick a new deployment (where supported)
|
|
70
|
+
final = dep.watch(d.id) # poll until ready/failed
|
|
71
|
+
if final.status.failed:
|
|
72
|
+
diag = dep.diagnose(d.id) # rule-based + AI root-cause from the logs
|
|
73
|
+
print(diag.rule_based["suggestion"])
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
| Platform | name | Scope option | Triggers deploys |
|
|
77
|
+
|----------|------|--------------|------------------|
|
|
78
|
+
| Vercel | `vercel` | `team_id` (optional) | via git integration |
|
|
79
|
+
| Netlify | `netlify` | `site_id` | yes (build) |
|
|
80
|
+
| Render | `render` | `service_id` | yes |
|
|
81
|
+
| Railway | `railway` | `service_id` | (monitor) |
|
|
82
|
+
| Surge | `surge` | `domain` | yes (CLI) |
|
|
83
|
+
|
|
84
|
+
The log diagnoser recognises common failures (missing modules, unset env vars,
|
|
85
|
+
OOM, port conflicts, lockfile mismatches) with zero credits; anything it can't
|
|
86
|
+
classify is escalated to your BYOE model.
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
radhiops deploy render status --id dep_123 --token $RENDER_TOKEN --opt service_id=srv_abc
|
|
90
|
+
radhiops deploy vercel diagnose --id dpl_123 --token $VERCEL_TOKEN
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Runtime Monitor — production health & incidents
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
mon = ops.monitor()
|
|
97
|
+
mon.add_target("api", "https://myapp.com/health", contains="ok")
|
|
98
|
+
mon.add_target("web", "https://myapp.com")
|
|
99
|
+
|
|
100
|
+
mon.poll() # probe every target once
|
|
101
|
+
for inc in mon.evaluate(): # anomalies -> incidents
|
|
102
|
+
print(inc.severity, inc.summary, "->", inc.escalate_to)
|
|
103
|
+
|
|
104
|
+
# crash detection from a runtime log stream
|
|
105
|
+
crash = mon.ingest_logs("api", ["Traceback (most recent call last):", "MemoryError"])
|
|
106
|
+
|
|
107
|
+
# or run a monitoring loop with a callback per incident
|
|
108
|
+
mon.watch(rounds=10, interval=30, on_incident=lambda i: print(i.to_dict()))
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Detects: endpoint down (consecutive failures), error-rate spikes, latency
|
|
112
|
+
degradation (p95), and runtime crashes (OOM, segfault, unhandled exceptions,
|
|
113
|
+
restart loops, DB connection failures). Each incident is tagged with the agent
|
|
114
|
+
that should handle it next (`DeploymentAgent`, `CyberDefenseAgent`), ready for
|
|
115
|
+
the autonomous loop. Thresholds are tunable via `Thresholds`.
|
|
116
|
+
|
|
117
|
+
## Cyber Defense agent — runtime attack detection
|
|
118
|
+
|
|
119
|
+
Feed inbound requests through the guard; it returns an allow/challenge/block
|
|
120
|
+
verdict and auto-blocklists serious offenders.
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
guard = ops.defense()
|
|
124
|
+
|
|
125
|
+
verdict = guard.analyze({
|
|
126
|
+
"ip": "203.0.113.9",
|
|
127
|
+
"path": "/search",
|
|
128
|
+
"query": "q=' UNION SELECT password FROM users--",
|
|
129
|
+
})
|
|
130
|
+
if verdict.blocked:
|
|
131
|
+
return Response(status=403)
|
|
132
|
+
|
|
133
|
+
# behavioral: repeated failed logins from one IP -> brute force / stuffing
|
|
134
|
+
verdict, incident = guard.inspect({"ip": "203.0.113.9", "auth_failed": True, "user": "admin"})
|
|
135
|
+
|
|
136
|
+
# framework hook
|
|
137
|
+
guard_fn = guard.middleware(on_block=lambda v: log.warning("blocked %s", v.ip))
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Detects: SQL injection, XSS, SSRF, path traversal, command injection, header
|
|
141
|
+
(CRLF) injection, brute force, credential stuffing, rate-limit abuse, and DDoS.
|
|
142
|
+
Code-level attacks escalate to RadhiSOC (fix the code); volumetric attacks are
|
|
143
|
+
blocked directly. Scoring/thresholds are tunable via `DefenseConfig`.
|
|
144
|
+
|
|
145
|
+
## CLI
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
export RADHIOPS_ACCESS_KEY=radhi_demo_yourtrialkey123
|
|
149
|
+
radhiops audit ./my-project
|
|
150
|
+
radhiops audit . --json
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
The `audit` command exits non-zero when high/critical findings are present, so
|
|
154
|
+
it drops straight into CI or a Kiro pre-push hook.
|
|
155
|
+
|
|
156
|
+
## Supported model providers (BYOE)
|
|
157
|
+
|
|
158
|
+
| Provider | name | Needs key? | Extra |
|
|
159
|
+
|----------|------|-----------|-------|
|
|
160
|
+
| OpenAI / compatible | `openai` | yes | `[openai]` |
|
|
161
|
+
| Anthropic | `anthropic` | yes | `[anthropic]` |
|
|
162
|
+
| Google Gemini | `google` | yes | `[google]` |
|
|
163
|
+
| Ollama (local) | `ollama` | no | — |
|
|
164
|
+
| Hugging Face | `huggingface` | yes | `[huggingface]` |
|
|
165
|
+
|
|
166
|
+
Register your own (e.g. an MCP-backed model) with
|
|
167
|
+
`radhiops.register_provider("mymodel", MyProviderClass)`.
|
|
168
|
+
|
|
169
|
+
## Status
|
|
170
|
+
|
|
171
|
+
Phase 0–5: BYOE model layer, access-key client, credit ledger (local + hosted),
|
|
172
|
+
and all five agents — RadhiSOC (security), Repo (Git + pre-push gate),
|
|
173
|
+
Deployment (multi-platform + log diagnosis), Runtime Monitor (health, anomalies,
|
|
174
|
+
crashes), and Cyber Defense (runtime attack detection) — plus the Supabase
|
|
175
|
+
backend. The autonomous cross-agent loop (incidents routing Monitor → Deployment
|
|
176
|
+
→ RadhiSOC → Repo automatically) is next.
|
|
177
|
+
|
|
178
|
+
### Online vs offline
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
# Offline (default): local credit ledger, demo keys seeded with 500 credits.
|
|
182
|
+
ops = RadhiOps(access_key="radhi_demo_...")
|
|
183
|
+
|
|
184
|
+
# Online: validate + meter against the hosted backend (Supabase edge functions).
|
|
185
|
+
ops = RadhiOps(
|
|
186
|
+
access_key="radhi_live_...",
|
|
187
|
+
offline=False,
|
|
188
|
+
api_base="https://<project-ref>.supabase.co",
|
|
189
|
+
anon_key="<supabase-anon-key>",
|
|
190
|
+
)
|
|
191
|
+
```
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "radhiops"
|
|
7
|
+
version = "0.0.1"
|
|
8
|
+
description = "RadhiOps — BYOE AI Engineering Platform SDK"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "Apache-2.0" }
|
|
12
|
+
authors = [{ name = "RadhiOps" }]
|
|
13
|
+
keywords = ["ai", "agents", "devops", "security", "byoe", "mcp"]
|
|
14
|
+
dependencies = [
|
|
15
|
+
"httpx>=0.27",
|
|
16
|
+
"pydantic>=2.6",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
[project.optional-dependencies]
|
|
20
|
+
openai = ["openai>=1.30"]
|
|
21
|
+
anthropic = ["anthropic>=0.34"]
|
|
22
|
+
google = ["google-generativeai>=0.7"]
|
|
23
|
+
huggingface = ["huggingface-hub>=0.24"]
|
|
24
|
+
# ollama uses its local HTTP API via httpx — no extra dep required.
|
|
25
|
+
all = [
|
|
26
|
+
"openai>=1.30",
|
|
27
|
+
"anthropic>=0.34",
|
|
28
|
+
"google-generativeai>=0.7",
|
|
29
|
+
"huggingface-hub>=0.24",
|
|
30
|
+
]
|
|
31
|
+
dev = [
|
|
32
|
+
"pytest>=8.0",
|
|
33
|
+
"pytest-asyncio>=0.23",
|
|
34
|
+
"ruff>=0.5",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[project.scripts]
|
|
38
|
+
radhiops = "radhiops.cli:main"
|
|
39
|
+
|
|
40
|
+
[tool.hatch.build.targets.wheel]
|
|
41
|
+
packages = ["radhiops"]
|
|
42
|
+
|
|
43
|
+
[tool.pytest.ini_options]
|
|
44
|
+
asyncio_mode = "auto"
|
|
45
|
+
testpaths = ["tests"]
|
|
46
|
+
|
|
47
|
+
[tool.ruff]
|
|
48
|
+
line-length = 100
|
|
49
|
+
target-version = "py310"
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""RadhiOps — BYOE AI Engineering Platform SDK."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__version__ = "0.0.1"
|
|
6
|
+
|
|
7
|
+
from .client import RadhiOps
|
|
8
|
+
from .exceptions import (
|
|
9
|
+
AgentError,
|
|
10
|
+
AuthError,
|
|
11
|
+
InsufficientCreditsError,
|
|
12
|
+
ProviderError,
|
|
13
|
+
RadhiOpsError,
|
|
14
|
+
SubscriptionRequiredError,
|
|
15
|
+
)
|
|
16
|
+
from .plans import PLANS, Plan, upgrade_hint
|
|
17
|
+
from .orchestrator import AutonomyMode, Orchestrator
|
|
18
|
+
from .providers import (
|
|
19
|
+
ChatResult,
|
|
20
|
+
Message,
|
|
21
|
+
ModelProvider,
|
|
22
|
+
available_providers,
|
|
23
|
+
get_provider,
|
|
24
|
+
register_provider,
|
|
25
|
+
)
|
|
26
|
+
from .deploy import (
|
|
27
|
+
Deployment,
|
|
28
|
+
DeployStatus,
|
|
29
|
+
available_deploy_providers,
|
|
30
|
+
get_deploy_provider,
|
|
31
|
+
register_deploy_provider,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
__all__ = [
|
|
35
|
+
"__version__",
|
|
36
|
+
"RadhiOps",
|
|
37
|
+
"RadhiOpsError",
|
|
38
|
+
"AuthError",
|
|
39
|
+
"AgentError",
|
|
40
|
+
"ProviderError",
|
|
41
|
+
"InsufficientCreditsError",
|
|
42
|
+
"SubscriptionRequiredError",
|
|
43
|
+
"PLANS",
|
|
44
|
+
"Plan",
|
|
45
|
+
"upgrade_hint",
|
|
46
|
+
"AutonomyMode",
|
|
47
|
+
"Orchestrator",
|
|
48
|
+
"ChatResult",
|
|
49
|
+
"Message",
|
|
50
|
+
"ModelProvider",
|
|
51
|
+
"available_providers",
|
|
52
|
+
"get_provider",
|
|
53
|
+
"register_provider",
|
|
54
|
+
"Deployment",
|
|
55
|
+
"DeployStatus",
|
|
56
|
+
"available_deploy_providers",
|
|
57
|
+
"get_deploy_provider",
|
|
58
|
+
"register_deploy_provider",
|
|
59
|
+
]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""RadhiOps agents."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from .base import Agent
|
|
6
|
+
from .cyberdefense import CyberDefenseAgent
|
|
7
|
+
from .deployment import DeploymentAgent
|
|
8
|
+
from .monitor import RuntimeMonitor
|
|
9
|
+
from .repo import RepoAgent
|
|
10
|
+
from .soc import RadhiSOC
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"Agent",
|
|
14
|
+
"RadhiSOC",
|
|
15
|
+
"RepoAgent",
|
|
16
|
+
"DeploymentAgent",
|
|
17
|
+
"RuntimeMonitor",
|
|
18
|
+
"CyberDefenseAgent",
|
|
19
|
+
]
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Base class shared by every RadhiOps agent.
|
|
2
|
+
|
|
3
|
+
An agent is given:
|
|
4
|
+
- a reference to the :class:`RadhiOps` client (for credits/auth/telemetry)
|
|
5
|
+
- an optional BYOE model provider (the user's own model)
|
|
6
|
+
|
|
7
|
+
Agents expose high-level capabilities (audit, deploy, monitor, ...). Each
|
|
8
|
+
capability that consumes platform resources reports its credit cost through
|
|
9
|
+
``self._charge(...)`` so usage stays transparent.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from typing import TYPE_CHECKING, Any
|
|
15
|
+
|
|
16
|
+
from ..providers.base import Message, ModelProvider
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from ..client import RadhiOps
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Agent:
|
|
23
|
+
#: Human-facing name, e.g. "RadhiSOC".
|
|
24
|
+
name: str = "Agent"
|
|
25
|
+
|
|
26
|
+
def __init__(self, client: "RadhiOps", model: ModelProvider | None = None) -> None:
|
|
27
|
+
self.client = client
|
|
28
|
+
self._model = model
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def model(self) -> ModelProvider:
|
|
32
|
+
"""The BYOE model bound to this agent (falls back to the client default)."""
|
|
33
|
+
m = self._model or self.client.default_model
|
|
34
|
+
if m is None:
|
|
35
|
+
from ..exceptions import ProviderError
|
|
36
|
+
|
|
37
|
+
raise ProviderError(
|
|
38
|
+
f"{self.name} needs a model. Pass model=... or set a default "
|
|
39
|
+
"provider/model on the RadhiOps client."
|
|
40
|
+
)
|
|
41
|
+
return m
|
|
42
|
+
|
|
43
|
+
def _ask(self, system: str, user: str, **opts: Any) -> str:
|
|
44
|
+
"""Convenience: single-turn prompt to the bound model."""
|
|
45
|
+
result = self.model.chat(
|
|
46
|
+
[Message("system", system), Message("user", user)], **opts
|
|
47
|
+
)
|
|
48
|
+
return result.text
|
|
49
|
+
|
|
50
|
+
def _charge(self, credits: int, action: str) -> None:
|
|
51
|
+
"""Record credit usage for an action via the client."""
|
|
52
|
+
self.client._consume_credits(credits, f"{self.name}:{action}")
|