diff --git a/.gitignore b/.gitignore
index faf7761..f0df0c5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,10 @@ hyperion.db
__pycache__/
*.pyc
.env
+*.bak*
+*.patchbak
+*.roundbak
+*.impbak
+*.tgz
+*.nft
+hyperion.env
diff --git a/app.py b/app.py
index dbf1375..ca68130 100644
--- a/app.py
+++ b/app.py
@@ -35,10 +35,17 @@ app.secret_key = os.environ.get("HYPERION_SECRET", "hyperion-secret-" + str(int(
# ---------------------------------------------------------------------------
# Config (from Phase 2 state). Overridable via env.
# ---------------------------------------------------------------------------
-BTCPAY_URL = os.environ.get("HYPERION_BTCPAY_URL", "https://10.30.20.140")
-BTCPAY_STORE = os.environ.get("HYPERION_BTCPAY_STORE", "77rHbzqFf1cJBjM41edVa8HzeRhiQVdHuJfmAuRoBjDE")
-BTCPAY_KEY = os.environ.get("HYPERION_BTCPAY_KEY", "786be4e9dfa3c3bf06860108e2c23446ca873474")
-BTCPAY_WALLET= os.environ.get("HYPERION_BTCPAY_WALLET","xpub6BhBoqZRiqkqthjYriiybMj5P2Fru26Bmu4WJ3dZcjoHZFquBRVqGNYq8pksuchSDe5bsqXHp7dU1ec2tmdbSqJsHw4DnL9uUfqSNSyBzyh")
+BTCPAY_URL = os.environ["HYPERION_BTCPAY_URL"]
+BTCPAY_STORE = os.environ["HYPERION_BTCPAY_STORE"]
+BTCPAY_KEY = os.environ["HYPERION_BTCPAY_KEY"]
+BTCPAY_WALLET= os.environ["HYPERION_BTCPAY_WALLET"]
+
+# Stripe (card payments) — parallel rail to BTCPay. Empty strings = disabled.
+STRIPE_SK = os.environ.get("HYPERION_STRIPE_SK", "")
+STRIPE_PK = os.environ.get("HYPERION_STRIPE_PK", "")
+STRIPE_WHSEC = os.environ.get("HYPERION_STRIPE_WHSEC", "")
+STRIPE_API = "https://api.stripe.com/v1"
+STRIPE_REDIRECT = "https://hyperion.thetempleofdoom.com"
PRICE_USD = 19.0 # Pro $19/mo (from state pricing)
PLAN_MONTHS = [1, 6, 12] # one-price default; monthly
@@ -91,6 +98,13 @@ def init_db():
"""
)
db.commit()
+ # migrate: Stripe columns (idempotent)
+ cols = [r[1] for r in db.execute("PRAGMA table_info(invoices)").fetchall()]
+ if "provider" not in cols:
+ db.execute("ALTER TABLE invoices ADD COLUMN provider TEXT DEFAULT 'btcpay'")
+ if "stripe_session_id" not in cols:
+ db.execute("ALTER TABLE invoices ADD COLUMN stripe_session_id TEXT")
+ db.commit()
db.close()
# ---------------------------------------------------------------------------
@@ -119,6 +133,51 @@ def fetch_invoice(inv_id):
r.raise_for_status()
return r.json()
+# ---------------------------------------------------------------------------
+# Stripe helpers (card payments) — reusable, no stripe SDK dependency (requests only)
+# ---------------------------------------------------------------------------
+def stripe_headers():
+ return {"Authorization": "Bearer " + STRIPE_SK}
+
+def create_stripe_checkout(user_id, email):
+ """Create a Stripe Checkout Session for Pro ($19/mo). Returns dict with id + url."""
+ payload = {
+ "mode": "payment",
+ "success_url": STRIPE_REDIRECT + "/dashboard?paid=stripe&session_id={CHECKOUT_SESSION_ID}",
+ "cancel_url": STRIPE_REDIRECT + "/dashboard?canceled=1",
+ "client_reference_id": str(user_id),
+ "metadata[user_id]": str(user_id),
+ "metadata[plan]": "pro",
+ "line_items[0][price_data][currency]": "usd",
+ "line_items[0][price_data][product_data][name]": "Hyperion Pro (1 month)",
+ "line_items[0][price_data][unit_amount]": str(int(PRICE_USD * 100)),
+ "line_items[0][quantity]": "1",
+ "payment_method_types[0]": "card",
+ }
+ if email:
+ payload["customer_email"] = email
+ r = requests.post(f"{STRIPE_API}/checkout/sessions", headers=stripe_headers(), data=payload, timeout=20)
+ r.raise_for_status()
+ return r.json()
+
+def verify_stripe_webhook(payload_bytes, sig_header):
+ """Verify Stripe-Signature (v1 scheme = HMAC-SHA256(whsec, 't.payload')). Returns event dict or None."""
+ if not STRIPE_WHSEC or not sig_header:
+ return None
+ parts = {}
+ for kv in sig_header.split(","):
+ k, _, v = kv.partition("=")
+ parts[k.strip()] = v.strip()
+ ts = parts.get("t", "")
+ sig = parts.get("v1", "")
+ if not ts or not sig:
+ return None
+ signed = f"{ts}.{payload_bytes.decode('utf-8', 'replace')}".encode()
+ expected = hmac.new(STRIPE_WHSEC.encode(), signed, hashlib.sha256).hexdigest()
+ if not hmac.compare_digest(expected, sig):
+ return None
+ return json.loads(payload_bytes.decode("utf-8", "replace"))
+
PAID_STATES = {"Paid", "Settled", "Confirmed", "Expired-and-paid"}
def now_iso():
@@ -180,11 +239,11 @@ nav .wrap{display:flex;align-items:center;justify-content:space-between;height:6
.menu a{color:var(--muted);padding:8px 12px;border-radius:9px;font-size:.92rem;font-weight:500}
.menu a:hover{color:var(--text);background:var(--surface)}
.btn{display:inline-flex;align-items:center;gap:8px;padding:10px 18px;border-radius:10px;
- font-weight:600;font-size:.92rem;cursor:pointer;border:1px solid var(--line);
+ font-weight:600;font-size:.92rem;cursor:pointer;border:1px solid var(--line);letter-spacing:.01em;
background:var(--surface);color:var(--text);transition:.18s}
.btn:hover{transform:translateY(-1px);border-color:#3a4157}
.btn.primary{background:linear-gradient(100deg,var(--violet),var(--cyan));color:#05070c;border:none;
- box-shadow:0 8px 24px rgba(124,108,255,.35)}
+ box-shadow:0 12px 32px rgba(124,108,255,.45)}
.btn.primary:hover{box-shadow:0 12px 32px rgba(34,211,238,.45)}
.btn.ghost{background:transparent}
.btn.sm{padding:7px 13px;font-size:.85rem}
@@ -200,7 +259,7 @@ nav .wrap{display:flex;align-items:center;justify-content:space-between;height:6
h1{font-size:clamp(2.6rem,6vw,4.6rem);line-height:1.02;margin:22px 0 18px;font-weight:800;letter-spacing:-.02em}
h1 .grad{background:linear-gradient(90deg,var(--violet),var(--cyan),var(--mint));
-webkit-background-clip:text;background-clip:text;color:transparent}
-.lead{font-size:1.18rem;color:var(--muted);max-width:640px}
+.lead{font-size:1.18rem;color:var(--muted);max-width:640px;line-height:1.7}
.cta{display:flex;gap:14px;margin-top:34px;flex-wrap:wrap}
/* grid / cards */
.grid{display:grid;gap:20px}
@@ -208,7 +267,7 @@ h1 .grad{background:linear-gradient(90deg,var(--violet),var(--cyan),var(--mint))
.grid.c2{grid-template-columns:repeat(auto-fit,minmax(280px,1fr))}
.card{background:var(--surface);border:1px solid var(--line);border-radius:16px;padding:24px;transition:.2s}
.card:hover{border-color:#3a4157;transform:translateY(-2px)}
-.card h3{margin:0 0 6px;font-size:1.08rem}
+.card h3{margin:0 0 6px;font-size:1.08rem;letter-spacing:-.01em}
.card p{margin:0;color:var(--muted);font-size:.95rem}
.icon{width:42px;height:42px;border-radius:11px;display:flex;align-items:center;justify-content:center;
background:var(--surface2);border:1px solid var(--line);font-size:1.2rem;margin-bottom:16px}
@@ -255,12 +314,12 @@ input:focus{border-color:var(--violet);box-shadow:0 0 0 3px rgba(124,108,255,.2)
.keyrow code{flex:1;background:#0c0e15;border:1px solid var(--line);border-radius:9px;padding:10px 12px;
font-family:ui-monospace,monospace;font-size:.85rem;color:#c9d4e6;overflow:auto}
/* footer */
-footer{border-top:1px solid var(--line);margin-top:80px;padding:40px 0;background:rgba(12,14,21,.5)}
+footer{border-top:1px solid var(--line);margin-top:80px;padding:72px 0;background:rgba(12,14,21,.5)}
footer .wrap{display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:20px}
footer .flinks{display:flex;gap:22px;flex-wrap:wrap}
footer .flinks a{color:var(--muted);font-size:.9rem}
footer .flinks a:hover{color:var(--text)}
-footer .copy{color:var(--dim);font-size:.85rem;letter-spacing:.02em;line-height:1.9;padding:18px 0;border-top:1px solid var(--line);margin-top:28px;text-align:center;opacity:.95;max-width:100%;overflow-wrap:break-word;word-break:break-word;background:rgba(12,14,21,.3);border-radius:8px;box-shadow:0 4px 12px rgba(0,0,0,.2);backdrop-filter:blur(4px)}
+footer .copy{color:var(--dim);font-size:.85rem;letter-spacing:.02em;line-height:1.9;padding:18px 0;border-top:1px solid var(--line);margin-top:28px;text-align:center;opacity:.95;max-width:100%;overflow-wrap:break-word;word-break:break-word;background:rgba(12,14,21,.3);border-radius:8px;box-shadow:0 4px 12px rgba(0,0,0,.2);backdrop-filter:blur(4px);border:1px solid var(--line)}
.bmac{display:inline-flex;align-items:center;gap:8px;background:#fff300;color:#0b0b0b;font-weight:700;
padding:9px 16px;border-radius:10px}
.bmac svg{width:16px;height:16px}
@@ -323,17 +382,19 @@ def page(title, body, user=None):
return r"""
Subscription
-
Dial up to Pro — $%(price)s / month, paid in Bitcoin through BTCPay. No KYC, no card.
+
Dial up to Pro — $%(price)s / month. Pay in Bitcoin (BTCPay) or by card (Stripe).
-
Subscribe
+
Pay with Bitcoin
+
Pay with card
Check payment
Re-open invoice
@@ -564,7 +636,22 @@ def dashboard_body(u, invoice, just_paid=False):
.catch(function(e){document.getElementById('paystatus').textContent='Error: '+e;});
};
}
- bindSubscribe(); bindCheck();
+ function bindCard(){
+ var b=document.getElementById('subscribe_card');
+ if(!b) return;
+ b.disabled = (plan==='pro');
+ b.onclick=function(){
+ document.getElementById('paystatus').textContent='Opening Stripe checkout...';
+ fetch('/api/subscribe_stripe',{method:'POST',headers:{'Content-Type':'application/json'},body:'{}'})
+ .then(function(r){return r.json();})
+ .then(function(d){
+ if(d.checkout_link){ window.location=d.checkout_link; }
+ else { document.getElementById('paystatus').textContent='Could not open checkout: '+(d.error||'?'); }
+ })
+ .catch(function(e){document.getElementById('paystatus').textContent='Error: '+e;});
+ };
+ }
+ bindSubscribe(); bindCheck(); bindCard();
if(pendingInvoice){
document.getElementById('relink').style.display='inline-flex';
document.getElementById('relink').href=pendingInvoice.checkout_link;
@@ -588,20 +675,110 @@ def about_body():
About
Built for the world of autonomous agents.
- Hyperion is a B2B infrastructure play: a hosted, Bitcoin-billed marketplace for MCP tools
- that AI agents consume machine-to-machine. No accounts that need a human, no cards, no KYC —
- just a key, a meter, and sats. 99.9% uptime. Open source. SOC 2 Type II.
+ Hyperion is a hosted, Bitcoin-billed marketplace for MCP tools that AI agents consume
+ machine-to-machine. No accounts that need a human, no cards, no KYC — just a key, a meter, and sats.
-
⚁
MCP-native The catalog is itself an MCP server, so any agent can speak it.
-
◎
Bitcoin only Settled on-chain through BTCPay. Payable in sats, private, no intermediary.
-
🔒
No KYC Username and password. Your key is what you are. That is the whole identity story. 24/7 support. GDPR compliant. SOC 2 Type II. ISO 27001. 99.9% Uptime.
+
⚁
MCP-native The catalog is itself an MCP server, so any agent can discover and call it directly — zero human triage.
+
◎
Bitcoin only Every call is metered in sats and settled on-chain through BTCPay. No cards, no intermediaries, no chargebacks.
+
🔒
No KYC Register with a username and password, get an API key, and you're in. Your key is your identity — nothing else.
+
+
+
How it works
+
Three steps, no humans anywhere in the loop.
+
+
①
Discover Point your agent at the catalog. Tools self-describe through MCP.
+
②
Subscribe Grab an API key. The free tier includes 100 calls a month.
+
③
Settle Calls are metered in sats and settled in Bitcoin. No invoices to chase.
Open source. Grab the code, read every line, ship your own node if you like:
-
View on Gitea →
-""" % {"github": GITHUB_URL}
+
View on Gitea →
+""".replace("__GITHUB__", GITHUB_URL)
+
+def privacy_body():
+ return r"""
+
+ Privacy
+ No KYC. No tracking. No nonsense .
+ Hyperion stores the minimum needed to meter and settle an agent's usage. Nothing else.
+
+
+
What we store
+
+
Account A username, a salted password hash, and your API key. No email required, no name, no address.
+
Usage Call counts and timestamps per key, used only to meter your plan and enforce the free tier.
+
Payments BTCPay invoices. Bitcoin settles on-chain — we never see a card, a bank, or an identity.
+
What we don't do Sell data, run tracking pixels, or fingerprint visitors. The customer here is an agent, not a person.
+
+"""
+
+def terms_body():
+ return r"""
+
+ Terms
+ Plain-languageterms .
+
+
+
+
Service Hyperion provides metered access to MCP tools, settled in Bitcoin. It is provided as-is, with no warranty of any kind.
+
Payment Subscriptions are paid in Bitcoin through BTCPay. Payments are final; Bitcoin transactions cannot be reversed.
+
Usage You're responsible for what your agents call. Don't use tools to break laws or harm others.
+
Termination We can suspend keys that abuse the platform. You can stop anytime — there's nothing to cancel.
+
+"""
+
+def status_body():
+ return r"""
+
+ Status
+ All systemsoperational .
+ Live health is exposed at /health for agents to poll programmatically.
+
+
+
+
Catalog Operational — MCP discovery responding.
+
Billing Operational — BTCPay settling invoices.
+
Tool calls Operational — metered execution online.
+
+"""
+
+def docs_body():
+ return r"""
+
+ Docs
+ Speak to it like an agent .
+ Hyperion exposes a small, MCP-native surface. Everything below is callable with a signed API key.
+
+
+
Endpoints
+
+
/api/catalogList every published tool with its description and pricing.
+
/api/tools/<id>/callInvoke a tool. Metered in sats, settled through BTCPay.
+
/api/subscribeCreate a Pro subscription invoice, paid in Bitcoin.
+
/mcpThe MCP endpoint itself — point any MCP client at it to discover tools.
+
+
+
Auth is a Bearer token in the Authorization header. Grab your key from the dashboard.
+"""
+
+def faq_body():
+ return r"""
+
+
+
+
What is Hyperion? A marketplace where AI agents publish MCP tools and other agents discover and call them, settled in Bitcoin.
+
What's an MCP tool? A self-describing capability any MCP-compatible agent can invoke without human setup.
+
Do I need a card or ID? No. A username and password, then pay in Bitcoin. No KYC, no card, no personal data.
+
What does the free tier include? 100 calls a month, forever. Pro lifts the cap and unlocks every tool.
+
How do payments work? BTCPay generates an invoice, you pay on-chain, and your key upgrades the moment it settles.
+
+
+""".replace("__GITHUB__", GITHUB_URL)
# ---------------------------------------------------------------------------
# Routes — public pages
@@ -625,6 +802,26 @@ def about():
return redirect("/dashboard")
return render("About", about_body())
+@app.route("/privacy")
+def privacy_page():
+ return render("Privacy", privacy_body())
+
+@app.route("/terms")
+def terms_page():
+ return render("Terms", terms_body())
+
+@app.route("/status")
+def status_page():
+ return render("Status", status_body())
+
+@app.route("/docs")
+def docs_page():
+ return render("Docs", docs_body())
+
+@app.route("/faq")
+def faq_page():
+ return render("FAQ", faq_body())
+
@app.route("/health")
def health():
db = get_db()
@@ -724,6 +921,28 @@ def api_subscribe():
db.commit()
return jsonify(btcpay_invoice_id=inv_id, checkout_link=link)
+@app.route("/api/subscribe_stripe", methods=["POST"])
+@login_required
+def api_subscribe_stripe():
+ uid = session["user_id"]
+ db = get_db()
+ u = db.execute("SELECT * FROM users WHERE id=?", (uid,)).fetchone()
+ if not u:
+ return jsonify(error="user not found"), 404
+ if u["plan"] == "pro":
+ return jsonify(error="already on pro")
+ if not STRIPE_SK:
+ return jsonify(error="stripe not configured"), 503
+ try:
+ sess = create_stripe_checkout(uid, u["email"])
+ except Exception as e:
+ return jsonify(error=str(e)[:300]), 502
+ db.execute(
+ "INSERT INTO invoices (user_id,provider,stripe_session_id,checkout_link,amount,currency,status,created_at) VALUES (?,?,?,?,?,?,?,?)",
+ (uid, "stripe", sess.get("id"), sess.get("url"), PRICE_USD, "USD", "pending", now_iso()))
+ db.commit()
+ return jsonify(stripe_session_id=sess.get("id"), checkout_link=sess.get("url"))
+
@app.route("/api/check_payment")
@login_required
def api_check_payment():
@@ -782,6 +1001,30 @@ def webhook_btcpay():
db.commit()
return ("", 200)
+@app.route("/webhook/stripe", methods=["POST"])
+def webhook_stripe():
+ raw = request.get_data()
+ sig = request.headers.get("Stripe-Signature", "")
+ event = verify_stripe_webhook(raw, sig)
+ if event is None:
+ return "bad signature", 400
+ if event.get("type") == "checkout.session.completed":
+ sobj = event.get("data", {}).get("object", {})
+ sid = sobj.get("id")
+ uid = (sobj.get("metadata") or {}).get("user_id")
+ db = get_db()
+ # idempotent: only act on a pending invoice row (Stripe retries deliveries)
+ row = None
+ if sid:
+ row = db.execute("SELECT * FROM invoices WHERE stripe_session_id=? AND status='pending'", (sid,)).fetchone()
+ if row:
+ activate_pro(row["user_id"])
+ db.execute("UPDATE invoices SET status='paid' WHERE id=?", (row["id"],))
+ db.commit()
+ elif uid and not sid:
+ activate_pro(uid)
+ return ("", 200)
+
# ---------------------------------------------------------------------------
# Agent-facing API
# ---------------------------------------------------------------------------
@@ -819,8 +1062,201 @@ def _is_private_url(url):
return True
return False
+def _proxy_styx(styx_tool, args):
+ """Forward a tool call to the STYX MCP server (Tor gateway)."""
+ H = {"Accept": "application/json, text/event-stream", "Content-Type": "application/json"}
+
+ def _parse(body):
+ for line in body.splitlines():
+ line = line.strip()
+ if line.startswith("data:"):
+ return json.loads(line[5:].strip())
+ try:
+ return json.loads(body)
+ except Exception:
+ return {}
+
+ try:
+ init = {"jsonrpc": "2.0", "id": 1, "method": "initialize",
+ "params": {"protocolVersion": "2024-11-05", "capabilities": {}, "clientInfo": {"name": "hyperion", "version": "1.0"}}}
+ r = requests.post("http://10.30.20.167:5060/mcp", json=init, timeout=30, headers=H)
+ r.raise_for_status()
+ sid = r.headers.get("Mcp-Session-Id")
+ call = {"jsonrpc": "2.0", "id": 2, "method": "tools/call",
+ "params": {"name": styx_tool, "arguments": args}}
+ h2 = dict(H)
+ if sid:
+ h2["Mcp-Session-Id"] = sid
+ r2 = requests.post("http://10.30.20.167:5060/mcp", json=call, timeout=30, headers=h2)
+ r2.raise_for_status()
+ data = _parse(r2.text)
+ if data.get("isError") or data.get("error"):
+ return False, str(data.get("error", "styx error"))[:500]
+ content = (data.get("result") or {}).get("content") or []
+ text = "".join(p.get("text", "") for p in content if p.get("type") == "text")
+ try:
+ return True, json.loads(text)
+ except Exception:
+ return True, text
+ except Exception as e:
+ return False, "styx call failed: %s" % str(e)[:300]
+
+
+def _proxy_mobsf(mobsf_tool, args):
+ """Forward a tool call to the MobSF REST API (mobile security scanning)."""
+ MOBSF_URL = "http://10.30.20.165"
+ MOBSF_KEY = "fe31787c0ce2401488b9f8522ac0981ed5fe782bd0ea6467fb2ddd8f1a0354a3"
+ try:
+ if mobsf_tool == "list_scans":
+ r = requests.get(MOBSF_URL + "/api/v1/scans", headers={"Authorization": MOBSF_KEY}, timeout=30)
+ r.raise_for_status()
+ return True, r.json()
+ if mobsf_tool == "report":
+ h = args.get("hash") or args.get("scan_hash")
+ if not h:
+ return False, "hash required"
+ r = requests.get(MOBSF_URL + "/api/v1/report_json", params={"hash": h}, headers={"Authorization": MOBSF_KEY}, timeout=60)
+ if r.status_code == 200:
+ return True, r.json()
+ return False, "report not ready or not found (status %s)" % r.status_code
+ return False, "unknown mobsf tool: %s" % mobsf_tool
+ except Exception as e:
+ return False, "mobsf call failed: %s" % str(e)[:300]
+
+
+def _proxy_titan(titan_tool, args):
+ """Forward a tool call to the TITAN REST API (GPU compute)."""
+ TITAN = "http://10.30.20.29:5059"
+ try:
+ if titan_tool == "beacon":
+ r = requests.get(f"{TITAN}/beacon", timeout=15)
+ return True, r.json()
+ elif titan_tool == "order":
+ r = requests.post(f"{TITAN}/order", json={"credits": int(args.get("credits", 100))}, timeout=30)
+ return True, r.json()
+ elif titan_tool == "generate":
+ key = args.get("api_key", "")
+ r = requests.post(f"{TITAN}/generate",
+ json={"prompt": args.get("prompt", ""), "model": args.get("model", "qwen3.8fast")},
+ headers={"X-API-Key": key}, timeout=180)
+ return True, r.json()
+ elif titan_tool == "stats":
+ r = requests.get(f"{TITAN}/stats", timeout=15)
+ return True, r.json()
+ return False, "unknown titan tool"
+ except Exception as e:
+ return False, "titan call failed: %s" % str(e)[:300]
+
+
+def _proxy_echo(echo_tool, args):
+ """Forward a tool call to the ECHO REST API (voice/TTS)."""
+ import base64
+ ECHO = "http://10.30.20.169:5057"
+ try:
+ if echo_tool == "beacon":
+ r = requests.get(f"{ECHO}/beacon", timeout=15)
+ return True, r.json()
+ elif echo_tool == "voices":
+ r = requests.get(f"{ECHO}/voices", timeout=15)
+ return True, r.json()
+ elif echo_tool == "tts":
+ key = args.get("api_key", "")
+ r = requests.post(f"{ECHO}/tts",
+ json={"text": args.get("text", ""), "voice": args.get("voice", "en-US-JennyNeural")},
+ headers={"X-API-Key": key}, timeout=60)
+ if r.status_code == 200:
+ return True, {"audio_base64": base64.b64encode(r.content).decode(), "mime": "audio/mpeg"}
+ try:
+ return True, r.json()
+ except Exception:
+ return True, {"status": r.status_code, "error": r.text[:200]}
+ elif echo_tool == "order":
+ r = requests.post(f"{ECHO}/order", json={"credits": int(args.get("credits", 50))}, timeout=30)
+ return True, r.json()
+ return False, "unknown echo tool"
+ except Exception as e:
+ return False, "echo call failed: %s" % str(e)[:300]
+
+
+LYNX_URL = "http://10.30.20.83:5059/mcp"
+LYNX_ADMIN = "sk-lynx-admin-7c97d4db9eac0f3a"
+
+def _proxy_lynx(lynx_tool, args):
+ """Forward a tool call to the LYNX MCP endpoint (inspection/steg/recon API)."""
+ a = dict(args or {})
+ a.setdefault("api_key", LYNX_ADMIN)
+ body = {"jsonrpc": "2.0", "id": 1, "method": "tools/call",
+ "params": {"name": lynx_tool, "arguments": a}}
+ try:
+ r = requests.post(LYNX_URL, json=body, timeout=300,
+ headers={"Content-Type": "application/json"})
+ r.raise_for_status()
+ data = r.json()
+ if data.get("error"):
+ return False, str(data.get("error"))[:500]
+ content = (data.get("result") or {}).get("content") or []
+ text = "".join(p.get("text", "") for p in content if p.get("type") == "text")
+ try:
+ return True, json.loads(text)
+ except Exception:
+ return True, text
+ except Exception as e:
+ return False, "lynx call failed: %s" % str(e)[:300]
+
+
+
+
+VEGA_URL = "http://10.30.20.147:8080"
+OSINT_URL = "http://10.30.20.174:8080"
+
+
+def _proxy_vega(vega_tool, args):
+ """Forward a tool call to VEGA (utility/developer tool host)."""
+ try:
+ r = requests.get(f"{VEGA_URL}/api/{vega_tool}", params=args or {}, timeout=15)
+ if r.status_code != 200:
+ return False, f"vega {r.status_code}: {r.text[:200]}"
+ data = r.json()
+ if data.get("error"):
+ return False, str(data["error"])[:300]
+ return True, data.get("result", data)
+ except Exception as e:
+ return False, f"vega call failed: {str(e)[:300]}"
+
+
+def _proxy_osint(osint_tool, args):
+ """Forward a tool call to OSINT Terminal (keyless recon tools)."""
+ q = (args or {}).get("q") or (args or {}).get("query") or ""
+ try:
+ r = requests.get(f"{OSINT_URL}/api/run/{osint_tool}", params={"q": q}, timeout=20)
+ if r.status_code != 200:
+ return False, f"osint {r.status_code}: {r.text[:200]}"
+ data = r.json()
+ if data.get("error"):
+ return False, str(data["error"])[:300]
+ return True, data
+ except Exception as e:
+ return False, f"osint call failed: {str(e)[:300]}"
+
def proxy_tool_call(nexus_name, args, block_private=False):
- """Forward a tool call to Omninexus and return (ok, result)."""
+ """Forward a tool call to Omninexus (or STYX for styx:* tools) and return (ok, result)."""
+ if nexus_name.startswith("styx:"):
+ return _proxy_styx(nexus_name.split(":", 1)[1], args)
+ if nexus_name.startswith("mobsf:"):
+ return _proxy_mobsf(nexus_name.split(":", 1)[1], args)
+ if nexus_name.startswith("titan:"):
+ return _proxy_titan(nexus_name.split(":", 1)[1], args)
+ if nexus_name.startswith("echo:"):
+ return _proxy_echo(nexus_name.split(":", 1)[1], args)
+ if nexus_name.startswith("lynx:"):
+ return _proxy_lynx(nexus_name.split(":", 1)[1], args)
+
+ if nexus_name.startswith("vega:"):
+ return _proxy_vega(nexus_name.split(":", 1)[1], args)
+ if nexus_name.startswith("osint:"):
+ return _proxy_osint(nexus_name.split(":", 1)[1], args)
+
+
if block_private:
target = args.get("url") or ""
if target and _is_private_url(target):
@@ -867,6 +1303,335 @@ CATALOG = [
{"id": "ascii", "name": "ASCII Art", "nexus": "ascii_art_generator", "desc": "Text → ASCII art", "sats": 150, "egress": False},
{"id": "netutils", "name": "Network Utilities", "nexus": "network_utilities", "desc": "URL/subnet/user-agent analysis", "sats": 300, "egress": False},
{"id": "cron", "name": "Cron Calculator", "nexus": "cron_calculator", "desc": "Explain/validate/next-run cron expressions", "sats": 150, "egress": False},
+ {"id": "styx_beacon", "name": "STYX Beacon", "nexus": "styx:styx_beacon", "desc": "Tor gateway status + current exit IP", "sats": 50, "egress": False},
+ {"id": "styx_fetch", "name": "STYX Fetch (Tor)", "nexus": "styx:styx_fetch", "desc": "Fetch a URL through the Tor network", "sats": 300, "egress": True},
+ {"id": "styx_renew", "name": "STYX Renew Circuit", "nexus": "styx:styx_renew", "desc": "Rotate to a fresh Tor circuit/identity", "sats": 100, "egress": False},
+ {"id": "mobsf_scans", "name": "MobSF Scan List", "nexus": "mobsf:list_scans", "desc": "List recent mobile app security scans", "sats": 200, "egress": False},
+ {"id": "mobsf_report", "name": "MobSF Report", "nexus": "mobsf:report", "desc": "Fetch a mobile app security report by hash", "sats": 400, "egress": False},
+ {"id": "titan_beacon", "name": "TITAN GPU Status", "nexus": "titan:beacon", "desc": "Check RTX 4080 GPU compute status", "sats": 0, "egress": False},
+ {"id": "titan_order", "name": "TITAN Credits", "nexus": "titan:order", "desc": "Buy GPU compute credits (BTCPay invoice)", "sats": 0, "egress": False},
+ {"id": "titan_generate", "name": "TITAN Inference", "nexus": "titan:generate", "desc": "Run LLM inference on the RTX 4080 (needs titan key)", "sats": 0, "egress": False},
+ {"id": "echo_beacon", "name": "ECHO Voice Status", "nexus": "echo:beacon", "desc": "Check voice/TTS API status", "sats": 0, "egress": False},
+ {"id": "echo_tts", "name": "ECHO TTS", "nexus": "echo:tts", "desc": "Synthesize speech from text (needs echo key)", "sats": 0, "egress": False},
+ {"id": "echo_order", "name": "ECHO Credits", "nexus": "echo:order", "desc": "Buy voice credits (BTCPay invoice)", "sats": 0, "egress": False},
+ {"id": "lynx_inspect", "name": "LYNX File Inspection", "nexus": "lynx:lynx_inspect_file", "desc": "Inspect a file: hashes/entropy/strings/PE/PDF/Office (base64)", "sats": 200, "egress": False},
+ {"id": "lynx_steg", "name": "LYNX Steganography", "nexus": "lynx:lynx_steg_extract", "desc": "Decode hidden data (steghide + zsteg LSB)", "sats": 300, "egress": False},
+ {"id": "lynx_recon", "name": "LYNX Recon", "nexus": "lynx:lynx_recon", "desc": "Recon external target (nmap/subfinder/nuclei/theHarvester/dnsrecon)", "sats": 400, "egress": True},
+ # --- VEGA (utility/dev tool host) ---
+ {"id": "v_json_format", "name": "JSON Format", "nexus": "vega:json_format", "desc": "Validate + pretty-print JSON", "sats": 100, "egress": False},
+ {"id": "v_json_csv", "name": "JSON → CSV", "nexus": "vega:json_to_csv", "desc": "Flatten a JSON array of objects to CSV", "sats": 100, "egress": False},
+ {"id": "v_json_diff", "name": "JSON Diff", "nexus": "vega:json_diff", "desc": "Diff two JSON values (added/removed/changed)", "sats": 100, "egress": False},
+ {"id": "v_jwt", "name": "JWT Decode", "nexus": "vega:jwt_decode", "desc": "Decode a JWT header + payload", "sats": 100, "egress": False},
+ {"id": "v_hash", "name": "Hash", "nexus": "vega:hash", "desc": "MD5/SHA1/SHA256/SHA512 of a string", "sats": 100, "egress": False},
+ {"id": "v_b64enc", "name": "Base64 Encode", "nexus": "vega:base64_encode", "desc": "Base64-encode text", "sats": 100, "egress": False},
+ {"id": "v_b64dec", "name": "Base64 Decode", "nexus": "vega:base64_decode", "desc": "Base64-decode to text + hex", "sats": 100, "egress": False},
+ {"id": "v_urlenc", "name": "URL Encode", "nexus": "vega:url_encode", "desc": "Percent-encode a string", "sats": 100, "egress": False},
+ {"id": "v_urldec", "name": "URL Decode", "nexus": "vega:url_decode", "desc": "Percent-decode a string", "sats": 100, "egress": False},
+ {"id": "v_urlparse", "name": "URL Parse", "nexus": "vega:url_parse", "desc": "Split a URL into scheme/host/path/query", "sats": 100, "egress": False},
+ {"id": "v_hex2txt", "name": "Hex → Text", "nexus": "vega:hex_to_text", "desc": "Decode hex bytes to text", "sats": 100, "egress": False},
+ {"id": "v_txt2hex", "name": "Text → Hex", "nexus": "vega:text_to_hex", "desc": "Encode text to hex", "sats": 100, "egress": False},
+ {"id": "v_uuid", "name": "UUID", "nexus": "vega:uuid", "desc": "Generate a random UUID v4", "sats": 100, "egress": False},
+ {"id": "v_regex", "name": "Regex Test", "nexus": "vega:regex_test", "desc": "Test a regex, return matches + groups", "sats": 100, "egress": False},
+ {"id": "v_cron", "name": "Cron Explain", "nexus": "vega:cron_explain", "desc": "Break down a 5-field cron expression", "sats": 100, "egress": False},
+ {"id": "v_slug", "name": "Slugify", "nexus": "vega:slugify", "desc": "Slugify text (lowercase, dashes, ascii)", "sats": 100, "egress": False},
+ {"id": "v_case", "name": "Case Convert", "nexus": "vega:case_convert", "desc": "camel/snake/kebab/pascal/title/upper/lower", "sats": 100, "egress": False},
+ {"id": "v_metrics", "name": "String Metrics", "nexus": "vega:string_metrics", "desc": "Length/word/line counts + Shannon entropy", "sats": 100, "egress": False},
+ {"id": "v_htmldec", "name": "HTML Decode", "nexus": "vega:html_decode", "desc": "Decode HTML entities", "sats": 100, "egress": False},
+ {"id": "v_baseconv", "name": "Base Convert", "nexus": "vega:base_convert", "desc": "Convert a number between bases 2-36", "sats": 100, "egress": False},
+ {"id": "v_epoch", "name": "Epoch → Date", "nexus": "vega:epoch", "desc": "Convert a unix epoch to UTC + relative", "sats": 100, "egress": False},
+ {"id": "v_now", "name": "Now", "nexus": "vega:now", "desc": "Current unix time (UTC)", "sats": 100, "egress": False},
+ # --- OSINT Terminal (keyless recon) ---
+ {"id": "o_abusecontact", "name": "Abuse Contact", "nexus": "osint:abusecontact", "desc": "Authoritative abuse email (RIPEstat finder)", "sats": 200, "egress": False},
+ {"id": "o_agify", "name": "Age Predictor", "nexus": "osint:agify", "desc": "Predict age from a first name (agify.io)", "sats": 200, "egress": False},
+ {"id": "o_airquality", "name": "Air Quality", "nexus": "osint:airquality", "desc": "PM2.5/PM10/European AQI at coords (Open-Meteo, no key)", "sats": 200, "egress": False},
+ {"id": "o_antipode", "name": "Antipode", "nexus": "osint:antipode", "desc": "Opposite point on Earth for coords (offline)", "sats": 200, "egress": False},
+ {"id": "o_api_key_scan", "name": "API Key Exposure", "nexus": "osint:api_key_scan", "desc": "Scan GitHub/Pastebin/Google for exposed API keys/secrets", "sats": 200, "egress": False},
+ {"id": "o_archiveorg", "name": "Archive.org Item", "nexus": "osint:archiveorg", "desc": "Internet Archive item metadata", "sats": 200, "egress": False},
+ {"id": "o_arxiv", "name": "arXiv Search", "nexus": "osint:arxiv", "desc": "arXiv paper search by title/author/keyword", "sats": 200, "egress": False},
+ {"id": "o_ascii85", "name": "Ascii85", "nexus": "osint:ascii85", "desc": "Ascii85 encode/decode (offline)", "sats": 200, "egress": False},
+ {"id": "o_asn", "name": "ASN / BGP", "nexus": "osint:asn", "desc": "ASN details or prefixes for an IP", "sats": 200, "egress": False},
+ {"id": "o_asnlookup", "name": "ASN Lookup", "nexus": "osint:asnlookup", "desc": "ASN org/country/prefixes via RIPEstat", "sats": 200, "egress": False},
+ {"id": "o_asrank", "name": "AS Rank (CAIDA)", "nexus": "osint:asrank", "desc": "Global ASN ranking + customer cone size", "sats": 200, "egress": False},
+ {"id": "o_atbash", "name": "Atbash Cipher", "nexus": "osint:atbash", "desc": "Atbash A↔Z mirror cipher (offline, self-inverse)", "sats": 200, "egress": False},
+ {"id": "o_attack_surface", "name": "Attack Surface", "nexus": "osint:attack_surface", "desc": "Map attack surface: services + endpoints + API", "sats": 200, "egress": False},
+ {"id": "o_barcode", "name": "Barcode Validate", "nexus": "osint:barcode", "desc": "EAN-13 / UPC-A check digit validation (offline)", "sats": 200, "egress": False},
+ {"id": "o_base32", "name": "Base32 Encode/Decode", "nexus": "osint:base32", "desc": "RFC 4648 Base32 encode or decode", "sats": 200, "egress": False},
+ {"id": "o_base36", "name": "Base36 Codec", "nexus": "osint:base36", "desc": "Encode int <-> base36, autodetecting direction (offline)", "sats": 200, "egress": False},
+ {"id": "o_base64", "name": "Base64", "nexus": "osint:base64", "desc": "Auto decode/encode base64 (offline)", "sats": 200, "egress": False},
+ {"id": "o_base_convert", "name": "Base Convert", "nexus": "osint:base_convert", "desc": "Binary/octal/decimal/hex number conversion (offline)", "sats": 200, "egress": False},
+ {"id": "o_bgphistory", "name": "BGP History", "nexus": "osint:bgphistory", "desc": "Routing origin history (RIPE Stat)", "sats": 200, "egress": False},
+ {"id": "o_bimi", "name": "BIMI", "nexus": "osint:bimi", "desc": "Brand-indicator (logo) DNS record", "sats": 200, "egress": False},
+ {"id": "o_binarytext", "name": "Binary Text", "nexus": "osint:binarytext", "desc": "Text ↔ 8-bit binary (offline)", "sats": 200, "egress": False},
+ {"id": "o_blockheight", "name": "Block Height", "nexus": "osint:blockheight", "desc": "Current Bitcoin + Ethereum block height", "sats": 200, "egress": False},
+ {"id": "o_bluesky", "name": "Bluesky", "nexus": "osint:bluesky", "desc": "AT Protocol public profile", "sats": 200, "egress": False},
+ {"id": "o_breach_aggregator", "name": "Breach Aggregator", "nexus": "osint:breach_aggregator", "desc": "Aggregate breach databases (XposedOrNot, LeakCheck, etc)", "sats": 200, "egress": False},
+ {"id": "o_breachdb", "name": "BreachDB Search", "nexus": "osint:breachdb", "desc": "Aggregated breach collection search", "sats": 200, "egress": False},
+ {"id": "o_breachdirectory", "name": "Breach Directory", "nexus": "osint:breachdirectory", "desc": "ProxyNova COMB dataset search for credential exposure", "sats": 200, "egress": False},
+ {"id": "o_breachsearch", "name": "Breach Catalog", "nexus": "osint:breachsearch", "desc": "Public HIBP breach metadata search", "sats": 200, "egress": False},
+ {"id": "o_btcaddr", "name": "BTC Address", "nexus": "osint:btcaddr", "desc": "Bitcoin balance/tx via mempool.space", "sats": 200, "egress": False},
+ {"id": "o_btcfees", "name": "BTC Fees", "nexus": "osint:btcfees", "desc": "Recommended Bitcoin fees sat/vB (mempool.space)", "sats": 200, "egress": False},
+ {"id": "o_c2_infrastructure", "name": "C2 Infrastructure", "nexus": "osint:c2_infrastructure", "desc": "Detect C2 infrastructure + hosting", "sats": 200, "egress": False},
+ {"id": "o_caesar", "name": "Caesar Cipher", "nexus": "osint:caesar", "desc": "ROT-N / Caesar brute force, all 25 shifts (offline)", "sats": 200, "egress": False},
+ {"id": "o_casify", "name": "Case Convert", "nexus": "osint:casify", "desc": "snake/camel/Pascal/kebab/CONSTANT case (offline)", "sats": 200, "egress": False},
+ {"id": "o_cdnjs", "name": "cdnjs", "nexus": "osint:cdnjs", "desc": "Hosted JS library version + assets", "sats": 200, "egress": False},
+ {"id": "o_cfradar", "name": "Cloudflare Radar", "nexus": "osint:cfradar", "desc": "Domain rank + categories from Cloudflare Radar", "sats": 200, "egress": False},
+ {"id": "o_checksum", "name": "CRC32/Adler32", "nexus": "osint:checksum", "desc": "CRC32 + Adler32 checksum of input text (offline)", "sats": 200, "egress": False},
+ {"id": "o_chesscom", "name": "Chess.com", "nexus": "osint:chesscom", "desc": "Public player profile + ratings", "sats": 200, "egress": False},
+ {"id": "o_cidr", "name": "CIDR Calculator", "nexus": "osint:cidr", "desc": "Subnet calc: network, mask, host range, count (offline)", "sats": 200, "egress": False},
+ {"id": "o_circlhash", "name": "Hash Lookup", "nexus": "osint:circlhash", "desc": "Known-file lookup (CIRCL hashlookup)", "sats": 200, "egress": False},
+ {"id": "o_clickjacking", "name": "Clickjacking", "nexus": "osint:clickjacking", "desc": "X-Frame-Options + CSP frame-ancestors check", "sats": 200, "egress": False},
+ {"id": "o_cloud", "name": "Cloud Provider", "nexus": "osint:cloud", "desc": "AWS/GCP/Azure/etc. detection + hosting flag", "sats": 200, "egress": False},
+ {"id": "o_codeberg", "name": "Codeberg", "nexus": "osint:codeberg", "desc": "Codeberg/Gitea public user", "sats": 200, "egress": False},
+ {"id": "o_codeforces", "name": "Codeforces", "nexus": "osint:codeforces", "desc": "Codeforces competitive programmer rating & rank", "sats": 200, "egress": False},
+ {"id": "o_color", "name": "Color Parser", "nexus": "osint:color", "desc": "hex/rgb -> rgb/hsl + nearest name (offline)", "sats": 200, "egress": False},
+ {"id": "o_cookies", "name": "Cookie Audit", "nexus": "osint:cookies", "desc": "Secure/HttpOnly/SameSite flag review", "sats": 200, "egress": False},
+ {"id": "o_cors", "name": "CORS Check", "nexus": "osint:cors", "desc": "Origin-reflection / wildcard misconfig", "sats": 200, "egress": False},
+ {"id": "o_cratedownloads", "name": "Crate Downloads", "nexus": "osint:cratedownloads", "desc": "Download totals for a Rust crate", "sats": 200, "egress": False},
+ {"id": "o_crates", "name": "crates.io", "nexus": "osint:crates", "desc": "Rust crate stats + downloads", "sats": 200, "egress": False},
+ {"id": "o_cratestats", "name": "crates.io Stats", "nexus": "osint:cratestats", "desc": "Rust crate downloads, version, repo (no key)", "sats": 200, "egress": False},
+ {"id": "o_crc32", "name": "CRC-32 Checksum", "nexus": "osint:crc32", "desc": "Compute CRC-32 of input", "sats": 200, "egress": False},
+ {"id": "o_credential_stuffing", "name": "Credential Stuffing Risk", "nexus": "osint:credential_stuffing", "desc": "Check breach + stuffing risk", "sats": 200, "egress": False},
+ {"id": "o_crossrefauthor", "name": "Crossref Author", "nexus": "osint:crossrefauthor", "desc": "Works by author/keyword (Crossref)", "sats": 200, "egress": False},
+ {"id": "o_crypto", "name": "Crypto Address", "nexus": "osint:crypto", "desc": "BTC/ETH balance & tx history", "sats": 200, "egress": False},
+ {"id": "o_cryptomarket", "name": "Crypto Market", "nexus": "osint:cryptomarket", "desc": "Global market cap, BTC dominance, 24h volume (CoinGecko)", "sats": 200, "egress": False},
+ {"id": "o_csp_parse", "name": "CSP Analyzer", "nexus": "osint:csp_parse", "desc": "Content-Security-Policy header analysis & grade", "sats": 200, "egress": False},
+ {"id": "o_cve", "name": "CVE Lookup", "nexus": "osint:cve", "desc": "CVE detail + CVSS (CIRCL, no key)", "sats": 200, "egress": False},
+ {"id": "o_cve_poc_checker", "name": "CVE POC Check", "nexus": "osint:cve_poc_checker", "desc": "Check if a CVE has public POC/exploit code", "sats": 200, "egress": False},
+ {"id": "o_cve_severity", "name": "CVE Severity", "nexus": "osint:cve_severity", "desc": "CVSS + EPSS + KEV for a CVE", "sats": 200, "egress": False},
+ {"id": "o_cve_timeline", "name": "CVE Timeline", "nexus": "osint:cve_timeline", "desc": "When a CVE was discussed (Twitter/Reddit/News)", "sats": 200, "egress": False},
+ {"id": "o_cvedetail", "name": "CVE Detail", "nexus": "osint:cvedetail", "desc": "Full CVE record (CVSS, refs) via CIRCL", "sats": 200, "egress": False},
+ {"id": "o_datacite", "name": "DataCite Search", "nexus": "osint:datacite", "desc": "Research datasets/DOIs by keyword", "sats": 200, "egress": False},
+ {"id": "o_datauri", "name": "Data URI Parse", "nexus": "osint:datauri", "desc": "Parse or create data: URIs", "sats": 200, "egress": False},
+ {"id": "o_decode", "name": "Decoder", "nexus": "osint:decode", "desc": "Auto base64/hex/URL-decode + refang", "sats": 200, "egress": False},
+ {"id": "o_dehashed_domain", "name": "DeHashed Domain", "nexus": "osint:dehashed_domain", "desc": "DeHashed public page scrape for domain breach exposure", "sats": 200, "egress": False},
+ {"id": "o_depsdev", "name": "deps.dev", "nexus": "osint:depsdev", "desc": "Open-source insights: versions, default", "sats": 200, "egress": False},
+ {"id": "o_devto", "name": "dev.to", "nexus": "osint:devto", "desc": "Forem/dev.to public profile", "sats": 200, "egress": False},
+ {"id": "o_dirlisting", "name": "Directory Listing", "nexus": "osint:dirlisting", "desc": "Open directory-index exposure (per-target)", "sats": 200, "egress": False},
+ {"id": "o_disasters", "name": "Disasters (GDACS)", "nexus": "osint:disasters", "desc": "Active worldwide disasters: quakes/cyclones/floods (feeds globe)", "sats": 200, "egress": False},
+ {"id": "o_dns", "name": "DNS Records", "nexus": "osint:dns", "desc": "A/AAAA/MX/NS/TXT/CNAME/SOA/CAA records", "sats": 200, "egress": False},
+ {"id": "o_dnsbl", "name": "DNS Blocklist", "nexus": "osint:dnsbl", "desc": "Spamhaus/Barracuda/SORBS/SpamCop check", "sats": 200, "egress": False},
+ {"id": "o_dnsgraph", "name": "DNS Graph (HE)", "nexus": "osint:dnsgraph", "desc": "Hurricane Electric DNS delegation info", "sats": 200, "egress": False},
+ {"id": "o_dnsmx", "name": "MX (DoH)", "nexus": "osint:dnsmx", "desc": "MX records via Google DNS-over-HTTPS", "sats": 200, "egress": False},
+ {"id": "o_dnsprop", "name": "DNS Propagation", "nexus": "osint:dnsprop", "desc": "Compare A records across Google/Cloudflare/Quad9", "sats": 200, "egress": False},
+ {"id": "o_dnsquery", "name": "DNS A Record", "nexus": "osint:dnsquery", "desc": "DNS A record lookup via Google DoH", "sats": 200, "egress": False},
+ {"id": "o_dnsrecon", "name": "DNS Recon", "nexus": "osint:dnsrecon", "desc": "Query ALL DNS record types at once", "sats": 200, "egress": False},
+ {"id": "o_dnsverify", "name": "TXT Verifications", "nexus": "osint:dnsverify", "desc": "Which SaaS a domain is enrolled in (TXT tokens)", "sats": 200, "egress": False},
+ {"id": "o_dockerhub", "name": "Docker Hub Repo", "nexus": "osint:dockerhub", "desc": "Docker Hub repo pulls, stars, last update (no key)", "sats": 200, "egress": False},
+ {"id": "o_doh", "name": "DoH Records", "nexus": "osint:doh", "desc": "Uncommon DNS records (HTTPS/SVCB/TLSA/SRV/NAPTR…)", "sats": 200, "egress": False},
+ {"id": "o_doi", "name": "DOI Resolver", "nexus": "osint:doi", "desc": "Crossref publication metadata for a DOI", "sats": 200, "egress": False},
+ {"id": "o_ean", "name": "Barcode/EAN", "nexus": "osint:ean", "desc": "EAN/UPC check digit + GS1 country prefix (offline)", "sats": 200, "egress": False},
+ {"id": "o_elevation", "name": "Elevation", "nexus": "osint:elevation", "desc": "Ground elevation in metres (Open-Meteo)", "sats": 200, "egress": False},
+ {"id": "o_email", "name": "Email Intel", "nexus": "osint:email", "desc": "Gravatar, MX, disposable detection", "sats": 200, "egress": False},
+ {"id": "o_emailrep", "name": "Email Reputation", "nexus": "osint:emailrep", "desc": "emailrep.io: malicious/spam/breach flags for email (no key, limited)", "sats": 200, "egress": False},
+ {"id": "o_emailsec", "name": "Email Security", "nexus": "osint:emailsec", "desc": "SPF / DMARC / DKIM posture", "sats": 200, "egress": False},
+ {"id": "o_ens", "name": "ENS Resolve", "nexus": "osint:ens", "desc": "ENS name <-> ETH address + avatar", "sats": 200, "egress": False},
+ {"id": "o_epoch", "name": "Epoch Time", "nexus": "osint:epoch", "desc": "Unix timestamp <-> UTC datetime", "sats": 200, "egress": False},
+ {"id": "o_epss", "name": "EPSS Score", "nexus": "osint:epss", "desc": "Exploitation probability (FIRST EPSS)", "sats": 200, "egress": False},
+ {"id": "o_exploit_cve", "name": "CVE Exploits", "nexus": "osint:exploit_cve", "desc": "Exploit-DB + GitHub POCs for a CVE", "sats": 200, "egress": False},
+ {"id": "o_favicon", "name": "Favicon Hash", "nexus": "osint:favicon", "desc": "favicon md5/sha256 for pivoting", "sats": 200, "egress": True},
+ {"id": "o_feeds", "name": "RSS / Atom Feeds", "nexus": "osint:feeds", "desc": "Discover syndication feeds on a site", "sats": 200, "egress": False},
+ {"id": "o_feodoips", "name": "Feodo C2 List", "nexus": "osint:feodoips", "desc": "Is IP on abuse.ch Feodo botnet C2 list", "sats": 200, "egress": False},
+ {"id": "o_flightsnear", "name": "Flights Nearby", "nexus": "osint:flightsnear", "desc": "Live aircraft within ~1° of coords (OpenSky)", "sats": 200, "egress": False},
+ {"id": "o_formaudit", "name": "Form Audit", "nexus": "osint:formaudit", "desc": "Enumerate forms/inputs (login/upload) — attack surface", "sats": 200, "egress": False},
+ {"id": "o_genderize", "name": "Gender Predictor", "nexus": "osint:genderize", "desc": "Predict gender from a first name (genderize.io)", "sats": 200, "egress": False},
+ {"id": "o_geocode", "name": "Geocode", "nexus": "osint:geocode", "desc": "Place name → coordinates (OSM Nominatim)", "sats": 200, "egress": False},
+ {"id": "o_gh_dorking", "name": "GitHub Dork Search", "nexus": "osint:gh_dorking", "desc": "GitHub code search for target string exposure in public repos", "sats": 200, "egress": False},
+ {"id": "o_gh_secret_scan", "name": "GitHub Secret Scan", "nexus": "osint:gh_secret_scan", "desc": "Scan GitHub user's public repos for leaked secrets/passwords", "sats": 200, "egress": False},
+ {"id": "o_ghkeysgpg", "name": "GitHub GPG Key", "nexus": "osint:ghkeysgpg", "desc": "Whether a user publishes a GPG key", "sats": 200, "egress": False},
+ {"id": "o_github_code", "name": "GitHub Code Search", "nexus": "osint:github_code", "desc": "GitHub unauthenticated code search (10 results)", "sats": 200, "egress": False},
+ {"id": "o_githubsearch", "name": "GitHub Repo Search", "nexus": "osint:githubsearch", "desc": "Search GitHub repos by keyword", "sats": 200, "egress": False},
+ {"id": "o_gitlab", "name": "GitLab", "nexus": "osint:gitlab", "desc": "Public user profile", "sats": 200, "egress": False},
+ {"id": "o_gleif_name", "name": "GLEIF Name", "nexus": "osint:gleif_name", "desc": "GLEIF fuzzy legal-entity name → LEI codes", "sats": 200, "egress": False},
+ {"id": "o_golangpkg", "name": "Go Module", "nexus": "osint:golangpkg", "desc": "Latest version of a Go module", "sats": 200, "egress": False},
+ {"id": "o_goproxy", "name": "Go Module", "nexus": "osint:goproxy", "desc": "Latest version of a Go module via module proxy (no key)", "sats": 200, "egress": False},
+ {"id": "o_gravatarfull", "name": "Gravatar Profile", "nexus": "osint:gravatarfull", "desc": "Full public Gravatar profile + linked accounts", "sats": 200, "egress": False},
+ {"id": "o_greynoise", "name": "GreyNoise", "nexus": "osint:greynoise", "desc": "Is the IP a known internet scanner — benign/malicious", "sats": 200, "egress": False},
+ {"id": "o_hackernews", "name": "Hacker News", "nexus": "osint:hackernews", "desc": "Profile: karma, age, activity", "sats": 200, "egress": False},
+ {"id": "o_hashid", "name": "Hash Identifier", "nexus": "osint:hashid", "desc": "Guess hash algorithm from length/charset", "sats": 200, "egress": False},
+ {"id": "o_hashtext", "name": "Hash Text", "nexus": "osint:hashtext", "desc": "md5/sha1/sha256/sha512 of text (offline)", "sats": 200, "egress": False},
+ {"id": "o_headers", "name": "HTTP / Security", "nexus": "osint:headers", "desc": "Headers + security-header scorecard", "sats": 200, "egress": True},
+ {"id": "o_hexdump", "name": "Hexdump", "nexus": "osint:hexdump", "desc": "Offset/hex/ASCII hexdump of input (offline)", "sats": 200, "egress": False},
+ {"id": "o_hibp", "name": "HIBP Password Check", "nexus": "osint:hibp", "desc": "Check if a password appeared in breaches via HIBP k-anonymity (no key)", "sats": 200, "egress": False},
+ {"id": "o_hibp_email", "name": "HIBP Email Breaches", "nexus": "osint:hibp_email", "desc": "Email breach exposure via XposedOrNot (HIBP-compatible, no key)", "sats": 200, "egress": False},
+ {"id": "o_hnsearch", "name": "HN Search", "nexus": "osint:hnsearch", "desc": "Search Hacker News stories/comments", "sats": 200, "egress": False},
+ {"id": "o_hnuser", "name": "Hacker News User", "nexus": "osint:hnuser", "desc": "HN profile: karma, created, submission count", "sats": 200, "egress": False},
+ {"id": "o_holidays", "name": "Public Holidays", "nexus": "osint:holidays", "desc": "Country public holidays this year (nager.at)", "sats": 200, "egress": False},
+ {"id": "o_homoglyph", "name": "Homoglyph", "nexus": "osint:homoglyph", "desc": "Detect confusable/mixed-script spoofing chars (offline)", "sats": 200, "egress": False},
+ {"id": "o_hostname", "name": "Reverse DNS", "nexus": "osint:hostname", "desc": "IP → hostname via reverse DNS", "sats": 200, "egress": False},
+ {"id": "o_hstspreload", "name": "HSTS Preload", "nexus": "osint:hstspreload", "desc": "Is the domain on the browser HSTS preload list", "sats": 200, "egress": False},
+ {"id": "o_htmlcomments", "name": "HTML Comments", "nexus": "osint:htmlcomments", "desc": "Extract HTML comments — leaked TODOs/paths/software", "sats": 200, "egress": False},
+ {"id": "o_htmlencode", "name": "HTML Encode/Decode", "nexus": "osint:htmlencode", "desc": "HTML entity encode/decode", "sats": 200, "egress": False},
+ {"id": "o_httpcode", "name": "HTTP Status Code", "nexus": "osint:httpcode", "desc": "HTTP status code meaning & family (offline)", "sats": 200, "egress": False},
+ {"id": "o_httping", "name": "HTTP Ping", "nexus": "osint:httping", "desc": "Reachability + response timing", "sats": 200, "egress": False},
+ {"id": "o_httpmethods", "name": "HTTP Methods", "nexus": "osint:httpmethods", "desc": "Allowed methods + TRACE/risky-verb check", "sats": 200, "egress": False},
+ {"id": "o_hudsonrock", "name": "HudsonRock Stealer", "nexus": "osint:hudsonrock", "desc": "Stealer-log exposure check via HudsonRock Cavalier free API", "sats": 200, "egress": False},
+ {"id": "o_huggingface", "name": "HuggingFace", "nexus": "osint:huggingface", "desc": "HuggingFace user profile or model card", "sats": 200, "egress": False},
+ {"id": "o_imagerev", "name": "Reverse Image", "nexus": "osint:imagerev", "desc": "Google Lens / Yandex / Bing / TinEye search links", "sats": 200, "egress": False},
+ {"id": "o_infra_fingerprint", "name": "Infra Fingerprint", "nexus": "osint:infra_fingerprint", "desc": "Infrastructure fingerprinting + hosting", "sats": 200, "egress": False},
+ {"id": "o_intelx_email", "name": "XposedOrNot Breach", "nexus": "osint:intelx_email", "desc": "Email breach exposure — breach names, data types, paste hits (XposedOrNot, no key)", "sats": 200, "egress": False},
+ {"id": "o_internetdb", "name": "Shodan InternetDB", "nexus": "osint:internetdb", "desc": "Open ports, CPEs, tags, known CVEs for a host", "sats": 200, "egress": False},
+ {"id": "o_ioc_reputation", "name": "IOC Reputation", "nexus": "osint:ioc_reputation", "desc": "Cross-check IP/domain/hash across feeds", "sats": 200, "egress": False},
+ {"id": "o_ip_math", "name": "IP Math", "nexus": "osint:ip_math", "desc": "CIDR: network/broadcast/range/host count (offline)", "sats": 200, "egress": False},
+ {"id": "o_ipfull", "name": "IP Full Profile", "nexus": "osint:ipfull", "desc": "Rich geo+ASN+proxy/mobile/hosting flags", "sats": 200, "egress": False},
+ {"id": "o_ipgeo", "name": "IP Geolocation", "nexus": "osint:ipgeo", "desc": "Geo, ISP, ASN, proxy/hosting flags", "sats": 200, "egress": False},
+ {"id": "o_ipint", "name": "IP ↔ Integer", "nexus": "osint:ipint", "desc": "IPv4 ↔ integer ↔ hex (offline)", "sats": 200, "egress": False},
+ {"id": "o_ipv4classify", "name": "IP Classify", "nexus": "osint:ipv4classify", "desc": "Classify IP: private/loopback/multicast/global (offline)", "sats": 200, "egress": False},
+ {"id": "o_ipwhois", "name": "IP WHOIS / RDAP", "nexus": "osint:ipwhois", "desc": "Network owner, range, abuse contact", "sats": 200, "egress": False},
+ {"id": "o_isbn", "name": "ISBN Book", "nexus": "osint:isbn", "desc": "Book metadata (OpenLibrary) + checksum", "sats": 200, "egress": False},
+ {"id": "o_isexitnode", "name": "Tor Exit Check", "nexus": "osint:isexitnode", "desc": "Is this a Tor exit node?", "sats": 200, "egress": False},
+ {"id": "o_isin", "name": "ISIN Validate", "nexus": "osint:isin", "desc": "Validate ISIN security identifier check digit (offline)", "sats": 200, "egress": False},
+ {"id": "o_isotime", "name": "Timestamp Convert", "nexus": "osint:isotime", "desc": "Parse/convert a timestamp (offline)", "sats": 200, "egress": False},
+ {"id": "o_jarm", "name": "JARM / InternetDB", "nexus": "osint:jarm", "desc": "Shodan InternetDB: ports, CPEs, vulns, tags (no key)", "sats": 200, "egress": False},
+ {"id": "o_jslibs", "name": "JS Libraries", "nexus": "osint:jslibs", "desc": "Enumerate